把 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>
128 lines
8.1 KiB
Markdown
128 lines
8.1 KiB
Markdown
# 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=OnError` on `ProductKey` — 序號正確就不跳視窗,錯了才問
|
||
|
||
這些不是可有可無的選項,而是「零互動」這個目標的具體落實。
|
||
|
||
### 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 長這樣。** 以後如果要改,知道每條線在撐著什麼,就不會拆錯地方。
|