Compare commits

...

4 Commits

Author SHA1 Message Date
935cddbbf2 新增 steps/ 拆解版本(每段獨立 .bat 雙擊跑)+ Run-Setup.bat;端對端驗證後修 11 處 bug
steps/ 把 Setup.ps1 拆成 18 個獨立步驟(00-17),每段:
- _common.ps1 提供 Log/Section/Set-RegValue/Wait-Network helper
- 各段一個 .ps1 + 同名 .bat(自動 UAC 提權)
- _admin-shell.bat 開一個已提權 cmd,從那裡跑各 .bat 不再被 UAC 問
- RUN-ALL.bat 依序跑 00..17
- _template.bat 全部 .bat 共用樣板
- README.txt 用法說明

從 macOS WinRM 端對端跑過所有 18 段、發現並修:
- 02-rdp-enable-3389:'Remote Desktop' display group 在某些 Win11 SKU 不存在,
  改顯式 New-NetFirewallRule + Restart-Service TermService
- 04-winrm-enable:開頭加 Set-NetConnectionProfile -NetworkCategory Private fallback,
  避免 winrm quickconfig 在 Public profile 拒絕啟用
- 11-kms-activate:原本 'successful|成功' 正則在 Big5 codepage 下 mojibake 出 '{x,y}',
  改用 SoftwareLicensingProduct.LicenseStatus -eq 1 WMI 判斷
- 12-winget-install-chrome:加 --source winget,避開 msstore 憑證錯誤造成的歧義
- 13-remove-windows-ai:拿掉 -AllOptions,顯式 10 個 option 排除 UpdateCleanupCheck
  (DISM Cleanup-Image + sfc + MessageBox 在 Session-0 default Yes 觸發 Restart-Computer)
- 14-win11debloat:加 -Sysprep(寫到 Default profile 不撞 HKCU 鎖),
  從 config 移除 DisableSearchHistory/DisableSearchHighlights/HideSearchTb
  (它們的 reg.exe import 會被「存取被拒」擋住)
- 07-apply-ui-ime-default-profile:補上 IsDeviceSearchHistoryEnabled / IsDynamicSearchBoxEnabled
  替代上面從 Win11Debloat 拔掉的兩項
- 新增 00-network-private 步驟把所有 NetConnectionProfile 改 Private
- 全部 19 個 .ps1 加 UTF-8 BOM,避免 CHT Windows PS5.1 fallback ANSI 讀中文 mojibake
- 全部 .bat REM 註解改 ASCII,根除「'券蠶setup.log' 不是內部命令」那種亂碼

Run-Setup.bat:當 unattend.xml FirstLogonCommands 沒成功跑起 Setup.ps1 時,
把這支從 USB 雙擊執行,會 UAC 提權 + 從 USB 重拷 + 跑 + console 即時印 + 收 log
2026-04-28 08:35:48 +08:00
ef9f5d16bc Setup.ps1 加 WU 暫停/恢復;unattend.xml Order 5 收 stderr 進 log
- Setup.ps1 開頭暫停 Windows Update(PauseUpdatesExpiryTime + 停 wuauserv/UsoSvc/BITS),
  結尾解暫停 + UsoClient 觸發掃描,避免跟 post-install 搶頻寬/CPU
- Win11Debloat config 從 here-string 改成 hashtable + ConvertTo-Json,
  繞開 PowerShell 5.1 ANSI 讀取時 here-string parse 失敗的 case
- Log 函式加 Write-Host,console 視窗能即時看 START/DONE
- unattend.xml Order 5 把 stdout+stderr 全部 redirect 到 firstlogon-runsetup.log,
  Setup.ps1 連 parse error 都會留紀錄;Order 3 改空密碼 + 強制改密
2026-04-28 08:35:20 +08:00
b1e5b6912a unattend.xml 直接以 User(首字大寫)建帳號;Setup.ps1 拿掉 rename-LocalUser
把 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>
2026-04-27 13:59:15 +08:00
c84e18bd9d 新增 Setup.ps1(13 段 post-install);FirstLogonCommands 改從 Ventoy USB 拷腳本
把分散在 unattend.xml inline 的網路相依步驟(OpenSSH/Telegram/RemoveAI)和
新加入的 10 個 post-install 動作整合進獨立的 Setup.ps1,由 FirstLogonCommands
Order 4 從 Ventoy USB(/ventoy/script/Setup.ps1)掃磁碟複製到 C:\Scripts\,
Order 5 執行。腳本分兩 phase:

Phase 1(不需網路):SSH 22 防火牆、RDP+3389、ICMP Echo、WinRM TrustedHosts=*、
移 OneDrive、關 Cortana/WebSearch、套 UI/IME 預設到 Default profile、user→User
改名 + 從登入畫面隱藏 Admin。

Phase 2(要網路,wait-network 後):OpenSSH Server FoD、KMS 啟用(先試
$InternalKmsServers 後 fallback 公開 KMS)、winget Chrome、RemoveWindowsAI、
Win11Debloat、22 個 UWP 移除、Telegram、刪 Panther 殘留。

每段以 Section 包裝寫獨立 START/DONE/ERR 到 C:\Windows\Temp\setup.log,方便逐段
重跑與排查。

文件全部同步:DEPLOY/QUICKSTART/SUMMARY/README/CLAUDE 都加上 Setup.ps1 的
USB 部署、cp 步驟、Order 表、排查指引。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 13:36:13 +08:00
50 changed files with 1573 additions and 108 deletions

View File

@@ -4,12 +4,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Repository nature ## Repository nature
This is a **configuration template repo**, not a code project. It ships two artifacts: This is a **configuration template repo**, not a code project. It ships three artifacts:
- `unattend.xml` — the Windows Setup answer file. - `unattend.xml` — the Windows Setup answer file.
- `Setup.ps1` — the post-install PowerShell that runs at first logon (firewall/WinRM/RDP/ICMP, KMS, Win11Debloat, RemoveWindowsAI, OneDrive removal, Cortana off, Default-profile UI/IME defaults, account rename, Telegram, Chrome via winget). Lives in the repo and is also copied to the Ventoy USB at `/ventoy/script/Setup.ps1`. At first logon `unattend.xml` scans removable drives for `\ventoy\script\Setup.ps1`, copies it to `C:\Scripts\Setup.ps1`, then runs it.
- `ventoy.json` — the Ventoy Auto Install plugin config that tells Ventoy which ISO to pair with which template. - `ventoy.json` — the Ventoy Auto Install plugin config that tells Ventoy which ISO to pair with which template.
Deployment is via **[Ventoy](https://www.ventoy.net/)**, not Rufus or direct ISO modification. The user keeps a Ventoy USB with multiple Windows ISOs; `ventoy.json` at `/ventoy/ventoy.json` on the USB and `unattend.xml` at `/ventoy/script/unattend.xml` let Ventoy inject the answer file at boot without touching the ISO. Assume this Ventoy-based flow when reasoning about deploy-time behavior. Deployment is via **[Ventoy](https://www.ventoy.net/)**, not Rufus or direct ISO modification. The user keeps a Ventoy USB with multiple Windows ISOs; `ventoy.json` at `/ventoy/ventoy.json` on the USB, `unattend.xml` at `/ventoy/script/unattend.xml`, and `Setup.ps1` at `/ventoy/script/Setup.ps1` let Ventoy inject the answer file at boot without touching the ISO and have the post-install script ready next to it. Assume this Ventoy-based flow when reasoning about deploy-time behavior.
There is no build, no test suite, no package manager, and no runtime. Changes are validated by: There is no build, no test suite, no package manager, and no runtime. Changes are validated by:
@@ -41,10 +42,19 @@ There is no way to "run" this repo on the dev machine (macOS). All verification
- **`processorArchitecture="amd64"`** is hardcoded on every `<component>`. ARM64 deployments require a global find-and-replace to `arm64`. - **`processorArchitecture="amd64"`** is hardcoded on every `<component>`. ARM64 deployments require a global find-and-replace to `arm64`.
- **Password is plaintext** (`<PlainText>true</PlainText>`). This is a deliberate testing-only choice documented in `SUMMARY.md`. For production, generate Base64 via Windows SIM; do not hand-encode. - **Password is plaintext** (`<PlainText>true</PlainText>`). This is a deliberate testing-only choice documented in `SUMMARY.md`. For production, generate Base64 via Windows SIM; do not hand-encode.
- **`ComputerName` does not support `%RAND:5%` or any other macro.** Windows Setup only accepts a literal name, `*` (auto-generate a random name like `DESKTOP-ABC123D`), or an empty value. `%RAND:N%` is an MDT/SCCM task-sequence variable — under the Ventoy → raw `unattend.xml` path it is passed verbatim as a literal string, fails NetBIOS character validation (`%` is not allowed), and causes the specialize pass to abort with a `Microsoft-Windows-Shell-Setup` error. If a `PC-XXXXX` style name is required, set `<ComputerName>*</ComputerName>` here and do the rename in `FirstLogonCommands` via `Rename-Computer` (a reboot is needed for it to take effect — combine with the existing `shutdown /r` step). (Distinct from `$$VT_*$$`, which *is* a real Ventoy pre-processor substitution.) - **`ComputerName` does not support `%RAND:5%` or any other macro.** Windows Setup only accepts a literal name, `*` (auto-generate a random name like `DESKTOP-ABC123D`), or an empty value. `%RAND:N%` is an MDT/SCCM task-sequence variable — under the Ventoy → raw `unattend.xml` path it is passed verbatim as a literal string, fails NetBIOS character validation (`%` is not allowed), and causes the specialize pass to abort with a `Microsoft-Windows-Shell-Setup` error. If a `PC-XXXXX` style name is required, set `<ComputerName>*</ComputerName>` here and do the rename in `FirstLogonCommands` via `Rename-Computer` (a reboot is needed for it to take effect — combine with the existing `shutdown /r` step). (Distinct from `$$VT_*$$`, which *is* a real Ventoy pre-processor substitution.)
- **`FirstLogonCommands` references `C:\Scripts\Setup.ps1`** but this repo does **not** contain that script. It is expected to be placed on the target by other means (baked into the image, copied from USB earlier in `FirstLogonCommands`, or pulled from network). - **`Setup.ps1` is shipped via the Ventoy USB, not embedded in `unattend.xml`.** `FirstLogonCommands` Order 4 scans every mounted filesystem drive for `\ventoy\script\Setup.ps1` and copies the first match to `C:\Scripts\Setup.ps1`; Order 5 runs it. Two consequences: (1) the Ventoy USB **must remain plugged in until first logon completes** — yanking it after WinPE means Setup.ps1 never makes it to disk and the post-install steps silently skip (`C:\Windows\Temp\firstlogon-copysetup.log` will say `NOT FOUND`); (2) editing `Setup.ps1` in the repo is not enough — re-copy to `/Volumes/Ventoy/ventoy/script/Setup.ps1` before each test deploy. Both `firstlogon-copysetup.log` and `setup.log` live under `C:\Windows\Temp\`.
- **`Setup.ps1` runs as the AutoLogon `Admin` session** in `FirstLogonCommands`. It modifies the Default profile's `NTUSER.DAT` (loaded via `reg load HKU\SetupDefault C:\Users\Default\NTUSER.DAT`) to push UI/IME defaults to *future* user logins — it does **not** touch the AutoLogon `Admin` session itself, and it cannot reach the just-created `user`/`User` profile because that profile dir doesn't exist until that user first logs in. Anything you want applied to `Admin`'s already-active session has to go through `HKCU` directly.
- **The `User` account is created directly in `unattend.xml` Order 3** with capitalised name (`net user User 1234 /add`); there is no rename step. `Setup.ps1`'s `hide-admin-account` section only handles hiding `Admin` from the login screen via `SpecialAccounts\UserList\Admin=0`. If you change the account name convention, only `unattend.xml` Order 3 needs to change.
## When making changes ## When making changes
- Preserve the `xmlns="urn:schemas-microsoft-com:unattend"` namespace and the `wcm:action="add"` attributes on list items — Windows SIM emits these and Setup requires them. - Preserve the `xmlns="urn:schemas-microsoft-com:unattend"` namespace and the `wcm:action="add"` attributes on list items — Windows SIM emits these and Setup requires them.
- Every `<component>` needs all four identity attributes: `name`, `processorArchitecture`, `publicKeyToken="31bf3856ad364e35"`, `language="neutral"`, `versionScope="nonSxS"`. - Every `<component>` needs all four identity attributes: `name`, `processorArchitecture`, `publicKeyToken="31bf3856ad364e35"`, `language="neutral"`, `versionScope="nonSxS"`.
- The Chinese-language docs (`README.md`, `QUICKSTART.md`, `SUMMARY.md`) explain the *why* behind design decisions — consult `SUMMARY.md` before proposing structural changes, as several current choices are explicit tradeoffs, not oversights. - The Chinese-language docs (`README.md`, `QUICKSTART.md`, `SUMMARY.md`, `DEPLOY.md`) explain the *why* behind design decisions — consult `SUMMARY.md` before proposing structural changes, as several current choices are explicit tradeoffs, not oversights.
## When making changes to `Setup.ps1`
- Sections are wrapped with the `Section` helper (`Section 'name' { ... }`) which logs `START` / `DONE` / `ERR` to `C:\Windows\Temp\setup.log`. Each one is idempotent and tolerates partial earlier runs — preserve that property when adding new sections, because deploys do get re-tested by re-running `Setup.ps1` manually.
- Phase ordering is enforced by file order, not by metadata: Phase 1 (no network — firewall, registry, Default profile, account rename) runs first; `wait-network` then blocks up to 60 s; Phase 2 (network — OpenSSH FoD, KMS, winget, RemoveAI, Win11Debloat, UWP removal, Telegram) follows. Don't move a network-dependent section above `wait-network`.
- `$Win11DebloatConfigJson` and `$UwpRemoveList` are mirrored from `windows-remote-toolkit/config/win11debloat-config.json` and `remove-apps-config.json`. They were intentionally **inlined** rather than fetched at runtime so the script is self-contained on the Ventoy USB. If you change one, update the comment pointing at the toolkit source so the next sync isn't accidental.
- `$InternalKmsServers` is intentionally empty — the public KMS list (`kms.digiboy.ir`, `kms8.msguides.com`, `kms.03k.org`, `kms.chinancce.com`) is the fallback. If a deployment is on a network with its own KMS host, that's the only line to change.

141
DEPLOY.md
View File

@@ -26,9 +26,12 @@ USB 根目錄兩個 ISO
└── ventoy/ └── ventoy/
├── ventoy.json ← 對應兩個 ISO都指向同一份 template ├── ventoy.json ← 對應兩個 ISO都指向同一份 template
└── script/ └── script/
── unattend.xml ← 與 repo 根目錄的 unattend.xml 相同 ── unattend.xml ← 與 repo 根目錄的 unattend.xml 相同
└── Setup.ps1 ← 與 repo 根目錄的 Setup.ps1 相同(首次登入自動複製到 C:\Scripts\
``` ```
`Setup.ps1` **必須跟 unattend.xml 放在同一個 `/ventoy/script/` 目錄下**`unattend.xml``FirstLogonCommands` Order 4 會掃所有掛載的磁碟,找到第一個有 `\ventoy\script\Setup.ps1` 的就複製到 `C:\Scripts\Setup.ps1`,所以這個路徑寫死,不能搬。
--- ---
## USB 上的 `ventoy.json` ## USB 上的 `ventoy.json`
@@ -80,6 +83,7 @@ USB 根目錄兩個 ISO
# 從 repo 根目錄執行 # 從 repo 根目錄執行
mkdir -p /Volumes/Ventoy/ventoy/script mkdir -p /Volumes/Ventoy/ventoy/script
cp unattend.xml /Volumes/Ventoy/ventoy/script/unattend.xml cp unattend.xml /Volumes/Ventoy/ventoy/script/unattend.xml
cp Setup.ps1 /Volumes/Ventoy/ventoy/script/Setup.ps1
# 寫 ventoy.json見上一節內容或直接手編 # 寫 ventoy.json見上一節內容或直接手編
# 注意image 檔名必須跟 USB 上的 ISO 檔名完全一致 # 注意image 檔名必須跟 USB 上的 ISO 檔名完全一致
@@ -93,17 +97,20 @@ dot_clean /Volumes/Ventoy/ventoy
```bash ```bash
ls /Volumes/Ventoy/ventoy/ /Volumes/Ventoy/ventoy/script/ ls /Volumes/Ventoy/ventoy/ /Volumes/Ventoy/ventoy/script/
diff unattend.xml /Volumes/Ventoy/ventoy/script/unattend.xml # 應無輸出 diff unattend.xml /Volumes/Ventoy/ventoy/script/unattend.xml # 應無輸出
diff Setup.ps1 /Volumes/Ventoy/ventoy/script/Setup.ps1 # 應無輸出
cat /Volumes/Ventoy/ventoy/ventoy.json cat /Volumes/Ventoy/ventoy/ventoy.json
``` ```
**改 `Setup.ps1` 後沒重新 `cp` 是部署最常見的失誤**FirstLogonCommands 從 USB 讀的是 `/ventoy/script/Setup.ps1` 那份repo 裡改了不會自己同步上去。每次調 Setup.ps1 都要記得跑 `cp Setup.ps1 /Volumes/Ventoy/ventoy/script/Setup.ps1``dot_clean`
--- ---
## 換 ISO 時要改什麼 ## 換 ISO 時要改什麼
1. 把新 ISO 丟到 USB 根目錄。 1. 把新 ISO 丟到 USB 根目錄。
2. 編輯 `/Volumes/Ventoy/ventoy/ventoy.json`,把對應條目的 `image` 換成新檔名(或新增一條)。 2. 編輯 `/Volumes/Ventoy/ventoy/ventoy.json`,把對應條目的 `image` 換成新檔名(或新增一條)。
3. **不用**動 `/Volumes/Ventoy/ventoy/script/unattend.xml`,除非要改 Windows 版本特有設定。 3. **不用**動 `/Volumes/Ventoy/ventoy/script/unattend.xml``Setup.ps1`,除非要改 Windows 版本特有設定。
4. 若 repo 裡的 `unattend.xml` 有更新,重跑一次 `cp``dot_clean` 即可。 4. 若 repo 裡的 `unattend.xml``Setup.ps1` 有更新,重跑一次 `cp``dot_clean` 即可。
--- ---
@@ -113,53 +120,96 @@ cat /Volumes/Ventoy/ventoy/ventoy.json
| Order | 動作 | 需要網路? | | Order | 動作 | 需要網路? |
|---|---|---| |---|---|---|
| 1 | `powershell -File C:\Scripts\Setup.ps1` | 看腳本內容 | | 1 | `net accounts /maxpwage:unlimited`(密碼永不過期) | 否 |
| 2 | `net accounts /maxpwage:unlimited`(密碼永不過期 | 否 | | 2 | `Rename-Computer` 把主機名改成 `PC-XXXXX`(重開後生效 | 否 |
| 3 | `Rename-Computer` 把主機名改成 `PC-XXXXX`(重開後生效 | 否 | | 3 | `net user User 1234 /add``/logonpasswordchg:yes`(建立一般使用者 `User`,首次登入強制改密 | 否 |
| 4 | `net user user 1234 /add``/logonpasswordchg:yes`(建立一般使用者 `user`,首次登入強制改密) | 否 | | 4 | 掃磁碟找 `\ventoy\script\Setup.ps1` 並複製到 `C:\Scripts\Setup.ps1`log: `firstlogon-copysetup.log` | 否(從 Ventoy USB 讀) |
| 5 | 等網路→`Add-WindowsCapability OpenSSH.Server``Set-Service sshd Automatic``Start-Service sshd`log: `firstlogon-sshd.log` | **是** | | 5 | `powershell -File C:\Scripts\Setup.ps1`13 段 post-install見下節log: `setup.log` | 大部分 **是** |
| 6 | 等網路→下載 `https://telegram.org/dl/desktop/win64` → 靜默安裝(`/VERYSILENT /SUPPRESSMSGBOXES /NORESTART`log: `firstlogon-telegram.log` | **是** | | 6 | `shutdown /r /t 5` 重開機 | 否 |
| 7 | `shutdown /r /t 5` 重開機 | 否 |
### 新加入的 `user` 帳號 Order 4/5 是改版重點:把原本散在 `FirstLogonCommands` 裡的 OpenSSH / Telegram / RemoveWindowsAI 三條搬進 `Setup.ps1`,並補上 10 個新動作。
### `Setup.ps1` 做了什麼13 段,按執行順序)
每段都用 `Section` 包裝,會把 `=== <name> START/DONE/ERR ===` 寫到 `C:\Windows\Temp\setup.log`,可以照名字逐段排查。
**Phase 1不需網路、本機設定**
| 段名 | 在做什麼 |
|---|---|
| `firewall-ssh-22` | 建 `SSH TCP 22 (Allow Inbound)` 防火牆規則,三個 profile 都啟用 |
| `rdp-enable-3389` | `fDenyTSConnections=0``UserAuthentication=1`、啟用 `Remote Desktop` 防火牆群組 |
| `icmp-echo-allow` | 允許 ICMPv4 Echo Request inbound讓外部 ping 得到) |
| `winrm-enable` | `Enable-PSRemoting``winrm quickconfig``Basic`/`AllowUnencrypted=true``TrustedHosts=*` |
| `onedrive-remove` | 殺 OneDrive process、跑兩處 `OneDriveSetup.exe /uninstall`、清啟動鍵與 CLSID、刪殘留資料夾 |
| `cortana-websearch-off` | `AllowCortana=0``DisableWebSearch=1``ConnectedSearchUseWeb=0``DisableSearchBoxSuggestions=1` |
| `apply-ui-ime-default-profile` | `reg load` Default profile NTUSER.DAT寫入顯示副檔名/隱藏檔、工作列靠左、Dark mode、隱藏搜尋框、停 Spotlight、英數鍵盤排第一+注音排第二、CHT 預設英數模式、停切換 Hotkey |
| `hide-admin-account` | `SpecialAccounts\UserList\Admin=0`(從登入畫面隱藏 Admin帳號仍存在仍可登入 |
**Phase 2需網路、最多等 60 秒):**
| 段名 | 在做什麼 |
|---|---|
| `wait-network` | 最多 30 回合×2 秒等 ICMP 通到 8.8.8.8 |
| `openssh-server-install` | `Add-WindowsCapability OpenSSH.Server``Set-Service sshd Automatic``Start-Service sshd` |
| `kms-activate` | 依序試 `$InternalKmsServers``$PublicKmsServers`kms.digiboy.ir 等),跑 `slmgr /skms``/ato`,遇到第一個 `successful/成功` 就跳出 |
| `winget-install-chrome` | `winget install Google.Chrome --silent --scope machine`Win10 沒裝 App Installer 會 skip |
| `remove-windows-ai` | `irm https://raw.githubusercontent.com/zoicware/RemoveWindowsAI/main/RemoveWindowsAi.ps1``& -nonInteractive -AllOptions -EnableLogging` |
| `win11debloat` | 下載 [Raphire/Win11Debloat](https://github.com/Raphire/Win11Debloat) `master.zip`、解壓、寫出 `unattend-config.json`(清單與 `windows-remote-toolkit/config/win11debloat-config.json` 同步)、跑 `Win11Debloat.ps1 -Silent -Config ... -NoRestartExplorer` |
| `remove-uwp-apps` | 依 `$UwpRemoveList`22 個 AppxPackageBingNews/BingWeather/Xbox*/MSTeams/Photos/...)逐個 `Remove-AppxPackage -AllUsers``Remove-AppxProvisionedPackage` |
| `install-telegram` | 下載 `https://telegram.org/dl/desktop/win64``/VERYSILENT /SUPPRESSMSGBOXES /NORESTART` |
| `cleanup-unattend-files` | 刪掉 `C:\Windows\Panther\unattend.xml``C:\Windows\Panther\Unattend\unattend.xml`(含明碼密碼) |
### `User` 帳號
- **用途**:給使用者日常使用,和部署用的管理員 `Admin` 分開。 - **用途**:給使用者日常使用,和部署用的管理員 `Admin` 分開。
- **權限**一般 `Users` 群組(非 Administrators。需要管理員權限時走 UAC - **建立**`unattend.xml` Order 3 直接 `net user User 1234 /add`(首字大寫一次到位,不在 Setup.ps1 改名)
- **權限**:一般 `Users` 群組(非 Administrators。需要管理員權限時走 UAC`Admin/P@ssw0rd!`)。
- **首次登入強制改密**`/logonpasswordchg:yes` 會在下次登入時跳「密碼必須變更」。可以改成複雜密碼。 - **首次登入強制改密**`/logonpasswordchg:yes` 會在下次登入時跳「密碼必須變更」。可以改成複雜密碼。
- **如果 `net user` 因為 Windows 密碼政策拒絕 `1234`**整條 Order 4 會失敗但不會擋後面,手動於 PowerShell 以 `New-LocalUser`+更寬的密碼重跑。 - **如果 `net user` 因為 Windows 密碼政策拒絕 `1234`**Order 3 會失敗但不會擋後面,手動於 PowerShell 以 `New-LocalUser`+更寬的密碼重跑。
### OpenSSH ServerOrder 5 ### Order 4 的磁碟掃描沒找到 Setup.ps1 怎麼辦
- 走 Windows 內建 Feature on Demand**會從 Windows Update 下載** `OpenSSH.Server~~~~0.0.1.0`。需要外網。 Order 4 會逐個檢查所有掛載中的 FileSystem PSDrive`\ventoy\script\Setup.ps1`。沒找到就寫 `NOT FOUND``C:\Windows\Temp\firstlogon-copysetup.log`Order 5 接著用 `if exist` 判斷,找不到就寫 `SETUP_NOT_FOUND``firstlogon-runsetup.log` 然後跳過。原因通常是:
- 完成後 `sshd` 服務設成 `Automatic`(每次開機自動啟動),防火牆規則由 FoD 自動建立TCP 22 inbound allow
- 驗證:`Get-Service sshd` 應該 `Running``Get-NetFirewallRule -Name OpenSSH-Server-In-TCP` 應該 `Enabled=True`
- 用來遠端登入:`ssh Admin@<機器 IP>``ssh user@<機器 IP>`。初次使用要先在 `%ProgramData%\ssh\administrators_authorized_keys` 加公鑰Windows OpenSSH 對 Administrators 群組的授權金鑰路徑特殊,不走 `~/.ssh/authorized_keys`)。
### TelegramOrder 6的注意事項 | 現象 | 原因 | 對策 |
- **要有網路**OOBE 裡 `SkipMachineOOBE=true` 跳過了網路設定精靈,所以目標機必須有 Ethernet+DHCP或本身已記住 Wi-Fi。這次有 log 可以驗證—`C:\Windows\Temp\firstlogon-telegram.log` 會寫 `NetWait=?s``Downloaded ?B``Exit ?`
- **網路等待上限 60 秒**Order 5 和 Order 6 都各跑一次 `Test-Connection 8.8.8.8` 迴圈,最多等 30 回合×2 秒ICMP 被擋住的環境會等完整 60 秒但 HTTPS 其實可用,也能跑下去。
- **per-user 安裝**Telegram 安裝程式預設裝到 Admin 這個帳號底下,**新建的 `user` 看不到**。要全機安裝就在 `ArgumentList``'/ALLUSERS'`
- **抓的是 latest 版**:每次部署抓當下最新版。要鎖版本改用 `https://updates.tdesktop.com/tsetup/tsetup-x64.X.Y.Z.exe` 直鏈。
- **順序不能搬到 reboot 後**`shutdown /r /t 5` 只是排程重開機、立即回傳,若放在 Telegram 前面,下載安裝會被 5 秒後的關機砍掉。
### 上次部署 Telegram 無聲失敗如何排查
這次每一條都有 log登入 `Admin` 後看
```
notepad C:\Windows\Temp\firstlogon-telegram.log
notepad C:\Windows\Temp\firstlogon-sshd.log
```
常見會看到的值:
| Log 內容 | 代表 | 對策 |
|---|---|---| |---|---|---|
| `NetWait=60s` + `ERR ...` | 60 秒內 ICMP 沒通,後面可能也失敗 | 接網路線、或在 OOBE 後手動連 Wi-Fi 後重跑指令 | | `NOT FOUND` | Ventoy USB 在 OOBE 完成前被拔掉 | 別拔,等 reboot 後再拔 |
| `Downloaded 0B` `ERR``SSL/TLS` | TLS 交握失敗 | 檢查系統時間是否對;老機器可能要裝 Windows Update for TLS 1.2 | | `NOT FOUND` | `Setup.ps1` 沒 cp 到 `/Volumes/Ventoy/ventoy/script/` | 重新 `cp` + `dot_clean` 後再插上重裝 |
| `Exit 0` 但桌面沒 Telegram 捷徑 | 裝成功但 per-user 安裝、不是登入那個帳號 | 以 Admin 登入,捷徑在 `%APPDATA%\Microsoft\Windows\Start Menu\Programs\Telegram Desktop` | | 找到但 Order 5 報 `Cannot run script ... not digitally signed` | 機器 ExecutionPolicy 是 Restricted | 不會發生Order 5 用 `-ExecutionPolicy Bypass` 強蓋 |
| 完全沒 log 檔 | 這條 `SynchronousCommand` 根本沒跑(通常是 `AutoLogon.Enabled=false` 或 XML 沒注入) | `C:\Windows\Panther\UnattendGC\setupact.log` | | 找到了但 Setup.ps1 早早 ERR | `C:\Windows\Temp\setup.log``=== <name> ERR ===` 行 | 對照上面的段名表逐段重跑 |
### 排查 Setup.ps1 失敗
```powershell
notepad C:\Windows\Temp\firstlogon-copysetup.log # Order 4USB 掃描結果
notepad C:\Windows\Temp\firstlogon-runsetup.log # Order 5找不到 Setup.ps1 才會有
notepad C:\Windows\Temp\setup.log # Setup.ps1 主 log
```
`setup.log` 每段都有 `=== <name> START === ... === <name> DONE === ``=== <name> ERR: <msg> ===`。失敗段可以登入 `Admin` 後手動:
```powershell
powershell -ExecutionPolicy Bypass -File C:\Scripts\Setup.ps1
```
整支重跑(每段都是冪等的);或只挑某段:把 `Setup.ps1` 開起來,把那段 `Section '...'` 區塊的內容貼進 PowerShell 視窗即可。
### RemoveWindowsAI 補充
- **在做什麼**:跑 [zoicware/RemoveWindowsAI](https://github.com/zoicware/RemoveWindowsAI) 的 `RemoveWindowsAi.ps1`,一次把 Copilot系統EdgeOffice、Recall排程任務、Input Insights、AI Fabric、Click to Do、Paint 實驗、Voice Access、Gaming Copilot、Notepad Rewrite、Photos AI 等 AI 元件全數關掉或移除AppX、CBS、Registry、Group Policy 都會動)。
- **參數**`-nonInteractive`(不跳互動選單)+`-AllOptions`(全部選項都跑)+`-EnableLogging`(讓腳本自己也寫 log
- **執行時間**:視目標機速度,通常 30 秒3 分鐘。移除 CBS package 那段最慢。
- **抓的是 `main` 分支**:每次部署可能拿到不同版本。要鎖定版本就把 `Setup.ps1` 中對應 URL 的 `/main/` 換成 commit SHA。
- **可逆性**:腳本本身有 `-revertMode``-backupMode`,要還原的話可以登入後手動以 `-revertMode` 重跑;但 `-AllOptions` 跑完有些 CBS package 已被刪,`-revertMode` 不一定救得回,需要搭配 `-RunWinUpdateRepair` 從 Windows Update 重取。
- **風險面**移除「Nonremovable」AppX、刪 CBS package 屬於侵入性操作,可能導致未來 Windows Update 失敗或特定功能殘缺。部署前建議先在 VM 跑過一次。
### Telegram 補充
- **要有網路**OOBE 裡 `SkipMachineOOBE=true` 跳過了網路設定精靈,所以目標機必須有 Ethernet+DHCP或本身已記住 Wi-Fi。
- **網路等待上限 60 秒**`Setup.ps1` 開頭的 `wait-network` 跑一次 `Test-Connection 8.8.8.8` 迴圈,最多等 30 回合×2 秒ICMP 被擋住的環境會等完整 60 秒但 HTTPS 其實可用,也能跑下去。
- **per-user 安裝**Telegram 安裝程式預設裝到 Admin 這個帳號底下,**新建的 `User` 看不到**。要全機安裝就在 `Setup.ps1``install-telegram` 段把 `ArgumentList` 加上 `'/ALLUSERS'`
- **抓的是 latest 版**:每次部署抓當下最新版。要鎖版本改用 `https://updates.tdesktop.com/tsetup/tsetup-x64.X.Y.Z.exe` 直鏈。
--- ---
@@ -168,7 +218,8 @@ notepad C:\Windows\Temp\firstlogon-sshd.log
沿用 repo 預設值,下列情況會失敗,改用前要先處理: 沿用 repo 預設值,下列情況會失敗,改用前要先處理:
- **目標機若是 BIOS/MBR**`<DiskConfiguration>` 目前寫死 UEFI+GPT分割階段會報錯。 - **目標機若是 BIOS/MBR**`<DiskConfiguration>` 目前寫死 UEFI+GPT分割階段會報錯。
- **ARM64 機器**:所有 `<component>``processorArchitecture="amd64"` 要改成 `arm64` - **ARM64 機器**:所有 `<component>``processorArchitecture="amd64"` 要改成 `arm64`;同時 `Setup.ps1` 內的 `OneDriveSetup.exe` SysWOW64 路徑、`winget` Chrome `--scope machine` 等部分行為要重驗
- **`C:\Scripts\Setup.ps1` 未提供**`FirstLogonCommands` 會在首次登入時報「找不到檔案」。解法:在 `FirstLogonCommands` 前段加一條從 USB 複製的指令,或另外烘進 image或乾脆刪掉 `Order=1` 那條 `SynchronousCommand` - **Ventoy USB 在 reboot 前被拔掉**Order 4 會找不到 `Setup.ps1`13 段 post-install 全部 skip。請等到第二次重開機進到登入畫面後才拔
- **密碼是明碼**`<PlainText>true</PlainText>`):僅適合測試用途;正式部署請用 Windows SIM 產 Base64。 - **密碼是明碼**`<PlainText>true</PlainText>`):僅適合測試用途;正式部署請用 Windows SIM 產 Base64。
- **Telegram 安裝需要外網**:目標機沒網路的話,Order 4 會失敗但不影響整個流程走完。 - **Phase 2 全部需要外網**:目標機沒網路的話,`wait-network` 之後 7 段都會 ERRPhase 1 的 8 段防火牆、RDP、ICMP、WinRM、OneDrive 移除、Cortana 關閉、Default profile UI/IME、改名隱藏帳號仍會成功完成。
- **公開 KMS 啟用**`Setup.ps1` 預設用 `kms.digiboy.ir` 等第三方 KMS這對內網/正式環境不適合。把 `$InternalKmsServers` 改成你自己的 KMS 主機就好。

View File

@@ -13,18 +13,25 @@
- [ ] Windows 10 或 11 官方 ISO從 [microsoft.com](https://www.microsoft.com/software-download) 取得) - [ ] Windows 10 或 11 官方 ISO從 [microsoft.com](https://www.microsoft.com/software-download) 取得)
- [ ] 已刷好 Ventoy 的 USB 隨身碟(≥ 16 GB 建議,因為會同時放 ISO 與 script - [ ] 已刷好 Ventoy 的 USB 隨身碟(≥ 16 GB 建議,因為會同時放 ISO 與 script
- [ ] 一台測試機或 VM強烈建議先在 VM 驗證) - [ ] 一台測試機或 VM強烈建議先在 VM 驗證)
- [ ] 本專案的 `unattend.xml``ventoy.json` - [ ] 本專案的 `unattend.xml``Setup.ps1``ventoy.json`
**編輯 `unattend.xml` 的必改項目:** **編輯 `unattend.xml` 的必改項目:**
| 行為 | 原值 | 要改成 | | 行為 | 原值 | 要改成 |
|---|---|---| |---|---|---|
| 產品序號 | `XXXXX-XXXXX-XXXXX-XXXXX-XXXXX` | 你的序號,或 KMS 通用金鑰,或整個 `<ProductKey>` 節點刪掉 | | 產品序號 | Win11/10 Pro GVLK`W269N-WFGWX-...` | 你的序號,或留 GVLK 用 KMS,或整個 `<ProductKey>` 節點刪掉 |
| 管理員密碼 | `P@ssw0rd!`(兩處:`LocalAccount``AutoLogon` | 你的密碼 | | 管理員密碼 | `P@ssw0rd!`(兩處:`LocalAccount``AutoLogon` | 你的密碼 |
| 組織名 | `YourCompany` | 你的組織 | | 組織名 | `YourCompany` | 你的組織 |
| 腳本路徑 | `C:\Scripts\Setup.ps1` | 留著或改路徑;不要執行就刪 `<SynchronousCommand Order=1>` |
| `DiskID` | `$$VT_WINDOWS_DISK_1ST_NONVTOY$$`Ventoy 變數) | **保留不動**。Ventoy 會在執行期替換成「第一顆非 Ventoy USB 的磁碟」 | | `DiskID` | `$$VT_WINDOWS_DISK_1ST_NONVTOY$$`Ventoy 變數) | **保留不動**。Ventoy 會在執行期替換成「第一顆非 Ventoy USB 的磁碟」 |
**編輯 `Setup.ps1` 的常改項目(在檔案頂部):**
| 變數 | 預設 | 要改成 |
|---|---|---|
| `$InternalKmsServers` | `@()` 空陣列 | 你內網 KMS 主機,例:`@('kms.your-corp.com')`;空陣列就 fallback 到公開 KMS |
| `$UwpRemoveList` | 22 個 Microsoft 預載 app | 想多移/少移就改這個陣列 |
| `$Win11DebloatConfigJson` | toolkit 的預設(暗色、工作列靠左、停 Bing/Copilot/Recall… | 不喜歡哪個 tweak 就把該行 `Value` 改成 `false` |
**編輯 `ventoy.json` 的必改項目:** **編輯 `ventoy.json` 的必改項目:**
| 欄位 | 原值 | 要改成 | | 欄位 | 原值 | 要改成 |
@@ -45,7 +52,8 @@
└── ventoy/ └── ventoy/
├── ventoy.json ← 本 repo 的 ventoy.json ├── ventoy.json ← 本 repo 的 ventoy.json
└── script/ └── script/
── unattend.xml ← 本 repo 的 unattend.xml改完的 ── unattend.xml ← 本 repo 的 unattend.xml改完的
└── Setup.ps1 ← 本 repo 的 Setup.ps1首次登入會被自動複製到 C:\Scripts\
``` ```
macOS / Linux 範例: macOS / Linux 範例:
@@ -56,8 +64,11 @@ cp Windows.iso /Volumes/Ventoy/Win11.iso
mkdir -p /Volumes/Ventoy/ventoy/script mkdir -p /Volumes/Ventoy/ventoy/script
cp ventoy.json /Volumes/Ventoy/ventoy/ventoy.json cp ventoy.json /Volumes/Ventoy/ventoy/ventoy.json
cp unattend.xml /Volumes/Ventoy/ventoy/script/unattend.xml cp unattend.xml /Volumes/Ventoy/ventoy/script/unattend.xml
cp Setup.ps1 /Volumes/Ventoy/ventoy/script/Setup.ps1
``` ```
> **改 `Setup.ps1` 後記得重 `cp`**。`unattend.xml` 的 FirstLogonCommands 從 USB 上的 `/ventoy/script/Setup.ps1` 抓檔案,不是從 repo。沒同步的話部署到的 Windows 會跑舊版。
> 不用改名成 `autounattend.xml`,也不用把 XML 塞進 ISO 根目錄。Ventoy 的 Auto Install plugin 會在開機時依 `ventoy.json` 把 XML 注入給 Windows Setup。 > 不用改名成 `autounattend.xml`,也不用把 XML 塞進 ISO 根目錄。Ventoy 的 Auto Install plugin 會在開機時依 `ventoy.json` 把 XML 注入給 Windows Setup。
**多個 ISO / 多套範本**`ventoy.json``auto_install` 是陣列,每個 ISO 一條目;`template` 也可以是陣列,開機時 Ventoy 會顯示選單讓你挑。 **多個 ISO / 多套範本**`ventoy.json``auto_install` 是陣列,每個 ISO 一條目;`template` 也可以是陣列,開機時 Ventoy 會顯示選單讓你挑。
@@ -102,9 +113,9 @@ Ventoy 支援寫進 VHD/VDI可直接掛進 Hyper-V / VirtualBox 當第一顆
| 第一次重開 | 黑屏 → 「請稍候」 | 12 分鐘 | | 第一次重開 | 黑屏 → 「請稍候」 | 12 分鐘 |
| OOBE 略過 | **不該出現任何選單**;直接跳過 | 秒殺 | | OOBE 略過 | **不該出現任何選單**;直接跳過 | 秒殺 |
| AutoLogon | 以 `Admin` 自動登入到桌面顯示名稱是「Administrator」 | — | | AutoLogon | 以 `Admin` 自動登入到桌面顯示名稱是「Administrator」 | — |
| FirstLogonCommands | 閃過一連串 PowerShell / cmd 黑視窗(`Setup.ps1`、建 user 帳號、裝 OpenSSH、裝 Telegram | 13 分鐘(含 FoD 下載 | | FirstLogonCommands | 閃過一連串 PowerShell / cmd 黑視窗(密碼永不過期、改主機名、建 `User` 帳號、從 USB 拷 Setup.ps1、跑 Setup.ps1 的 13 段 post-install | 515 分鐘(含 FoD 下載、Win11Debloat 解壓、winget Chrome、AI 元件移除 |
| 重開機 | 5 秒倒數後重啟 | — | | 重開機 | 5 秒倒數後重啟 | — |
| 第二次開機 | 看到登入畫面,有 `Administrator`= `Admin` 帳號)和 `user` 兩個選項 | — | | 第二次開機 | 看到登入畫面,有 `User` 一個選項(`Admin` 已被 SpecialAccounts 隱藏;輸入帳號名仍可登入) | — |
**正常就是全程不需要碰鍵盤滑鼠。** 如果中途卡在 OOBE 畫面要你選語系/建帳號 → 去看第 5 節除錯。 **正常就是全程不需要碰鍵盤滑鼠。** 如果中途卡在 OOBE 畫面要你選語系/建帳號 → 去看第 5 節除錯。
@@ -126,14 +137,23 @@ Ventoy 支援寫進 VHD/VDI可直接掛進 Hyper-V / VirtualBox 當第一顆
reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v AutoAdminLogon reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v AutoAdminLogon
``` ```
3. **改密碼**:範本的 `Admin/P@ssw0rd!` 和 `user/1234` 都是給自動化用的,上線前換掉。`user` 登入時會被強制改密,`Admin` 要自己 `net user Admin *` 改。 3. **改密碼**:範本的 `Admin/P@ssw0rd!` 和 `User/1234` 都是給自動化用的,上線前換掉。`User` 登入時會被強制改密,`Admin` 要自己 `net user Admin *` 改。
4. **看一下 FirstLogon log**(本輪新增) 4. **看一下 FirstLogon / Setup log**
```powershell ```powershell
notepad C:\Windows\Temp\firstlogon-sshd.log # OpenSSH 安裝結果 notepad C:\Windows\Temp\firstlogon-copysetup.log # Order 4Setup.ps1 是否從 Ventoy USB 複製成功
notepad C:\Windows\Temp\firstlogon-telegram.log # Telegram 下載與安裝結果 notepad C:\Windows\Temp\firstlogon-runsetup.log # Order 5Setup.ps1 找不到才會有此檔
notepad C:\Windows\Temp\setup.log # Setup.ps1 主 log13 段每段都寫 START/DONE/ERR
```
找 `=== <name> ERR: ... ===` 行就是失敗段;`Setup.ps1` 每段都冪等,可以登入 `Admin` 後 `powershell -ExecutionPolicy Bypass -File C:\Scripts\Setup.ps1` 整支重跑,或挑特定段貼進視窗。
5. **驗證遠端管理可用**(從別台機器試):
```bash
ping <機器 IP> # ICMP 應該回應
ssh Admin@<機器 IP> # OpenSSH 22/TCP密碼 P@ssw0rd!(金鑰要先放 administrators_authorized_keys
# RDP用任何 RDP client 連 <機器 IP>:3389
# WinRM5985HTTPAllowUnencrypted=trueTrustedHosts=*
``` ```
`Exit 0` 才是真的成功。前一輪 Telegram 無聲失敗就是因為當時沒有 log、無從判斷。
--- ---
@@ -163,7 +183,8 @@ Shift + F10 在任何安裝畫面會開出命令提示字元,可以 `notepad`
| `ProductKey` 錯誤 | 序號無效 | 改正確序號、用 KMS 通用金鑰、或刪掉整個 `<ProductKey>` 節點 | | `ProductKey` 錯誤 | 序號無效 | 改正確序號、用 KMS 通用金鑰、或刪掉整個 `<ProductKey>` 節點 |
| OOBE 沒略過 | Windows 版本不支援該欄位 | 比對當前版本 SIM 可用欄位 | | OOBE 沒略過 | Windows 版本不支援該欄位 | 比對當前版本 SIM 可用欄位 |
| FirstLogonCommands 沒跑 | `AutoLogon` 沒設或 `Order` 重複 | 確認 `AutoLogon.Enabled=true` 且 `Order` 不重複 | | FirstLogonCommands 沒跑 | `AutoLogon` 沒設或 `Order` 重複 | 確認 `AutoLogon.Enabled=true` 且 `Order` 不重複 |
| `Setup.ps1` 找不到 | 路徑不存在 | 先用其他方式把腳本放到 `C:\Scripts\`,或改用 `FirstLogonCommands` 從 USB 複製 | | `Setup.ps1` 找不到`firstlogon-copysetup.log` 寫 `NOT FOUND` | Ventoy USB 太早被拔掉,或 `Setup.ps1` 沒 cp 到 `/ventoy/script/` | 等到第二次重開機進登入畫面後才拔;或重新 `cp Setup.ps1 /Volumes/Ventoy/ventoy/script/` |
| `Setup.ps1` 跑了但某段 ERR | 看 `C:\Windows\Temp\setup.log` 的 `=== <name> ERR: ... ===` | 修一修對應段,登入 `Admin` 後 `powershell -ExecutionPolicy Bypass -File C:\Scripts\Setup.ps1` 整支重跑 |
### 驗證 XML 語法 ### 驗證 XML 語法

View File

@@ -8,8 +8,11 @@
| 檔案 | 用途 | | 檔案 | 用途 |
|---|---| |---|---|
| `unattend.xml` | 主回應檔,放入 USB/ISO 時需改名為 `autounattend.xml` | | `unattend.xml` | 主回應檔,部署到 Ventoy USB 的 `/ventoy/script/unattend.xml`**不**改名為 `autounattend.xml`Ventoy Auto Install plugin 走 `ventoy.json` 注入) |
| `Setup.ps1` | 首次登入後的 post-install PowerShell部署到 USB 的 `/ventoy/script/Setup.ps1`FirstLogonCommands 會掃磁碟自動複製到 `C:\Scripts\` 後執行 |
| `ventoy.json` | Ventoy Auto Install plugin 設定,把 ISO 對到 `unattend.xml` |
| `QUICKSTART.md` | 操作步驟:如何產生 USB、掛載 ISO、跑 VM 測試 | | `QUICKSTART.md` | 操作步驟:如何產生 USB、掛載 ISO、跑 VM 測試 |
| `DEPLOY.md` | 本機 USB 實機部署紀錄+ FirstLogonCommands / Setup.ps1 詳細排查 |
| `SUMMARY.md` | 本專案的來由與設計脈絡 | | `SUMMARY.md` | 本專案的來由與設計脈絡 |
--- ---
@@ -42,7 +45,7 @@ Windows Setup 在不同階段套用不同 `<settings pass="...">`
### specialize ### specialize
- **Microsoft-Windows-Shell-Setup** - **Microsoft-Windows-Shell-Setup**
- `ComputerName``PC-%RAND:5%`5 位隨機數字) - `ComputerName``*`Setup 自動產生隨機名,`FirstLogonCommands` 再用 `Rename-Computer` 改成 `PC-XXXXX`,重開後生效)。**不可寫 `PC-%RAND:5%`**——那是 MDT/SCCM 變數,直接放這裡會被當字面字串送進 NetBIOS 驗證而失敗
- `TimeZone``Taipei Standard Time` - `TimeZone``Taipei Standard Time`
- `RegisteredOwner` / `RegisteredOrganization` - `RegisteredOwner` / `RegisteredOrganization`
@@ -54,13 +57,18 @@ Windows Setup 在不同階段套用不同 `<settings pass="...">`
- `UserAccounts.LocalAccounts` — 建立 `Admin`Administrators 群組,密碼 `P@ssw0rd!`,明碼) - `UserAccounts.LocalAccounts` — 建立 `Admin`Administrators 群組,密碼 `P@ssw0rd!`,明碼)
- `AutoLogon` — 以 `Admin` 自動登入一次(`LogonCount=1` - `AutoLogon` — 以 `Admin` 自動登入一次(`LogonCount=1`
- `FirstLogonCommands` — 按 `Order` 依序執行: - `FirstLogonCommands` — 按 `Order` 依序執行:
1. `powershell.exe -ExecutionPolicy Bypass -File C:\Scripts\Setup.ps1` 1. `cmd.exe /c net accounts /maxpwage:unlimited`(密碼永不過期)
2. `cmd.exe /c net accounts /maxpwage:unlimited`(密碼永不過期 2. `Rename-Computer` 改主機名成 `PC-XXXXX`(重開後生效
3. `Rename-Computer` 改主機名成 `PC-XXXXX`(重開後生效 3. 建立本機帳號 `User`(密碼 `1234``net user /logonpasswordchg:yes` 強制首次登入改密
4. 建立本機帳號 `user`(密碼 `1234``net user /logonpasswordchg:yes` 強制首次登入改密 4. 掃所有掛載中的 FileSystem PSDrive 找 `\ventoy\script\Setup.ps1`,複製到 `C:\Scripts\Setup.ps1`log 寫到 `C:\Windows\Temp\firstlogon-copysetup.log`
5. 等網路→`Add-WindowsCapability OpenSSH.Server` 安裝並 `Set-Service sshd Automatic` / `Start-Service sshd`log 寫到 `C:\Windows\Temp\firstlogon-sshd.log` 5. `C:\Scripts\Setup.ps1`13 段 post-install 詳見 `Setup.ps1``DEPLOY.md`log 寫到 `C:\Windows\Temp\setup.log`;找不到檔案時改寫 `firstlogon-runsetup.log` 並跳過
6. 等網路→下載 `https://telegram.org/dl/desktop/win64` 並以 `/VERYSILENT /SUPPRESSMSGBOXES /NORESTART` 靜默安裝 Telegramlog 寫到 `C:\Windows\Temp\firstlogon-telegram.log` 6. `shutdown /r /t 5`(重開機
7. `shutdown /r /t 5`(重開機)
### `Setup.ps1` 涵蓋的 13 段(首次登入由 Order 5 執行)
**Phase 1不需網路** SSH 22/TCP 防火牆、RDP+3389、ICMP Echo Inbound、WinRM`TrustedHosts=*`、OneDrive 解除安裝、Cortana/Web Search 關閉、Default profile 套 Dark mode顯示副檔名隱藏檔工作列靠左預設英數鍵盤停 IME 切換熱鍵、從登入畫面隱藏 `Admin`
**Phase 2需網路`wait-network` 後執行):** OpenSSH Server FoD、KMS 啟用(先試 `$InternalKmsServers` 再 fallback 公開 KMS、winget 裝 Chrome、`zoicware/RemoveWindowsAI -AllOptions``Raphire/Win11Debloat -Silent -Config`、22 個 UWP AppxPackage 移除(與 `windows-remote-toolkit/config/remove-apps-config.json` 同步、Telegram 靜默安裝、刪 `C:\Windows\Panther\unattend.xml` 殘留。
--- ---
@@ -82,10 +90,10 @@ Windows Setup 在不同階段套用不同 `<settings pass="...">`
| 方式 | 位置 | 檔名 | | 方式 | 位置 | 檔名 |
|---|---|---| |---|---|---|
| **Ventoy Auto Install plugin本 repo 預設)** | Ventoy USB 的 `/ventoy/script/`,並在 `/ventoy/ventoy.json` 指定 | 任意(本 repo 用 `unattend.xml` | | **Ventoy Auto Install plugin本 repo 預設)** | Ventoy USB 的 `/ventoy/script/unattend.xml``/ventoy/script/Setup.ps1`,並在 `/ventoy/ventoy.json` 指定 | 任意(本 repo 用 `unattend.xml` |
| 傳統 USB 安裝碟 | USB 根目錄 | `autounattend.xml` | | 傳統 USB 安裝碟 | USB 根目錄 | `autounattend.xml`注意FirstLogonCommands Order 4 的磁碟掃描還是會找 `\ventoy\script\Setup.ps1`,要嘛把 `Setup.ps1` 放對位置,要嘛改 Order 4 的搜尋路徑) |
| ISO `sources\` 目錄 | ISO 根 | `autounattend.xml` | | ISO `sources\` 目錄 | ISO 根 | `autounattend.xml`(同上) |
| DISM 離線注入已掛載映像 | — | 任意名稱,用 `/Apply-Unattend:` 指定 | | DISM 離線注入已掛載映像 | — | 任意名稱,用 `/Apply-Unattend:` 指定(這條路徑下 `Setup.ps1` 通常會直接烘進 image 而不再走 USB 掃描) |
--- ---
@@ -99,11 +107,9 @@ Windows Setup 在不同階段套用不同 `<settings pass="...">`
## 安全性 ## 安全性
- 本範本密碼為明碼,僅適合內網測試;生產環境請改 Base64 或交由 MDT/SCCM 管理。 - 本範本密碼為明碼,僅適合內網測試;生產環境請改 Base64 或交由 MDT/SCCM 管理。
- 部署結束後應刪除殘留的 unattend 檔,例如在 `Setup.ps1` 尾端: - `Setup.ps1``cleanup-unattend-files` 段已經會在收尾時刪掉 `C:\Windows\Panther\unattend.xml``Panther\Unattend\unattend.xml`(含明碼密碼)。
```powershell - WinRM `AllowUnencrypted=true` + `TrustedHosts=*` 是測試環境設定;正式環境要改 HTTPS5986+ 限制 TrustedHosts。
Remove-Item C:\Windows\Panther\unattend.xml -Force -ErrorAction SilentlyContinue - KMS 預設 fallback 到第三方公開伺服器(`kms.digiboy.ir` 等);正式環境改 `Setup.ps1``$InternalKmsServers`
Remove-Item C:\Windows\Panther\Unattend\unattend.xml -Force -ErrorAction SilentlyContinue
```
--- ---

49
Run-Setup.bat Normal file
View File

@@ -0,0 +1,49 @@
@echo off
REM Run-Setup.bat — 從 Ventoy USB 雙擊就能手動觸發 Setup.ps1
REM 用途:當 unattend.xml 的 FirstLogonCommands Order 5 沒成功跑起 Setup.ps1 時,
REM 把這份 .bat 從 USB 雙擊執行,會自動 UAC 提權、覆蓋 C:\Scripts\Setup.ps1、
REM 跑 Setup.ps1所有輸出含 parse error都會收到 setup-stdout.log。
setlocal
REM 先用 fltmc 偵測是否已是 Administrator不是就用 PowerShell 自我提權重啟
fltmc >nul 2>&1
if %errorLevel% neq 0 (
echo Requesting administrator privileges...
powershell -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
exit /b
)
set "USB_DIR=%~dp0"
set "DST=C:\Scripts\Setup.ps1"
set "OUT=C:\Windows\Temp\setup-stdout.log"
echo === Run-Setup.bat (elevated) ===
echo USB source : %USB_DIR%Setup.ps1
echo Copy to : %DST%
echo Stdout log : %OUT%
echo Setup log : C:\Windows\Temp\setup.log
echo.
mkdir C:\Scripts >nul 2>&1
copy /Y "%USB_DIR%Setup.ps1" "%DST%"
if errorlevel 1 (
echo [ERROR] Copy failed. Is the USB still plugged in?
pause
exit /b 1
)
echo Running Setup.ps1 (output streams to this window AND %OUT%) ...
echo -----------------------------------------------------------------
powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "& '%DST%' 2>&1 | Tee-Object -FilePath '%OUT%'"
set RC=%errorLevel%
echo -----------------------------------------------------------------
echo.
echo === Done (exit code %RC%) ===
echo View logs:
echo notepad C:\Windows\Temp\setup.log
echo notepad %OUT%
echo.
pause
endlocal

View File

@@ -44,20 +44,44 @@ XML 能做的事有上限。超過這條線(裝軟體、設 Registry、拉 Git
`FirstLogonCommands` 就是交棒點: `FirstLogonCommands` 就是交棒點:
``` ```
Order 1 → Setup.ps1 所有「後續設定」的預留入口 Order 1 → net accounts 一行搞定的雜事直接 cmd密碼永不過期
Order 2 → net accounts 一行搞定的雜事直接 cmd Order 2 → Rename-Computer 主機名改 PC-XXXXX重開後生效
Order 3 → Rename-Computer (主機名改 PC-XXXXX重開後生效 Order 3 → net user User 1234 /add (建立一般使用者 User強制首次登入改密
Order 4 → net user user 1234 /add (建立一般使用者,強制首次登入改密 Order 4 → Copy Setup.ps1 from Ventoy USB (掃磁碟找 \ventoy\script\Setup.ps1 → C:\Scripts\
Order 5 → Add-WindowsCapability OpenSSH (等網路→安裝並啟動 sshd Order 5 → powershell -File Setup.ps1 13 段 post-install看下表
Order 6 → Telegram 靜默安裝 (等網路→下載 tsetup.exe + /VERYSILENT Order 6 → shutdown /r (重開機收尾,讓 rename 生效
Order 7 → shutdown /r (重開機收尾,讓 rename 生效)
``` ```
這讓 `unattend.xml` 保持「只負責 Windows 本身的安裝」,其他事情外包給腳本。職責分離之後,改軟體清單不需要動 XML改部署流程也不需要動 Setup 邏輯。 `Setup.ps1` 內部分兩個 phase用空的 `wait-network` 段切開):
> Order 5、6OpenSSH、Telegram是「交棒到腳本」這條原則的例外**單一套件、需求穩定**的情況下,直接在 `FirstLogonCommands` 裡下載安裝比起硬塞進 `Setup.ps1` 單純。一旦要裝的東西變成清單3+ 個套件、要版本鎖定、要錯誤處理),就該整批移進 `Setup.ps1` 或改用 winget/choco。 ```
[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 個 AppxPackageBingNews、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 必須留到第一次登入完成才能拔。
> >
> Order 5、6 共用「等 ICMP 通到 8.8.8.8、最多 60 秒」的網路等待迴圈,並且各自把結果寫到 `C:\Windows\Temp\firstlogon-*.log`,避免前一輪 Telegram 無聲失敗的情況再發生 > `remove-windows-ai` 跑的是 `main` 分支的腳本,**每次部署抓的版本可能不同**。要確定性、可稽核的部署,把 `Setup.ps1` 中對應 URL 的 `/main/` 換成 commit SHA
--- ---
@@ -85,7 +109,8 @@ Windows Setup 有七個 pass本檔只用三個。不是偷懶是**三個 p
| **本機帳號 + AutoLogon** | 純離線可跑 | 改成 Domain Join + 網域管理員登入 | | **本機帳號 + AutoLogon** | 純離線可跑 | 改成 Domain Join + 網域管理員登入 |
| **ProtectYourPC=3** | 略過 OOBE 的隱私頁,預設行為 | 依公司政策調整,或用 GPO 蓋掉 | | **ProtectYourPC=3** | 略過 OOBE 的隱私頁,預設行為 | 依公司政策調整,或用 GPO 蓋掉 |
| **單一 Disk 0 + 整碟清空** | 對測試 VM 最安全的假設 | 多硬碟環境要加 `DiskID` 判斷邏輯 | | **單一 Disk 0 + 整碟清空** | 對測試 VM 最安全的假設 | 多硬碟環境要加 `DiskID` 判斷邏輯 |
| **`Setup.ps1` 路徑硬寫 C:\Scripts\** | 簡單 | 改從 USB/網路拉,或用 MDT 的 `Applications` 機制 | | **`Setup.ps1` 用磁碟掃描從 Ventoy USB 拷貝** | 不需要架網路服務、跨網段也能跑、USB 拔掉也能在現場補救 | 內部 IT 場合改用 SCCM/Intune 派送,或乾脆 base64 內嵌進 XML 拿到完全自包含 |
| **`$PublicKmsServers` 用第三方 KMS** | 沒授權主機也能跑通 | 把 `$InternalKmsServers` 改成自家 KMS 主機,公開那串不再被 fallback |
這些不是 bug是**當前脈絡下的合理起點**。要產品化就對照這張表一個一個升級。 這些不是 bug是**當前脈絡下的合理起點**。要產品化就對照這張表一個一個升級。

358
Setup.ps1 Normal file
View File

@@ -0,0 +1,358 @@
# Setup.ps1 — Windows 自動部署完成後的 post-install 腳本
#
# 入口unattend.xml 的 FirstLogonCommands 會把這份從 Ventoy USB/ventoy/script/Setup.ps1
# 複製到 C:\Scripts\Setup.ps1然後以 Admin 帳號執行。
#
# Log: C:\Windows\Temp\setup.log每段都寫 START/DONE/ERR
#
# 順序:開頭先暫停 Windows Update避免跟本腳本搶頻寬CPU
# 再做不需網路的本機設定(防火牆 / 註冊機碼 / Default profile
# 接著等網路跑下載安裝OpenSSH FoD / KMS / winget / RemoveAI / Win11Debloat / Telegram
# 結尾解除 WU 暫停並主動觸發掃描,這樣更新會排在所有 post-install 之後才開始下載。
$ErrorActionPreference = 'Continue'
$ProgressPreference = 'SilentlyContinue'
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$LogFile = 'C:\Windows\Temp\setup.log'
# 內網 KMS 主機請改下面這列;保持空陣列就會用公開 KMS 清單輪流試
$InternalKmsServers = @()
$PublicKmsServers = @('kms.digiboy.ir', 'kms8.msguides.com', 'kms.03k.org', 'kms.chinancce.com')
# 要批次移除的 UWP與 windows-remote-toolkit/config/remove-apps-config.json 同步)
$UwpRemoveList = @(
'Microsoft.BingNews','Microsoft.BingWeather','Microsoft.GetHelp',
'Microsoft.MicrosoftSolitaireCollection','Microsoft.Todos','Microsoft.Windows.DevHome',
'Microsoft.WindowsAlarms','Microsoft.WindowsCamera','Microsoft.WindowsFeedbackHub',
'Microsoft.WindowsSoundRecorder','Microsoft.Xbox.TCUI','Microsoft.XboxGameCallableUI',
'Microsoft.XboxIdentityProvider','Microsoft.XboxSpeechToTextOverlay','Microsoft.YourPhone',
'Microsoft.ZuneMusic','MicrosoftCorporationII.QuickAssist',
'MicrosoftWindows.Client.WebExperience','MicrosoftWindows.CrossDevice','MSTeams',
'Microsoft.PowerAutomateDesktop','Microsoft.Windows.Photos'
)
# Win11Debloat 的設定(與 toolkit/config/win11debloat-config.json 同步)
# 改用 hashtable + ConvertTo-Json避免 here-string 在某些 PowerShell 環境 parse 失敗
$Win11DebloatTweakNames = @(
'RemoveApps','RemoveGamingApps','RemoveCommApps','RemoveW11Outlook',
'DisableTelemetry','DisableSearchHistory','DisableBing','DisableStoreSearchSuggestions',
'DisableSearchHighlights','DisableLockscreenTips','DisableSuggestions','DisableDesktopSpotlight',
'DisableFindMyDevice','DisableEdgeAds','DisableBraveBloat','DisableSettings365Ads',
'DisableSettingsHome','DisableCopilot','DisableRecall','DisableClickToDo',
'DisableAISvcAutoStart','DisablePaintAI','DisableNotepadAI','DisableEdgeAI',
'PreventUpdateAutoReboot','DisableModernStandbyNetworking','DisableDVR','DisableGameBarIntegration',
'ShowHiddenFolders','ShowKnownFileExt','EnableDarkMode','TaskbarAlignLeft',
'HideSearchTb','HideTaskview','HideChat','DisableWidgets','DisableStartRecommended',
'Hide3dObjects','ExplorerToThisPC','HideGallery','DisableDragTray'
)
$Win11DebloatConfig = [ordered]@{
Version = '1.0'
Tweaks = @($Win11DebloatTweakNames | ForEach-Object { [ordered]@{ Name = $_; Value = $true } })
Deployment = @(
[ordered]@{ Name = 'CreateRestorePoint'; Value = $true },
[ordered]@{ Name = 'RestartExplorer'; Value = $false },
[ordered]@{ Name = 'AppRemovalScopeIndex'; Value = 0 }
)
}
$Win11DebloatConfigJson = $Win11DebloatConfig | ConvertTo-Json -Depth 5
# ---- helpers ----
function Log {
param([string]$msg)
$line = '[{0}] {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $msg
Add-Content -Path $LogFile -Value $line -Encoding UTF8
Write-Host $line
}
function Section {
param([string]$name, [scriptblock]$block)
Log "=== $name START ==="
try {
& $block
Log "=== $name DONE ==="
} catch {
Log "=== $name ERR: $($_.Exception.Message) ==="
}
}
function Wait-Network {
param([int]$timeoutSec = 60)
$i = 0
$max = [int]($timeoutSec / 2)
while ($i -lt $max -and -not (Test-Connection 8.8.8.8 -Count 1 -Quiet -ErrorAction SilentlyContinue)) {
Start-Sleep 2
$i++
}
Log "Network wait: $($i*2)s"
}
function Set-RegValue {
param([string]$Path, [string]$Name, $Value, [string]$Type = 'DWord')
if (-not (Test-Path $Path)) { New-Item -Path $Path -Force | Out-Null }
New-ItemProperty -Path $Path -Name $Name -Value $Value -PropertyType $Type -Force | Out-Null
}
# 開頭橫線log 開始)
"`n========== Setup.ps1 starting at $(Get-Date -Format o) ==========" | Add-Content $LogFile -Encoding UTF8
# ---- Phase 1: 本機設定(不需網路)----
Section 'pause-windows-update' {
# 設定官方暫停旗標:暫停 24 小時Setup.ps1 結尾會主動清掉並觸發掃描)
$now = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
$expiry = (Get-Date).ToUniversalTime().AddHours(24).ToString("yyyy-MM-ddTHH:mm:ssZ")
$ux = 'HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings'
Set-RegValue $ux 'PauseUpdatesStartTime' $now 'String'
Set-RegValue $ux 'PauseUpdatesExpiryTime' $expiry 'String'
Set-RegValue $ux 'PauseFeatureUpdatesStartTime' $now 'String'
Set-RegValue $ux 'PauseFeatureUpdatesEndTime' $expiry 'String'
Set-RegValue $ux 'PauseQualityUpdatesStartTime' $now 'String'
Set-RegValue $ux 'PauseQualityUpdatesEndTime' $expiry 'String'
Log "WU paused until $expiry (UTC)"
# 停掉已啟動的 WU 相關服務,避免在 Setup.ps1 期間搶頻寬CPU
foreach ($svc in @('UsoSvc','wuauserv','BITS')) {
Stop-Service -Name $svc -Force -ErrorAction SilentlyContinue
}
}
Section 'firewall-ssh-22' {
Remove-NetFirewallRule -DisplayName 'SSH TCP 22 (Allow Inbound)' -ErrorAction SilentlyContinue
New-NetFirewallRule -DisplayName 'SSH TCP 22 (Allow Inbound)' `
-Direction Inbound -Protocol TCP -LocalPort 22 -Action Allow -Profile Any -Enabled True | Out-Null
Set-NetFirewallProfile -Profile Domain,Private,Public -Enabled True
}
Section 'rdp-enable-3389' {
Set-RegValue 'HKLM:\System\CurrentControlSet\Control\Terminal Server' 'fDenyTSConnections' 0
Set-RegValue 'HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' 'UserAuthentication' 1
Enable-NetFirewallRule -DisplayGroup 'Remote Desktop' -ErrorAction SilentlyContinue
}
Section 'icmp-echo-allow' {
Remove-NetFirewallRule -DisplayName 'ICMPv4 Echo Request (Allow Inbound)' -ErrorAction SilentlyContinue
New-NetFirewallRule -DisplayName 'ICMPv4 Echo Request (Allow Inbound)' `
-Direction Inbound -Protocol ICMPv4 -IcmpType 8 -Action Allow -Profile Any -Enabled True | Out-Null
Get-NetFirewallRule -ErrorAction SilentlyContinue |
Where-Object { $_.DisplayName -match 'Echo Request' } |
Enable-NetFirewallRule -ErrorAction SilentlyContinue
}
Section 'winrm-enable' {
Enable-PSRemoting -Force -SkipNetworkProfileCheck | Out-Null
Set-Service -Name WinRM -StartupType Automatic
Start-Service -Name WinRM -ErrorAction SilentlyContinue
cmd.exe /c 'winrm quickconfig -quiet -force' | Out-Null
cmd.exe /c 'winrm set winrm/config/service/auth @{Basic="true"}' | Out-Null
cmd.exe /c 'winrm set winrm/config/service @{AllowUnencrypted="true"}' | Out-Null
cmd.exe /c 'winrm set winrm/config/client @{TrustedHosts="*"}' | Out-Null
Enable-NetFirewallRule -DisplayGroup 'Windows Remote Management' -ErrorAction SilentlyContinue
}
Section 'onedrive-remove' {
Get-Process OneDrive -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
foreach ($p in @("$env:SystemRoot\System32\OneDriveSetup.exe", "$env:SystemRoot\SysWOW64\OneDriveSetup.exe")) {
if (Test-Path $p) { Start-Process $p -ArgumentList '/uninstall' -Wait -ErrorAction SilentlyContinue }
}
Remove-ItemProperty -Path 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Run' -Name 'OneDrive' -ErrorAction SilentlyContinue
Remove-Item 'HKLM:\SOFTWARE\Classes\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}' -Recurse -ErrorAction SilentlyContinue
Remove-Item 'HKLM:\SOFTWARE\Wow6432Node\Classes\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}' -Recurse -ErrorAction SilentlyContinue
Remove-Item -Path 'C:\OneDriveTemp', "$env:USERPROFILE\OneDrive", "$env:LOCALAPPDATA\Microsoft\OneDrive" -Recurse -Force -ErrorAction SilentlyContinue
}
Section 'cortana-websearch-off' {
$k1 = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\Windows Search'
Set-RegValue $k1 'AllowCortana' 0
Set-RegValue $k1 'DisableWebSearch' 1
Set-RegValue $k1 'ConnectedSearchUseWeb' 0
Set-RegValue $k1 'AllowSearchToUseLocation' 0
$k2 = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\Explorer'
Set-RegValue $k2 'DisableSearchBoxSuggestions' 1
}
# 把 UI / IME 預設套到 Default profile新使用者首次登入會繼承
Section 'apply-ui-ime-default-profile' {
$hivePath = 'C:\Users\Default\NTUSER.DAT'
$mountKey = 'HKU\SetupDefault'
reg.exe load $mountKey $hivePath | Out-Null
try {
$base = "Registry::$mountKey"
$adv = "$base\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced"
Set-RegValue $adv 'HideFileExt' 0 # 顯示副檔名
Set-RegValue $adv 'Hidden' 1 # 顯示隱藏檔
Set-RegValue $adv 'TaskbarAl' 0 # 工作列靠左
Set-RegValue $adv 'ShowTaskViewButton' 0 # 隱藏 Task View 按鈕
Set-RegValue $adv 'LaunchTo' 1 # 開啟 This PC
Set-RegValue $adv 'Start_TrackProgs' 0 # 不追蹤最近開啟的程式
$theme = "$base\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"
Set-RegValue $theme 'AppsUseLightTheme' 0
Set-RegValue $theme 'SystemUseLightTheme' 0
# 工作列搜尋框0=隱藏, 1=圖示, 2=框, 3=圖示+標籤
$search = "$base\Software\Microsoft\Windows\CurrentVersion\Search"
Set-RegValue $search 'SearchboxTaskbarMode' 0
$cloud = "$base\Software\Policies\Microsoft\Windows\CloudContent"
Set-RegValue $cloud 'DisableSpotlightCollectionOnDesktop' 1
Set-RegValue $cloud 'DisableWindowsSpotlightFeatures' 1
# 鍵盤排序:英文鍵盤排第一(預設英數),注音排第二
$kbPre = "$base\Keyboard Layout\Preload"
Set-RegValue $kbPre '1' '00000409' 'String'
Set-RegValue $kbPre '2' '00000404' 'String'
# CHT 注音:預設英數模式、關掉雲端候選與預測
$cht = "$base\Software\Microsoft\InputMethod\Settings\CHT"
Set-RegValue $cht 'Enable Default Input Mode' 0
Set-RegValue $cht 'Enable Cand Predict' 0
Set-RegValue $cht 'Enable Cloud Cand' 0
Set-RegValue $cht 'Enable Cloud Input' 0
# 把系統 Hotkey 切換鍵停掉3 = 不指定鍵)
$tg = "$base\Keyboard Layout\Toggle"
Set-RegValue $tg 'Hotkey' '3' 'String'
Set-RegValue $tg 'Language Hotkey' '3' 'String'
Set-RegValue $tg 'Layout Hotkey' '3' 'String'
} finally {
[GC]::Collect()
Start-Sleep 1
reg.exe unload $mountKey | Out-Null
}
}
Section 'hide-admin-account' {
# 帳號 User 在 unattend.xml Order 3 已直接以大寫建立,這裡只負責把 Admin 從登入畫面藏掉
$userListKey = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList'
Set-RegValue $userListKey 'Admin' 0 # 0 = 從登入畫面隱藏(帳號仍存在仍可用)
}
# ---- Phase 2: 需要網路 ----
Section 'wait-network' { Wait-Network -timeoutSec 60 }
Section 'openssh-server-install' {
Add-WindowsCapability -Online -Name 'OpenSSH.Server~~~~0.0.1.0' -ErrorAction Stop | Out-Null
Set-Service sshd -StartupType Automatic
Start-Service sshd
}
Section 'kms-activate' {
$servers = @()
if ($InternalKmsServers.Count -gt 0) { $servers += $InternalKmsServers }
$servers += $PublicKmsServers
$activated = $false
foreach ($kms in $servers) {
Log "Try KMS: $kms"
cscript.exe //nologo C:\Windows\System32\slmgr.vbs /skms $kms | ForEach-Object { Log " skms: $_" }
$out = cscript.exe //nologo C:\Windows\System32\slmgr.vbs /ato 2>&1
foreach ($line in $out) { Log " ato : $line" }
if ($out -match 'successful|成功') {
Log "Activated via $kms"
$activated = $true
break
}
}
if (-not $activated) { Log 'All KMS servers failed; remaining in grace period' }
}
Section 'winget-install-chrome' {
$winget = (Get-Command winget -ErrorAction SilentlyContinue).Source
if (-not $winget) { Log 'winget not found (Win10 沒裝 App Installer)'; return }
& $winget install --id Google.Chrome -e --silent `
--accept-source-agreements --accept-package-agreements --scope machine 2>&1 |
ForEach-Object { Log " winget: $_" }
}
Section 'remove-windows-ai' {
$sb = [scriptblock]::Create((Invoke-RestMethod 'https://raw.githubusercontent.com/zoicware/RemoveWindowsAI/main/RemoveWindowsAi.ps1'))
& $sb -nonInteractive -AllOptions -EnableLogging | Out-Null
}
Section 'win11debloat' {
$work = Join-Path $env:TEMP 'win11debloat'
$zip = "$work.zip"
if (Test-Path $work) { Remove-Item $work -Recurse -Force }
Invoke-WebRequest -UseBasicParsing -Uri 'https://github.com/Raphire/Win11Debloat/archive/refs/heads/master.zip' -OutFile $zip
Expand-Archive -Path $zip -DestinationPath $work -Force
$dir = Get-ChildItem $work -Directory | Select-Object -First 1
if (-not $dir) { throw 'Win11Debloat zip extracted but no inner dir' }
$config = Join-Path $dir.FullName 'unattend-config.json'
Set-Content -Path $config -Value $Win11DebloatConfigJson -Encoding UTF8
& "$($dir.FullName)\Win11Debloat.ps1" -Silent -Config $config -NoRestartExplorer
}
Section 'remove-uwp-apps' {
foreach ($name in $UwpRemoveList) {
Get-AppxPackage -AllUsers -Name $name -ErrorAction SilentlyContinue | ForEach-Object {
try {
Remove-AppxPackage -AllUsers -Package $_.PackageFullName -ErrorAction Stop
Log "Removed AppxPackage $name"
} catch {
Log "Skip AppxPackage $name : $($_.Exception.Message)"
}
}
Get-AppxProvisionedPackage -Online -ErrorAction SilentlyContinue |
Where-Object { $_.DisplayName -eq $name } |
ForEach-Object {
try {
Remove-AppxProvisionedPackage -Online -PackageName $_.PackageName -ErrorAction Stop | Out-Null
Log "Removed Provisioned $name"
} catch {
Log "Skip Provisioned $name : $($_.Exception.Message)"
}
}
}
}
Section 'install-telegram' {
$p = Join-Path $env:TEMP 'tsetup.exe'
Invoke-WebRequest -UseBasicParsing -Uri 'https://telegram.org/dl/desktop/win64' -OutFile $p
$proc = Start-Process -FilePath $p -ArgumentList '/VERYSILENT','/SUPPRESSMSGBOXES','/NORESTART' -Wait -PassThru
Log "Telegram installer exit code: $($proc.ExitCode)"
}
Section 'resume-and-trigger-windows-update' {
# 清掉暫停旗標pause-windows-update 設的那幾個值)
$ux = 'HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings'
foreach ($n in @('PauseUpdatesStartTime','PauseUpdatesExpiryTime',
'PauseFeatureUpdatesStartTime','PauseFeatureUpdatesEndTime',
'PauseQualityUpdatesStartTime','PauseQualityUpdatesEndTime')) {
Remove-ItemProperty -Path $ux -Name $n -ErrorAction SilentlyContinue
}
# 確保服務可啟動
foreach ($svc in @('BITS','wuauserv','UsoSvc')) {
Start-Service -Name $svc -ErrorAction SilentlyContinue
}
# 通知 Update Orchestrator 重新讀設定,然後立即掃描/下載/安裝
$uso = "$env:SystemRoot\System32\UsoClient.exe"
if (Test-Path $uso) {
& $uso RefreshSettings | Out-Null
& $uso StartScan | Out-Null
& $uso StartDownload | Out-Null
& $uso StartInstall | Out-Null
Log 'WU resumed; UsoClient scan/download/install triggered'
} else {
# 後備:呼叫 COM 觸發掃描
try {
(New-Object -ComObject Microsoft.Update.AutoUpdate).DetectNow()
Log 'WU resumed; AutoUpdate.DetectNow triggered (COM fallback)'
} catch {
Log "WU resumed but trigger failed: $($_.Exception.Message)"
}
}
}
# 結尾:刪掉殘留的明碼回應檔
Section 'cleanup-unattend-files' {
Remove-Item 'C:\Windows\Panther\unattend.xml' -Force -ErrorAction SilentlyContinue
Remove-Item 'C:\Windows\Panther\Unattend\unattend.xml' -Force -ErrorAction SilentlyContinue
}
"========== Setup.ps1 finished at $(Get-Date -Format o) ==========" | Add-Content $LogFile -Encoding UTF8

View File

@@ -0,0 +1,20 @@
@echo off
REM Double-click to run %~n0.ps1 with UAC elevation.
REM All output goes to this window AND C:\Windows\Temp\setup.log.
fltmc >nul 2>&1
if %errorLevel% neq 0 (
powershell -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
exit /b
)
echo ============================================================
echo Running: %~n0
echo Log : C:\Windows\Temp\setup.log
echo ============================================================
echo.
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dpn0.ps1"
set RC=%errorLevel%
echo.
echo ============================================================
echo Done (exit code %RC%)
echo ============================================================
pause

View File

@@ -0,0 +1,18 @@
. "$PSScriptRoot\_common.ps1"
Section 'network-set-private' {
$profiles = Get-NetConnectionProfile -ErrorAction SilentlyContinue
if (-not $profiles) {
Log 'No active NetConnectionProfile (offline?) - nothing to set'
return
}
foreach ($p in $profiles) {
Log ("Profile {0} (idx {1}) was {2}" -f $p.Name, $p.InterfaceIndex, $p.NetworkCategory)
if ($p.NetworkCategory -ne 'Private') {
Set-NetConnectionProfile -InterfaceIndex $p.InterfaceIndex -NetworkCategory Private
Log (" -> set to Private")
} else {
Log ' already Private'
}
}
}

View File

@@ -0,0 +1,20 @@
@echo off
REM Double-click to run %~n0.ps1 with UAC elevation.
REM All output goes to this window AND C:\Windows\Temp\setup.log.
fltmc >nul 2>&1
if %errorLevel% neq 0 (
powershell -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
exit /b
)
echo ============================================================
echo Running: %~n0
echo Log : C:\Windows\Temp\setup.log
echo ============================================================
echo.
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dpn0.ps1"
set RC=%errorLevel%
echo.
echo ============================================================
echo Done (exit code %RC%)
echo ============================================================
pause

View File

@@ -0,0 +1,8 @@
. "$PSScriptRoot\_common.ps1"
Section 'firewall-ssh-22' {
Remove-NetFirewallRule -DisplayName 'SSH TCP 22 (Allow Inbound)' -ErrorAction SilentlyContinue
New-NetFirewallRule -DisplayName 'SSH TCP 22 (Allow Inbound)' `
-Direction Inbound -Protocol TCP -LocalPort 22 -Action Allow -Profile Any -Enabled True | Out-Null
Set-NetFirewallProfile -Profile Domain,Private,Public -Enabled True
}

View File

@@ -0,0 +1,20 @@
@echo off
REM Double-click to run %~n0.ps1 with UAC elevation.
REM All output goes to this window AND C:\Windows\Temp\setup.log.
fltmc >nul 2>&1
if %errorLevel% neq 0 (
powershell -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
exit /b
)
echo ============================================================
echo Running: %~n0
echo Log : C:\Windows\Temp\setup.log
echo ============================================================
echo.
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dpn0.ps1"
set RC=%errorLevel%
echo.
echo ============================================================
echo Done (exit code %RC%)
echo ============================================================
pause

View File

@@ -0,0 +1,16 @@
. "$PSScriptRoot\_common.ps1"
Section 'rdp-enable-3389' {
Set-RegValue 'HKLM:\System\CurrentControlSet\Control\Terminal Server' 'fDenyTSConnections' 0
Set-RegValue 'HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' 'UserAuthentication' 1
# Windows 內建的 'Remote Desktop' display group 在某些 SKU/精簡版中不存在,
# 改自建顯式規則確保 3389 通Profile Any 涵蓋 Domain/Private/Public
Remove-NetFirewallRule -DisplayName 'RDP TCP 3389 (Allow Inbound)' -ErrorAction SilentlyContinue
New-NetFirewallRule -DisplayName 'RDP TCP 3389 (Allow Inbound)' `
-Direction Inbound -Protocol TCP -LocalPort 3389 -Action Allow -Profile Any -Enabled True | Out-Null
Enable-NetFirewallRule -DisplayGroup 'Remote Desktop' -ErrorAction SilentlyContinue
# 改完 fDenyTSConnections 需要重啟 TermService 才會立刻 listen
Restart-Service TermService -Force -ErrorAction SilentlyContinue
}

View File

@@ -0,0 +1,20 @@
@echo off
REM Double-click to run %~n0.ps1 with UAC elevation.
REM All output goes to this window AND C:\Windows\Temp\setup.log.
fltmc >nul 2>&1
if %errorLevel% neq 0 (
powershell -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
exit /b
)
echo ============================================================
echo Running: %~n0
echo Log : C:\Windows\Temp\setup.log
echo ============================================================
echo.
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dpn0.ps1"
set RC=%errorLevel%
echo.
echo ============================================================
echo Done (exit code %RC%)
echo ============================================================
pause

View File

@@ -0,0 +1,10 @@
. "$PSScriptRoot\_common.ps1"
Section 'icmp-echo-allow' {
Remove-NetFirewallRule -DisplayName 'ICMPv4 Echo Request (Allow Inbound)' -ErrorAction SilentlyContinue
New-NetFirewallRule -DisplayName 'ICMPv4 Echo Request (Allow Inbound)' `
-Direction Inbound -Protocol ICMPv4 -IcmpType 8 -Action Allow -Profile Any -Enabled True | Out-Null
Get-NetFirewallRule -ErrorAction SilentlyContinue |
Where-Object { $_.DisplayName -match 'Echo Request' } |
Enable-NetFirewallRule -ErrorAction SilentlyContinue
}

20
steps/04-winrm-enable.bat Normal file
View File

@@ -0,0 +1,20 @@
@echo off
REM Double-click to run %~n0.ps1 with UAC elevation.
REM All output goes to this window AND C:\Windows\Temp\setup.log.
fltmc >nul 2>&1
if %errorLevel% neq 0 (
powershell -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
exit /b
)
echo ============================================================
echo Running: %~n0
echo Log : C:\Windows\Temp\setup.log
echo ============================================================
echo.
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dpn0.ps1"
set RC=%errorLevel%
echo.
echo ============================================================
echo Done (exit code %RC%)
echo ============================================================
pause

20
steps/04-winrm-enable.ps1 Normal file
View File

@@ -0,0 +1,20 @@
. "$PSScriptRoot\_common.ps1"
Section 'winrm-enable' {
# winrm quickconfig 不接受 Public profile先把現有連線改成 Private
Get-NetConnectionProfile -ErrorAction SilentlyContinue |
Where-Object { $_.NetworkCategory -ne 'Private' } |
ForEach-Object {
Log ("Set NetConnectionProfile idx {0} ({1}) Private" -f $_.InterfaceIndex, $_.Name)
Set-NetConnectionProfile -InterfaceIndex $_.InterfaceIndex -NetworkCategory Private
}
Enable-PSRemoting -Force -SkipNetworkProfileCheck | Out-Null
Set-Service -Name WinRM -StartupType Automatic
Start-Service -Name WinRM -ErrorAction SilentlyContinue
cmd.exe /c 'winrm quickconfig -quiet -force' | Out-Null
cmd.exe /c 'winrm set winrm/config/service/auth @{Basic="true"}' | Out-Null
cmd.exe /c 'winrm set winrm/config/service @{AllowUnencrypted="true"}' | Out-Null
cmd.exe /c 'winrm set winrm/config/client @{TrustedHosts="*"}' | Out-Null
Enable-NetFirewallRule -DisplayGroup 'Windows Remote Management' -ErrorAction SilentlyContinue
}

View File

@@ -0,0 +1,20 @@
@echo off
REM Double-click to run %~n0.ps1 with UAC elevation.
REM All output goes to this window AND C:\Windows\Temp\setup.log.
fltmc >nul 2>&1
if %errorLevel% neq 0 (
powershell -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
exit /b
)
echo ============================================================
echo Running: %~n0
echo Log : C:\Windows\Temp\setup.log
echo ============================================================
echo.
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dpn0.ps1"
set RC=%errorLevel%
echo.
echo ============================================================
echo Done (exit code %RC%)
echo ============================================================
pause

View File

@@ -0,0 +1,12 @@
. "$PSScriptRoot\_common.ps1"
Section 'onedrive-remove' {
Get-Process OneDrive -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
foreach ($p in @("$env:SystemRoot\System32\OneDriveSetup.exe", "$env:SystemRoot\SysWOW64\OneDriveSetup.exe")) {
if (Test-Path $p) { Start-Process $p -ArgumentList '/uninstall' -Wait -ErrorAction SilentlyContinue }
}
Remove-ItemProperty -Path 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Run' -Name 'OneDrive' -ErrorAction SilentlyContinue
Remove-Item 'HKLM:\SOFTWARE\Classes\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}' -Recurse -ErrorAction SilentlyContinue
Remove-Item 'HKLM:\SOFTWARE\Wow6432Node\Classes\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}' -Recurse -ErrorAction SilentlyContinue
Remove-Item -Path 'C:\OneDriveTemp', "$env:USERPROFILE\OneDrive", "$env:LOCALAPPDATA\Microsoft\OneDrive" -Recurse -Force -ErrorAction SilentlyContinue
}

View File

@@ -0,0 +1,20 @@
@echo off
REM Double-click to run %~n0.ps1 with UAC elevation.
REM All output goes to this window AND C:\Windows\Temp\setup.log.
fltmc >nul 2>&1
if %errorLevel% neq 0 (
powershell -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
exit /b
)
echo ============================================================
echo Running: %~n0
echo Log : C:\Windows\Temp\setup.log
echo ============================================================
echo.
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dpn0.ps1"
set RC=%errorLevel%
echo.
echo ============================================================
echo Done (exit code %RC%)
echo ============================================================
pause

View File

@@ -0,0 +1,12 @@
. "$PSScriptRoot\_common.ps1"
Section 'cortana-websearch-off' {
$k1 = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\Windows Search'
Set-RegValue $k1 'AllowCortana' 0
Set-RegValue $k1 'DisableWebSearch' 1
Set-RegValue $k1 'ConnectedSearchUseWeb' 0
Set-RegValue $k1 'AllowSearchToUseLocation' 0
$k2 = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\Explorer'
Set-RegValue $k2 'DisableSearchBoxSuggestions' 1
}

View File

@@ -0,0 +1,20 @@
@echo off
REM Double-click to run %~n0.ps1 with UAC elevation.
REM All output goes to this window AND C:\Windows\Temp\setup.log.
fltmc >nul 2>&1
if %errorLevel% neq 0 (
powershell -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
exit /b
)
echo ============================================================
echo Running: %~n0
echo Log : C:\Windows\Temp\setup.log
echo ============================================================
echo.
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dpn0.ps1"
set RC=%errorLevel%
echo.
echo ============================================================
echo Done (exit code %RC%)
echo ============================================================
pause

View File

@@ -0,0 +1,59 @@
. "$PSScriptRoot\_common.ps1"
# 把 UI / IME 預設套到 Default profile新使用者首次登入會繼承
Section 'apply-ui-ime-default-profile' {
$hivePath = 'C:\Users\Default\NTUSER.DAT'
$mountKey = 'HKU\SetupDefault'
reg.exe load $mountKey $hivePath | Out-Null
try {
$base = "Registry::$mountKey"
$adv = "$base\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced"
Set-RegValue $adv 'HideFileExt' 0
Set-RegValue $adv 'Hidden' 1
Set-RegValue $adv 'TaskbarAl' 0
Set-RegValue $adv 'ShowTaskViewButton' 0
Set-RegValue $adv 'LaunchTo' 1
Set-RegValue $adv 'Start_TrackProgs' 0
$theme = "$base\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"
Set-RegValue $theme 'AppsUseLightTheme' 0
Set-RegValue $theme 'SystemUseLightTheme' 0
$search = "$base\Software\Microsoft\Windows\CurrentVersion\Search"
Set-RegValue $search 'SearchboxTaskbarMode' 0
# SearchSettings 這個 key 在某些 build 上 reg.exe import/add 會被「存取被拒」
# Win11Debloat 的 Disable_Search_History.reg 跟 Disable_Search_Highlights.reg 都會撞),
# 但 PowerShell 的 .NET API 寫得進去——所以這裡一次處理掉Win11Debloat config 已把這兩項移除
$searchSettings = "$base\Software\Microsoft\Windows\CurrentVersion\SearchSettings"
Set-RegValue $searchSettings 'IsDeviceSearchHistoryEnabled' 0
Set-RegValue $searchSettings 'IsDynamicSearchBoxEnabled' 0
$cloud = "$base\Software\Policies\Microsoft\Windows\CloudContent"
Set-RegValue $cloud 'DisableSpotlightCollectionOnDesktop' 1
Set-RegValue $cloud 'DisableWindowsSpotlightFeatures' 1
# 鍵盤排序:英文鍵盤排第一(預設英數),注音排第二
$kbPre = "$base\Keyboard Layout\Preload"
Set-RegValue $kbPre '1' '00000409' 'String'
Set-RegValue $kbPre '2' '00000404' 'String'
# CHT 注音:預設英數模式、關掉雲端候選與預測
$cht = "$base\Software\Microsoft\InputMethod\Settings\CHT"
Set-RegValue $cht 'Enable Default Input Mode' 0
Set-RegValue $cht 'Enable Cand Predict' 0
Set-RegValue $cht 'Enable Cloud Cand' 0
Set-RegValue $cht 'Enable Cloud Input' 0
# 把系統 Hotkey 切換鍵停掉3 = 不指定鍵)
$tg = "$base\Keyboard Layout\Toggle"
Set-RegValue $tg 'Hotkey' '3' 'String'
Set-RegValue $tg 'Language Hotkey' '3' 'String'
Set-RegValue $tg 'Layout Hotkey' '3' 'String'
} finally {
[GC]::Collect()
Start-Sleep 1
reg.exe unload $mountKey | Out-Null
}
}

View File

@@ -0,0 +1,20 @@
@echo off
REM Double-click to run %~n0.ps1 with UAC elevation.
REM All output goes to this window AND C:\Windows\Temp\setup.log.
fltmc >nul 2>&1
if %errorLevel% neq 0 (
powershell -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
exit /b
)
echo ============================================================
echo Running: %~n0
echo Log : C:\Windows\Temp\setup.log
echo ============================================================
echo.
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dpn0.ps1"
set RC=%errorLevel%
echo.
echo ============================================================
echo Done (exit code %RC%)
echo ============================================================
pause

View File

@@ -0,0 +1,7 @@
. "$PSScriptRoot\_common.ps1"
Section 'hide-admin-account' {
# 把 Admin 從登入畫面藏掉(帳號仍存在仍可用)
$userListKey = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList'
Set-RegValue $userListKey 'Admin' 0
}

View File

@@ -0,0 +1,20 @@
@echo off
REM Double-click to run %~n0.ps1 with UAC elevation.
REM All output goes to this window AND C:\Windows\Temp\setup.log.
fltmc >nul 2>&1
if %errorLevel% neq 0 (
powershell -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
exit /b
)
echo ============================================================
echo Running: %~n0
echo Log : C:\Windows\Temp\setup.log
echo ============================================================
echo.
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dpn0.ps1"
set RC=%errorLevel%
echo.
echo ============================================================
echo Done (exit code %RC%)
echo ============================================================
pause

View File

@@ -0,0 +1,18 @@
. "$PSScriptRoot\_common.ps1"
Section 'pause-windows-update' {
$now = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
$expiry = (Get-Date).ToUniversalTime().AddHours(24).ToString("yyyy-MM-ddTHH:mm:ssZ")
$ux = 'HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings'
Set-RegValue $ux 'PauseUpdatesStartTime' $now 'String'
Set-RegValue $ux 'PauseUpdatesExpiryTime' $expiry 'String'
Set-RegValue $ux 'PauseFeatureUpdatesStartTime' $now 'String'
Set-RegValue $ux 'PauseFeatureUpdatesEndTime' $expiry 'String'
Set-RegValue $ux 'PauseQualityUpdatesStartTime' $now 'String'
Set-RegValue $ux 'PauseQualityUpdatesEndTime' $expiry 'String'
Log "WU paused until $expiry (UTC)"
foreach ($svc in @('UsoSvc','wuauserv','BITS')) {
Stop-Service -Name $svc -Force -ErrorAction SilentlyContinue
}
}

View File

@@ -0,0 +1,20 @@
@echo off
REM Double-click to run %~n0.ps1 with UAC elevation.
REM All output goes to this window AND C:\Windows\Temp\setup.log.
fltmc >nul 2>&1
if %errorLevel% neq 0 (
powershell -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
exit /b
)
echo ============================================================
echo Running: %~n0
echo Log : C:\Windows\Temp\setup.log
echo ============================================================
echo.
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dpn0.ps1"
set RC=%errorLevel%
echo.
echo ============================================================
echo Done (exit code %RC%)
echo ============================================================
pause

View File

@@ -0,0 +1,8 @@
. "$PSScriptRoot\_common.ps1"
Section 'openssh-server-install' {
Wait-Network -timeoutSec 30
Add-WindowsCapability -Online -Name 'OpenSSH.Server~~~~0.0.1.0' -ErrorAction Stop | Out-Null
Set-Service sshd -StartupType Automatic
Start-Service sshd
}

20
steps/11-kms-activate.bat Normal file
View File

@@ -0,0 +1,20 @@
@echo off
REM Double-click to run %~n0.ps1 with UAC elevation.
REM All output goes to this window AND C:\Windows\Temp\setup.log.
fltmc >nul 2>&1
if %errorLevel% neq 0 (
powershell -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
exit /b
)
echo ============================================================
echo Running: %~n0
echo Log : C:\Windows\Temp\setup.log
echo ============================================================
echo.
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dpn0.ps1"
set RC=%errorLevel%
echo.
echo ============================================================
echo Done (exit code %RC%)
echo ============================================================
pause

46
steps/11-kms-activate.ps1 Normal file
View File

@@ -0,0 +1,46 @@
. "$PSScriptRoot\_common.ps1"
# 內網 KMS 主機請改下面這列;保持空陣列就會用公開 KMS 清單輪流試
$InternalKmsServers = @()
$PublicKmsServers = @('kms.digiboy.ir', 'kms8.msguides.com', 'kms.03k.org', 'kms.chinancce.com')
function Test-WindowsActivated {
# ApplicationID 55c92734-... = Windows
# LicenseStatus: 0=Unlicensed, 1=Licensed, 2=OOB grace, 3=OOT grace, 4=NonGenuine grace, 5=Notification, 6=Extended grace
$slp = Get-CimInstance -ClassName SoftwareLicensingProduct -ErrorAction SilentlyContinue |
Where-Object {
$_.PartialProductKey -and
$_.ApplicationID -eq '55c92734-d682-4d71-983e-d6ec3f16059f' -and
$_.LicenseStatus -eq 1
}
return [bool]$slp
}
Section 'kms-activate' {
Wait-Network -timeoutSec 30
if (Test-WindowsActivated) {
Log 'Already activated; skipping KMS attempts'
return
}
$servers = @()
if ($InternalKmsServers.Count -gt 0) { $servers += $InternalKmsServers }
$servers += $PublicKmsServers
$activated = $false
foreach ($kms in $servers) {
Log "Try KMS: $kms"
cscript.exe //nologo C:\Windows\System32\slmgr.vbs /skms $kms | Out-Null
cscript.exe //nologo C:\Windows\System32\slmgr.vbs /ato 2>&1 | Out-Null
Start-Sleep 1
if (Test-WindowsActivated) {
Log "Activated via $kms"
$activated = $true
break
} else {
Log " not activated yet"
}
}
if (-not $activated) { Log 'All KMS servers failed; remaining in grace period' }
}

View File

@@ -0,0 +1,20 @@
@echo off
REM Double-click to run %~n0.ps1 with UAC elevation.
REM All output goes to this window AND C:\Windows\Temp\setup.log.
fltmc >nul 2>&1
if %errorLevel% neq 0 (
powershell -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
exit /b
)
echo ============================================================
echo Running: %~n0
echo Log : C:\Windows\Temp\setup.log
echo ============================================================
echo.
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dpn0.ps1"
set RC=%errorLevel%
echo.
echo ============================================================
echo Done (exit code %RC%)
echo ============================================================
pause

View File

@@ -0,0 +1,10 @@
. "$PSScriptRoot\_common.ps1"
Section 'winget-install-chrome' {
Wait-Network -timeoutSec 30
$winget = (Get-Command winget -ErrorAction SilentlyContinue).Source
if (-not $winget) { Log 'winget not found (Win10 沒裝 App Installer)'; return }
& $winget install --id Google.Chrome -e --silent --source winget `
--accept-source-agreements --accept-package-agreements --scope machine 2>&1 |
ForEach-Object { Log " winget: $_" }
}

View File

@@ -0,0 +1,20 @@
@echo off
REM Double-click to run %~n0.ps1 with UAC elevation.
REM All output goes to this window AND C:\Windows\Temp\setup.log.
fltmc >nul 2>&1
if %errorLevel% neq 0 (
powershell -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
exit /b
)
echo ============================================================
echo Running: %~n0
echo Log : C:\Windows\Temp\setup.log
echo ============================================================
echo.
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dpn0.ps1"
set RC=%errorLevel%
echo.
echo ============================================================
echo Done (exit code %RC%)
echo ============================================================
pause

View File

@@ -0,0 +1,24 @@
. "$PSScriptRoot\_common.ps1"
Section 'remove-windows-ai' {
Wait-Network -timeoutSec 30
# 用顯式 options 取代 -AllOptions排除 'UpdateCleanupCheck'
# 那段會跑 DISM /Cleanup-Image + sfc /scannow + 跳 MessageBox 問要不要重啟,
# 在 WinRM/nonInteractive/Session-0 下會 default Yes 觸發 Restart-Computer -Force
# 把整個 RUN-ALL 流程打斷。其他 10 個 option 不會重啟。
$opts = @(
'DisableRegKeys',
'PreventAIPackageReinstall',
'DisableCopilotPolicies',
'RemoveAppxPackages',
'RemoveRecallFeature',
'RemoveCBSPackages',
'RemoveAIFiles',
'HideAIComponents',
'DisableRewrite',
'RemoveRecallTasks'
)
$sb = [scriptblock]::Create((Invoke-RestMethod 'https://raw.githubusercontent.com/zoicware/RemoveWindowsAI/main/RemoveWindowsAi.ps1'))
& $sb -nonInteractive -Options $opts -EnableLogging | Out-Null
}

20
steps/14-win11debloat.bat Normal file
View File

@@ -0,0 +1,20 @@
@echo off
REM Double-click to run %~n0.ps1 with UAC elevation.
REM All output goes to this window AND C:\Windows\Temp\setup.log.
fltmc >nul 2>&1
if %errorLevel% neq 0 (
powershell -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
exit /b
)
echo ============================================================
echo Running: %~n0
echo Log : C:\Windows\Temp\setup.log
echo ============================================================
echo.
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dpn0.ps1"
set RC=%errorLevel%
echo.
echo ============================================================
echo Done (exit code %RC%)
echo ============================================================
pause

21
steps/14-win11debloat.ps1 Normal file
View File

@@ -0,0 +1,21 @@
. "$PSScriptRoot\_common.ps1"
Section 'win11debloat' {
Wait-Network -timeoutSec 30
$work = Join-Path $env:TEMP 'win11debloat'
$zip = "$work.zip"
if (Test-Path $work) { Remove-Item $work -Recurse -Force }
Invoke-WebRequest -UseBasicParsing -Uri 'https://github.com/Raphire/Win11Debloat/archive/refs/heads/master.zip' -OutFile $zip
Expand-Archive -Path $zip -DestinationPath $work -Force
$dir = Get-ChildItem $work -Directory | Select-Object -First 1
if (-not $dir) { throw 'Win11Debloat zip extracted but no inner dir' }
$cfgSrc = Join-Path $PSScriptRoot 'win11debloat-config.json'
$cfgDst = Join-Path $dir.FullName 'unattend-config.json'
Copy-Item $cfgSrc $cfgDst -Force
# -Sysprep 讓 Win11Debloat 把所有 HKCU 設定寫到 Default profile 的 offline hive
# 避免 HKCU\SearchSettings 被 Search Indexer 鎖住造成 reg import 失敗,
# 同時符合「Admin 會被隱藏、未來只有 User 登入」的部署模式
& "$($dir.FullName)\Win11Debloat.ps1" -Silent -Sysprep -Config $cfgDst -NoRestartExplorer
}

View File

@@ -0,0 +1,20 @@
@echo off
REM Double-click to run %~n0.ps1 with UAC elevation.
REM All output goes to this window AND C:\Windows\Temp\setup.log.
fltmc >nul 2>&1
if %errorLevel% neq 0 (
powershell -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
exit /b
)
echo ============================================================
echo Running: %~n0
echo Log : C:\Windows\Temp\setup.log
echo ============================================================
echo.
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dpn0.ps1"
set RC=%errorLevel%
echo.
echo ============================================================
echo Done (exit code %RC%)
echo ============================================================
pause

View File

@@ -0,0 +1,35 @@
. "$PSScriptRoot\_common.ps1"
$UwpRemoveList = @(
'Microsoft.BingNews','Microsoft.BingWeather','Microsoft.GetHelp',
'Microsoft.MicrosoftSolitaireCollection','Microsoft.Todos','Microsoft.Windows.DevHome',
'Microsoft.WindowsAlarms','Microsoft.WindowsCamera','Microsoft.WindowsFeedbackHub',
'Microsoft.WindowsSoundRecorder','Microsoft.Xbox.TCUI','Microsoft.XboxGameCallableUI',
'Microsoft.XboxIdentityProvider','Microsoft.XboxSpeechToTextOverlay','Microsoft.YourPhone',
'Microsoft.ZuneMusic','MicrosoftCorporationII.QuickAssist',
'MicrosoftWindows.Client.WebExperience','MicrosoftWindows.CrossDevice','MSTeams',
'Microsoft.PowerAutomateDesktop','Microsoft.Windows.Photos'
)
Section 'remove-uwp-apps' {
foreach ($name in $UwpRemoveList) {
Get-AppxPackage -AllUsers -Name $name -ErrorAction SilentlyContinue | ForEach-Object {
try {
Remove-AppxPackage -AllUsers -Package $_.PackageFullName -ErrorAction Stop
Log "Removed AppxPackage $name"
} catch {
Log "Skip AppxPackage $name : $($_.Exception.Message)"
}
}
Get-AppxProvisionedPackage -Online -ErrorAction SilentlyContinue |
Where-Object { $_.DisplayName -eq $name } |
ForEach-Object {
try {
Remove-AppxProvisionedPackage -Online -PackageName $_.PackageName -ErrorAction Stop | Out-Null
Log "Removed Provisioned $name"
} catch {
Log "Skip Provisioned $name : $($_.Exception.Message)"
}
}
}
}

View File

@@ -0,0 +1,20 @@
@echo off
REM Double-click to run %~n0.ps1 with UAC elevation.
REM All output goes to this window AND C:\Windows\Temp\setup.log.
fltmc >nul 2>&1
if %errorLevel% neq 0 (
powershell -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
exit /b
)
echo ============================================================
echo Running: %~n0
echo Log : C:\Windows\Temp\setup.log
echo ============================================================
echo.
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dpn0.ps1"
set RC=%errorLevel%
echo.
echo ============================================================
echo Done (exit code %RC%)
echo ============================================================
pause

View File

@@ -0,0 +1,9 @@
. "$PSScriptRoot\_common.ps1"
Section 'install-telegram' {
Wait-Network -timeoutSec 30
$p = Join-Path $env:TEMP 'tsetup.exe'
Invoke-WebRequest -UseBasicParsing -Uri 'https://telegram.org/dl/desktop/win64' -OutFile $p
$proc = Start-Process -FilePath $p -ArgumentList '/VERYSILENT','/SUPPRESSMSGBOXES','/NORESTART' -Wait -PassThru
Log "Telegram installer exit code: $($proc.ExitCode)"
}

View File

@@ -0,0 +1,20 @@
@echo off
REM Double-click to run %~n0.ps1 with UAC elevation.
REM All output goes to this window AND C:\Windows\Temp\setup.log.
fltmc >nul 2>&1
if %errorLevel% neq 0 (
powershell -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
exit /b
)
echo ============================================================
echo Running: %~n0
echo Log : C:\Windows\Temp\setup.log
echo ============================================================
echo.
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dpn0.ps1"
set RC=%errorLevel%
echo.
echo ============================================================
echo Done (exit code %RC%)
echo ============================================================
pause

View File

@@ -0,0 +1,30 @@
. "$PSScriptRoot\_common.ps1"
Section 'resume-and-trigger-windows-update' {
$ux = 'HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings'
foreach ($n in @('PauseUpdatesStartTime','PauseUpdatesExpiryTime',
'PauseFeatureUpdatesStartTime','PauseFeatureUpdatesEndTime',
'PauseQualityUpdatesStartTime','PauseQualityUpdatesEndTime')) {
Remove-ItemProperty -Path $ux -Name $n -ErrorAction SilentlyContinue
}
foreach ($svc in @('BITS','wuauserv','UsoSvc')) {
Start-Service -Name $svc -ErrorAction SilentlyContinue
}
$uso = "$env:SystemRoot\System32\UsoClient.exe"
if (Test-Path $uso) {
& $uso RefreshSettings | Out-Null
& $uso StartScan | Out-Null
& $uso StartDownload | Out-Null
& $uso StartInstall | Out-Null
Log 'WU resumed; UsoClient scan/download/install triggered'
} else {
try {
(New-Object -ComObject Microsoft.Update.AutoUpdate).DetectNow()
Log 'WU resumed; AutoUpdate.DetectNow triggered (COM fallback)'
} catch {
Log "WU resumed but trigger failed: $($_.Exception.Message)"
}
}
}

53
steps/README.txt Normal file
View File

@@ -0,0 +1,53 @@
steps/ — 拆解後的 post-install 步驟,每段一個 .bat 雙擊執行
==========================================================
用法
----
1. 把 USB 插上 Windows 機器
2. 用檔案總管打開 D:\ventoy\script\steps\D 換成 USB 在 Windows 看到的代號)
3. 雙擊任一個 NN-xxx.bat會自動 UAC 提權)
4. 黑色 console 視窗會即時顯示 START / DONE / ERR
5. 跑完按任意鍵關掉視窗
想一次跑完,雙擊 RUN-ALL.bat 即可。
順序建議
--------
Phase 1不需要網路本機設定
01-firewall-ssh-22 防火牆開 SSH 22
02-rdp-enable-3389 打開 RDP
03-icmp-echo-allow 允許 ping
04-winrm-enable 啟用 WinRM
05-onedrive-remove 移除 OneDrive
06-cortana-websearch-off 關 Cortana / Web Search
07-apply-ui-ime-default-profile UI/IME 預設套到 Default profile
08-hide-admin-account 把 Admin 從登入畫面藏掉
09-pause-windows-update 暫停 WU避免下面動作搶頻寬
Phase 2需要網路下載安裝
10-openssh-server-install OpenSSH Server FoD
11-kms-activate KMS 啟用
12-winget-install-chrome 裝 Chrome
13-remove-windows-ai RemoveWindowsAI 線上腳本
14-win11debloat Win11Debloat讀同目錄 win11debloat-config.json
15-remove-uwp-apps 批次移除 UWP
16-install-telegram 裝 Telegram
收尾
17-resume-trigger-wu 解除 WU 暫停 + 觸發掃描
Log
---
所有 step.ps1 共用同一個 log
C:\Windows\Temp\setup.log
每段會印 [時間] === 段名 START === / DONE === / ERR ===
檔案結構
--------
_common.ps1 共用 helperLog / Section / Set-RegValue / Wait-Network
_template.bat .bat 樣板(不要動)
NN-xxx.ps1 各段邏輯
NN-xxx.bat 各段 .bat自動 UAC + 跑 .ps1
win11debloat-config.json 14 段用的 JSON 設定
RUN-ALL.bat 依序跑 01..17 全部

27
steps/RUN-ALL.bat Normal file
View File

@@ -0,0 +1,27 @@
@echo off
REM RUN-ALL.bat - run steps 00..17 in order.
REM Each step runs independently; a failure in one does not stop the rest.
fltmc >nul 2>&1
if %errorLevel% neq 0 (
powershell -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
exit /b
)
cd /d "%~dp0"
echo ============================================================
echo RUN-ALL: running steps 00..17 sequentially
echo Log : C:\Windows\Temp\setup.log
echo ============================================================
for %%F in (00-network-private 01-firewall-ssh-22 02-rdp-enable-3389 03-icmp-echo-allow 04-winrm-enable 05-onedrive-remove 06-cortana-websearch-off 07-apply-ui-ime-default-profile 08-hide-admin-account 09-pause-windows-update 10-openssh-server-install 11-kms-activate 12-winget-install-chrome 13-remove-windows-ai 14-win11debloat 15-remove-uwp-apps 16-install-telegram 17-resume-trigger-wu) do (
echo.
echo --- %%F ---
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0%%F.ps1"
)
echo.
echo ============================================================
echo RUN-ALL done.
echo ============================================================
pause

26
steps/_admin-shell.bat Normal file
View File

@@ -0,0 +1,26 @@
@echo off
REM _admin-shell.bat - opens an elevated cmd in steps\.
REM Run this once, enter Admin password, then call any step .bat
REM from inside the elevated window without further UAC prompts.
fltmc >nul 2>&1
if %errorLevel% neq 0 (
powershell -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
exit /b
)
cd /d "%~dp0"
echo ============================================================
echo Admin shell opened at: %~dp0
whoami /groups | findstr /i "S-1-5-32-544 BUILTIN\Administrators" >nul && echo [OK] elevated as Administrator || echo [WARN] NOT elevated
echo ============================================================
echo Run any step .bat without further UAC prompts, e.g.:
echo 01-firewall-ssh-22.bat
echo 02-rdp-enable-3389.bat
echo ...
echo RUN-ALL.bat runs all 17 steps in order
echo exit close this window
echo ============================================================
echo.
cmd /K

47
steps/_common.ps1 Normal file
View File

@@ -0,0 +1,47 @@
# _common.ps1 — 給 steps/*.ps1 dot-source 用的共用 helper
# 每個 step.ps1 開頭會:. "$PSScriptRoot\_common.ps1"
$ErrorActionPreference = 'Continue'
$ProgressPreference = 'SilentlyContinue'
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$global:LogFile = 'C:\Windows\Temp\setup.log'
if (-not (Test-Path 'C:\Windows\Temp')) {
New-Item -ItemType Directory -Path 'C:\Windows\Temp' -Force | Out-Null
}
function Log {
param([string]$msg)
$line = '[{0}] {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $msg
Add-Content -Path $LogFile -Value $line -Encoding UTF8
Write-Host $line
}
function Section {
param([string]$name, [scriptblock]$block)
Log "=== $name START ==="
try {
& $block
Log "=== $name DONE ==="
} catch {
Log "=== $name ERR: $($_.Exception.Message) ==="
}
}
function Set-RegValue {
param([string]$Path, [string]$Name, $Value, [string]$Type = 'DWord')
if (-not (Test-Path $Path)) { New-Item -Path $Path -Force | Out-Null }
New-ItemProperty -Path $Path -Name $Name -Value $Value -PropertyType $Type -Force | Out-Null
}
function Wait-Network {
param([int]$timeoutSec = 60)
$i = 0
$max = [int]($timeoutSec / 2)
while ($i -lt $max -and -not (Test-Connection 8.8.8.8 -Count 1 -Quiet -ErrorAction SilentlyContinue)) {
Start-Sleep 2
$i++
}
Log "Network wait: $($i*2)s"
}

20
steps/_template.bat Normal file
View File

@@ -0,0 +1,20 @@
@echo off
REM Double-click to run %~n0.ps1 with UAC elevation.
REM All output goes to this window AND C:\Windows\Temp\setup.log.
fltmc >nul 2>&1
if %errorLevel% neq 0 (
powershell -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
exit /b
)
echo ============================================================
echo Running: %~n0
echo Log : C:\Windows\Temp\setup.log
echo ============================================================
echo.
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dpn0.ps1"
set RC=%errorLevel%
echo.
echo ============================================================
echo Done (exit code %RC%)
echo ============================================================
pause

View File

@@ -0,0 +1,48 @@
{
"Version": "1.0",
"Tweaks": [
{ "Name": "RemoveApps", "Value": true },
{ "Name": "RemoveGamingApps", "Value": true },
{ "Name": "RemoveCommApps", "Value": true },
{ "Name": "RemoveW11Outlook", "Value": true },
{ "Name": "DisableTelemetry", "Value": true },
{ "Name": "DisableBing", "Value": true },
{ "Name": "DisableStoreSearchSuggestions", "Value": true },
{ "Name": "DisableLockscreenTips", "Value": true },
{ "Name": "DisableSuggestions", "Value": true },
{ "Name": "DisableDesktopSpotlight", "Value": true },
{ "Name": "DisableFindMyDevice", "Value": true },
{ "Name": "DisableEdgeAds", "Value": true },
{ "Name": "DisableBraveBloat", "Value": true },
{ "Name": "DisableSettings365Ads", "Value": true },
{ "Name": "DisableSettingsHome", "Value": true },
{ "Name": "DisableCopilot", "Value": true },
{ "Name": "DisableRecall", "Value": true },
{ "Name": "DisableClickToDo", "Value": true },
{ "Name": "DisableAISvcAutoStart", "Value": true },
{ "Name": "DisablePaintAI", "Value": true },
{ "Name": "DisableNotepadAI", "Value": true },
{ "Name": "DisableEdgeAI", "Value": true },
{ "Name": "PreventUpdateAutoReboot", "Value": true },
{ "Name": "DisableModernStandbyNetworking", "Value": true },
{ "Name": "DisableDVR", "Value": true },
{ "Name": "DisableGameBarIntegration", "Value": true },
{ "Name": "ShowHiddenFolders", "Value": true },
{ "Name": "ShowKnownFileExt", "Value": true },
{ "Name": "EnableDarkMode", "Value": true },
{ "Name": "TaskbarAlignLeft", "Value": true },
{ "Name": "HideTaskview", "Value": true },
{ "Name": "HideChat", "Value": true },
{ "Name": "DisableWidgets", "Value": true },
{ "Name": "DisableStartRecommended", "Value": true },
{ "Name": "Hide3dObjects", "Value": true },
{ "Name": "ExplorerToThisPC", "Value": true },
{ "Name": "HideGallery", "Value": true },
{ "Name": "DisableDragTray", "Value": true }
],
"Deployment": [
{ "Name": "CreateRestorePoint", "Value": true },
{ "Name": "RestartExplorer", "Value": false },
{ "Name": "AppRemovalScopeIndex", "Value": 0 }
]
}

View File

@@ -148,44 +148,45 @@
</Password> </Password>
</AutoLogon> </AutoLogon>
<!-- 首次登入執行的自訂腳本 --> <!-- 首次登入執行的自訂腳本
前 4 條為「不需網路、可獨立成功」的本機指令;
Order 5 從 Ventoy USB 把 Setup.ps1 複製到 C:\Scripts\
Order 6 跑 Setup.ps113 項 post-install 動作集中在那裡),
Order 7 重開機。
各條都會寫獨立 log 到 C:\Windows\Temp\,方便排查無聲失敗。 -->
<FirstLogonCommands> <FirstLogonCommands>
<SynchronousCommand wcm:action="add"> <SynchronousCommand wcm:action="add">
<Order>1</Order> <Order>1</Order>
<CommandLine>powershell.exe -ExecutionPolicy Bypass -File C:\Scripts\Setup.ps1</CommandLine>
<Description>執行部署後設定腳本</Description>
<RequiresUserInput>false</RequiresUserInput>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<Order>2</Order>
<CommandLine>cmd.exe /c net accounts /maxpwage:unlimited</CommandLine> <CommandLine>cmd.exe /c net accounts /maxpwage:unlimited</CommandLine>
<Description>密碼永不過期</Description> <Description>密碼永不過期</Description>
</SynchronousCommand> </SynchronousCommand>
<SynchronousCommand wcm:action="add"> <SynchronousCommand wcm:action="add">
<Order>3</Order> <Order>2</Order>
<CommandLine>powershell.exe -NoProfile -Command "Rename-Computer -NewName ('PC-' + (Get-Random -Minimum 10000 -Maximum 100000)) -Force"</CommandLine> <CommandLine>powershell.exe -NoProfile -Command "Rename-Computer -NewName ('PC-' + (Get-Random -Minimum 10000 -Maximum 100000)) -Force"</CommandLine>
<Description>主機名改成 PC-XXXXX重開後生效</Description> <Description>主機名改成 PC-XXXXX重開後生效</Description>
</SynchronousCommand> </SynchronousCommand>
<SynchronousCommand wcm:action="add">
<Order>3</Order>
<CommandLine>cmd.exe /c "net user User /add &amp; net user User /logonpasswordchg:yes"</CommandLine>
<Description>建立本機帳號 User空密碼首次登入強制設定新密碼</Description>
<RequiresUserInput>false</RequiresUserInput>
</SynchronousCommand>
<SynchronousCommand wcm:action="add"> <SynchronousCommand wcm:action="add">
<Order>4</Order> <Order>4</Order>
<CommandLine>cmd.exe /c "net user user 1234 /add &amp; net user user /logonpasswordchg:yes"</CommandLine> <CommandLine>powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "$log='C:\Windows\Temp\firstlogon-copysetup.log'; New-Item -ItemType Directory -Force -Path C:\Scripts | Out-Null; $src=$null; foreach ($d in (Get-PSDrive -PSProvider FileSystem -EA 0).Root) { $cand=Join-Path $d 'ventoy\script\Setup.ps1'; if (Test-Path $cand) { $src=$cand; break } }; if ($src) { Copy-Item $src 'C:\Scripts\Setup.ps1' -Force; ('OK from ' + $src + ' at ' + (Get-Date -Format o)) | Out-File $log -Append } else { ('NOT FOUND at ' + (Get-Date -Format o)) | Out-File $log -Append }"</CommandLine>
<Description>建立本機帳號 user密碼 1234首次登入強制改密</Description> <Description>從 Ventoy USB 的 \ventoy\script\Setup.ps1 複製到 C:\Scripts\log: firstlogon-copysetup.log</Description>
<RequiresUserInput>false</RequiresUserInput> <RequiresUserInput>false</RequiresUserInput>
</SynchronousCommand> </SynchronousCommand>
<SynchronousCommand wcm:action="add"> <SynchronousCommand wcm:action="add">
<Order>5</Order> <Order>5</Order>
<CommandLine>powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "$log='C:\Windows\Temp\firstlogon-sshd.log'; $i=0; while ($i -lt 30 -and -not (Test-Connection 8.8.8.8 -Count 1 -Quiet -EA 0)) { Start-Sleep 2; $i++ }; ('NetWait=' + ($i*2) + 's') | Out-File $log -Append; try { Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 -EA Stop | Out-Null; Set-Service sshd -StartupType Automatic -EA Stop; Start-Service sshd -EA Stop; ('OK ' + (Get-Date -Format o)) | Out-File $log -Append } catch { ('ERR ' + $_.Exception.Message) | Out-File $log -Append }"</CommandLine> <CommandLine>cmd.exe /c "(echo == firstlogon-runsetup %DATE% %TIME% == &amp; if exist C:\Scripts\Setup.ps1 (powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\Scripts\Setup.ps1) else (echo SETUP_NOT_FOUND)) &gt; C:\Windows\Temp\firstlogon-runsetup.log 2&gt;&amp;1"</CommandLine>
<Description>等網路→安裝並啟用 OpenSSH Serverlog: firstlogon-sshd.log</Description> <Description>執行 Setup.ps1所有 stdout/stderr 都收到 firstlogon-runsetup.log含 parse error正常 Setup.ps1 自身寫到 setup.log</Description>
<RequiresUserInput>false</RequiresUserInput> <RequiresUserInput>false</RequiresUserInput>
</SynchronousCommand> </SynchronousCommand>
<SynchronousCommand wcm:action="add"> <SynchronousCommand wcm:action="add">
<Order>6</Order> <Order>6</Order>
<CommandLine>powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "$log='C:\Windows\Temp\firstlogon-telegram.log'; [Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12; $ProgressPreference='SilentlyContinue'; $p=Join-Path $env:TEMP 'tsetup.exe'; $i=0; while ($i -lt 30 -and -not (Test-Connection 8.8.8.8 -Count 1 -Quiet -EA 0)) { Start-Sleep 2; $i++ }; ('NetWait=' + ($i*2) + 's') | Out-File $log -Append; try { ('Start ' + (Get-Date -Format o)) | Out-File $log -Append; Invoke-WebRequest -UseBasicParsing -Uri 'https://telegram.org/dl/desktop/win64' -OutFile $p; ('Downloaded ' + (Get-Item $p).Length + 'B') | Out-File $log -Append; $proc=Start-Process -FilePath $p -ArgumentList '/VERYSILENT','/SUPPRESSMSGBOXES','/NORESTART' -Wait -PassThru; ('Exit ' + $proc.ExitCode) | Out-File $log -Append } catch { ('ERR ' + $_.Exception.Message) | Out-File $log -Append }"</CommandLine>
<Description>下載並靜默安裝 Telegram Desktoplog: firstlogon-telegram.log</Description>
<RequiresUserInput>false</RequiresUserInput>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<Order>7</Order>
<CommandLine>shutdown /r /t 5</CommandLine> <CommandLine>shutdown /r /t 5</CommandLine>
<Description>完成後重開機</Description> <Description>完成後重開機</Description>
</SynchronousCommand> </SynchronousCommand>