把 Order 3 的 net user 命令直接寫成 net user User,省掉「先建小寫 user 再 Rename-LocalUser 改成大寫 User」這個迂迴。Setup.ps1 的 rename-hide-accounts 段同時改名為 hide-admin-account,內容只剩設 SpecialAccounts\\UserList\\Admin=0 這一行——隱藏 Admin 仍然由 Setup.ps1 負責。 文件全部同步:DEPLOY/QUICKSTART/SUMMARY/README/CLAUDE 把「先建小寫再改名」的 bulletproof 解釋與相關段名都拿掉。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8.1 KiB
SUMMARY — 這份 unattend.xml 是怎麼長出來的
為什麼需要它
手動裝一台 Windows 是這樣的:按「下一步」、選語系、接受 EULA、分割磁碟、填序號、建帳號、略過 OOBE 一堆隱私問題、登入後開 PowerShell 裝軟體。一台大約 20–30 分鐘,十台就是半天。而且每次點的選項還不一定一樣。
部署這件事的本質是:把「一次性的人工操作」變成「可重複的檔案」。
Windows 給的答案叫 Answer File(回應檔,通常是 unattend.xml)。開機時 Setup 會去讀這份 XML,把原本需要人類回答的問題一題一題自己答完。
設計的三條主線
這份 unattend.xml 不是隨便把欄位湊齊,而是圍繞三個目標組出來的:
1. 零互動(Zero Touch)
從 USB 開機到桌面出現,中間不應該有任何畫面需要鍵盤滑鼠。
為了做到這件事,檔案裡有一連串「否決畫面」的開關:
HideEULAPage/HideOEMRegistrationScreen/HideOnlineAccountScreens/HideWirelessSetupInOOBE— 把 OOBE 那些煩人的頁面全部隱藏SkipMachineOOBE/SkipUserOOBE— 機器層與使用者層的 OOBE 都略過AcceptEula=true— 安裝時的授權合約預先接受WillShowUI=OnErroronProductKey— 序號正確就不跳視窗,錯了才問
這些不是可有可無的選項,而是「零互動」這個目標的具體落實。
2. 確定性(Deterministic)
同一份 unattend.xml 套上去,結果應該一樣。
- 磁碟分割是硬寫的:清空 Disk 0 → EFI 500 MB → MSR 128 MB → 剩下全給 Windows。不讓 Setup 自己猜,不處理遷移場景。
- 語系/時區是硬寫的:
zh-TW+Taipei Standard Time。不依賴 OEM image 預設。 - 唯一的例外是電腦名稱:XML 裡設
<ComputerName>*</ComputerName>讓 Windows Setup 先給一個隨機名(否則 specialize 階段會因為%RAND:5%之類的假巨集字面送入而失敗),再由FirstLogonCommands的Rename-Computer把它改成PC-XXXXX格式並在收尾shutdown /r時一起生效。
這是一個取捨:確定性優先,但在會造成衝突的地方(主機名)讓步。
3. 交棒到腳本(Hand-off to Scripts)
XML 能做的事有上限。超過這條線(裝軟體、設 Registry、拉 Git repo、裝驅動)就不該硬塞 XML,應該交給 PowerShell。
FirstLogonCommands 就是交棒點:
Order 1 → net accounts (一行搞定的雜事直接 cmd,密碼永不過期)
Order 2 → Rename-Computer (主機名改 PC-XXXXX,重開後生效)
Order 3 → net user User 1234 /add (建立一般使用者 User,強制首次登入改密)
Order 4 → Copy Setup.ps1 from Ventoy USB (掃磁碟找 \ventoy\script\Setup.ps1 → C:\Scripts\)
Order 5 → powershell -File Setup.ps1 (13 段 post-install,看下表)
Order 6 → shutdown /r (重開機收尾,讓 rename 生效)
Setup.ps1 內部分兩個 phase(用空的 wait-network 段切開):
[Phase 1:本機設定,不需網路]
firewall-ssh-22 → SSH 22/TCP inbound 防火牆規則
rdp-enable-3389 → 啟用 RDP + 3389 防火牆群組
icmp-echo-allow → ICMPv4 Echo Request inbound(外部 ping 得到)
winrm-enable → Enable-PSRemoting + winrm quickconfig + TrustedHosts=*
onedrive-remove → /uninstall + 清啟動鍵 + 移除 CLSID
cortana-websearch-off → AllowCortana=0、DisableWebSearch=1、SearchBoxSuggestions=1
apply-ui-ime-default-profile → reg load Default profile,套 Dark mode / 工作列靠左 / 顯示副檔名 / 預設英數鍵盤 / 停 IME 切換熱鍵
hide-admin-account → 從登入畫面隱藏 Admin(帳號仍存在仍可用)
[Phase 2:需要網路]
wait-network → 等 ICMP 通到 8.8.8.8,上限 60 秒
openssh-server-install → Add-WindowsCapability OpenSSH.Server + Start-Service sshd
kms-activate → 試 $InternalKmsServers → $PublicKmsServers,第一個 successful 就停
winget-install-chrome → winget install Google.Chrome --silent --scope machine
remove-windows-ai → irm zoicware/RemoveWindowsAI | iex -nonInteractive -AllOptions
win11debloat → 下載 Raphire/Win11Debloat → -Silent -Config
remove-uwp-apps → 22 個 AppxPackage(BingNews、Xbox*、MSTeams、Photos…)
install-telegram → 下載 telegram.org/dl/desktop/win64 + /VERYSILENT
cleanup-unattend-files → 刪 C:\Windows\Panther\unattend.xml(清明碼密碼殘留)
這讓 unattend.xml 保持「只負責 Windows 本身的安裝+簡單到不會壞的 cmd」,其他事情外包給 Setup.ps1。職責分離之後,改軟體清單不需要動 XML,改部署流程也不需要動 Setup 邏輯。
為什麼把
Setup.ps1放在 Ventoy USB 上、不直接 base64 內嵌進unattend.xml:(1)unattend.xml仍是 Windows SIM 可驗證的純 schema XML;(2)Setup.ps1改完只要cp一次就更新,不用重 escape;(3) 每段Section寫到setup.log才方便排查。代價是:USB 必須留到第一次登入完成才能拔。
remove-windows-ai跑的是main分支的腳本,每次部署抓的版本可能不同。要確定性、可稽核的部署,把Setup.ps1中對應 URL 的/main/換成 commit SHA。
為什麼分成三個 Pass
Windows Setup 有七個 pass,本檔只用三個。不是偷懶,是三個 pass 對應三個時機,各自只能做某些事:
| Pass | 時機 | 此時系統長什麼樣 | 只能在這裡做的事 |
|---|---|---|---|
windowsPE |
從 USB 開機、Windows 還沒裝 | 記憶體中的迷你作業系統 | 磁碟分割、選要裝哪個映像、產品序號 |
specialize |
映像展開完、第一次開機前 | C:\ 已存在但沒有使用者 | 電腦名稱、加網域、硬體相關 |
oobeSystem |
首次開機、OOBE 跑起來時 | 完整 Windows、等使用者建帳號 | 建本機帳號、AutoLogon、FirstLogonCommands |
每個欄位都有「必須」出現的 pass,放錯地方 Windows 就不讀。 語系要在 windowsPE 設(給 WinPE 用)也要在 oobeSystem 設(給 OOBE 用)——這就是為什麼 International-Core 在檔案裡出現兩次,不是冗餘。
妥協的地方
幾個「明知道不理想、但為了能動先這樣」的決定:
| 妥協 | 為什麼這樣 | 正式環境怎麼改 |
|---|---|---|
| 密碼明碼 | 方便第一次測試,看得懂 | 用 Windows SIM 產生 Base64,或交給 MDT/SCCM 管 |
| 本機帳號 + AutoLogon | 純離線可跑 | 改成 Domain Join + 網域管理員登入 |
| ProtectYourPC=3 | 略過 OOBE 的隱私頁,預設行為 | 依公司政策調整,或用 GPO 蓋掉 |
| 單一 Disk 0 + 整碟清空 | 對測試 VM 最安全的假設 | 多硬碟環境要加 DiskID 判斷邏輯 |
Setup.ps1 用磁碟掃描從 Ventoy USB 拷貝 |
不需要架網路服務、跨網段也能跑、USB 拔掉也能在現場補救 | 內部 IT 場合改用 SCCM/Intune 派送,或乾脆 base64 內嵌進 XML 拿到完全自包含 |
$PublicKmsServers 用第三方 KMS |
沒授權主機也能跑通 | 把 $InternalKmsServers 改成自家 KMS 主機,公開那串不再被 fallback |
這些不是 bug,是當前脈絡下的合理起點。要產品化就對照這張表一個一個升級。
對話的起點
這份檔案最初是從一個問題開始的:
「透過回應檔案(Answer files,通常檔名為 unattend.xml)來自動化配置 Windows 安裝設定。自動處理像是語系選擇、建立使用者帳號、輸入序號,甚至是執行自定義的腳本。」
答案就是上面講的那套設計。剩下的事(產 USB、跑 VM、除錯)都寫在 QUICKSTART.md,每個欄位做什麼事寫在 README.md。
這份 SUMMARY.md 只想講一件事:為什麼這份 XML 長這樣。 以後如果要改,知道每條線在撐著什麼,就不會拆錯地方。