Files
windows-unattend/Setup.ps1
Timmy 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

359 lines
16 KiB
PowerShell
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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