改用 Get-Printer 系列 cmdlet 自己做備份還原。LPR 埠會被靜默建成 RAW、驅動路徑爆 260 字元、WPP 擋掉第三方驅動。
發布於 2026-09-05・約 5 分鐘
換電腦最麻煩的一項是印表機。使用者桌上那台可能是十年前 IT 設好的,IP、佇列名稱、雙面預設值,沒有人記得。
以前有官方工具 printbrm.exe(Printer Migration),匯出成 .printerExport 再匯入就好。Windows 10 2004 之後跟 Windows 11 已經把它移除,而且不再支援,DISM 也裝不回來。
printbrm.exe
.printerExport
所以只能自己用 PowerShell 的印表機管理 cmdlet 重做一份。以下是實際做出來之後才知道的事。
這是最貴的一個坑。備份的時候我照 Get-PrinterPort 讀到的欄位存下來,還原時這樣建:
Get-PrinterPort
Add-PrinterPort -Name "IP_192.168.1.50" -PrinterHostAddress "192.168.1.50"
印表機建起來了,內容看起來也對,就是印不出東西。
Add-PrinterPort 用 -PrinterHostAddress 建出來的一律是 RAW 協定、9100 埠。LPR 是完全不同的一組參數:
Add-PrinterPort
-PrinterHostAddress
Add-PrinterPort -Name "IP_192.168.1.50" ` -LprHostAddress "192.168.1.50" ` -LprQueueName "lp" ` -LprByteCounting
沒有任何錯誤訊息告訴你協定被改掉了。所以我在建完之後回讀一次驗證:
$chk = Get-PrinterPort -Name $portInfo.Name -ErrorAction Stop $chkIsLpr = ([int]$chk.Protocol -eq 2) if ($chkIsLpr -ne $isLpr) { Write-Warning "驗證失敗:連接埠建立後協定為 $(if($chkIsLpr){'LPR'}else{'RAW'}),與備份不符。" }
Protocol 是 1 或 2,1 = RAW(預設 9100),2 = LPR(預設 515,而且一定要有佇列名稱)。
Protocol
同名連接埠已經存在的時候我選擇不動它,只在協定不符時警告。改一個正在被其他印表機用的埠,副作用範圍太大。
LPR 的 byte counting 就讀不到。這些要從 TCPMon 的登錄機碼補:
$regPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Print\Monitors\Standard TCP/IP Port\Ports\$($Port.Name)" $reg = Get-ItemProperty -Path $regPath -ErrorAction Stop if ($null -ne $reg.Protocol) { $protoNum = [int]$reg.Protocol } if ($reg.Queue) { $queue = $reg.Queue } # TCPMon 用「Double Spool」這個值存 LPR byte counting if ($null -ne $reg."Double Spool") { $byteCounting = [bool]$reg."Double Spool" }
Double Spool 這個名字跟它實際存的東西沒什麼關係,只能靠比對機器上的登錄值認出來。
Double Spool
另外 Protocol 欄位有時候回 UInt32、有時候回字串,取決於來源。兩種都要能解析,不然在某些機器上會整支炸掉。
UInt32
Get-PrinterDriver 給得到 InfPath,那個 .inf 所在的資料夾就是 Driver Store 裡的一份完整驅動:
Get-PrinterDriver
InfPath
.inf
$driverStoreDir = Split-Path $driver.InfPath -Parent Copy-Item -Path (Join-Path $driverStoreDir "*") -Destination $destDir -Recurse -Force
還原時用 pnputil 註冊,再 Add-PrinterDriver:
pnputil
Add-PrinterDriver
& pnputil.exe /add-driver "$($inf.FullName)" /install
pnputil 回傳 259 不是錯誤。 259 的意思是「沒有裝置符合此驅動」,對純軟體的印表機驅動來說完全正常,這時候印表機根本還沒建起來。我一開始把它當失敗處理,整批印表機都還原不了。
驅動資料夾名稱要縮短。 Driver Store 的原始路徑本來就很長(一長串雜湊),再放進 USB 的深層目錄就會超過 260 字元上限,複製直接失敗。我改成加流水號、名稱截到 30 字元:
$safeDriverName = ($driver.Name -replace '[\\/:*?"<>|]', '_') if ($safeDriverName.Length -gt 30) { $safeDriverName = $safeDriverName.Substring(0, 30) } $folderName = "{0:D2}_{1}" -f $driverIndex, $safeDriverName.Trim()
同一個驅動被多台印表機共用時記得記在字典裡,不要匯出好幾份。三台同型號印表機各一份驅動,備份大小差三倍。
Windows 受保護的列印模式(Windows Protected Print Mode)開啟時,第三方印表機驅動會被系統移除,而且裝不回去。你在來源機器上備份到的驅動,在同樣開著 WPP 的目標機器上一定安裝失敗。
$wppKey = "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Printers\WPP" $v = Get-ItemProperty -Path $wppKey -Name "WindowsProtectedPrintMode" -ErrorAction SilentlyContinue return ($null -ne $v -and $v.WindowsProtectedPrintMode -eq 1)
備份端只警告不擋(讀不到驅動不代表不能備份其他設定),還原端直接擋下來,因為繼續跑一定失敗,先講清楚比讓人事後除錯省時間。
一、只有一台印表機時 (Get-Printer).Name 回傳字串不是陣列。 後面的 -contains 判斷跟 += 累加都會壞掉,累加會變成字串相接。@() 一定要加:
(Get-Printer).Name
-contains
+=
@()
$existingPrinters = @((Get-Printer -ErrorAction SilentlyContinue).Name)
這是 PowerShell 最常見的一類 bug,測試環境有兩台印表機就永遠測不出來。
二、內建虛擬印表機用連接埠名稱判斷。 Print to PDF、XPS、傳真這些目標機器本來就有,備份它們只是浪費空間。顯示名稱會跟著系統語言變,連接埠名稱不會:
$builtInPorts = @("PORTPROMPT:", "XPSPort:", "SHRFAX:", "nul:", "Microsoft.Office.OneNote")
三、USB / LPT 的印表機搬不了。 沒有 PrinterHostAddress 而且連接埠名稱以 USB、LPT、COM、DOT4 開頭的,設定沒辦法靠檔案搬移,只能把線接到新電腦讓 Windows 重新偵測。備份時就要警告,不要讓人交機時才發現。
PrinterHostAddress
USB
LPT
COM
DOT4
最後存成一個 JSON 加一個驅動資料夾:
輸出位置我固定寫在「指令碼自己旁邊」,從 USB 執行就直接寫在 USB 上。IT 拿著隨身碟在兩台機器之間跑的時候,少一個要輸入路徑的步驟就少一次打錯。
預設印表機是 HKCU 的設定,讀出來的是「執行備份的那個帳號」的預設值。這一點跟其他 per-user 設定一樣,換帳號執行就會抓到別人的。
HKCU
標籤:Windows、印表機、PowerShell、驅動程式、部署