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

341 lines
15 KiB
PowerShell
Raw 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
#
# 順序:先做不需網路的本機設定(防火牆 / 註冊機碼 / Default profile
# 再等網路最後跑下載安裝OpenSSH FoD / KMS / winget / RemoveAI / Win11Debloat / Telegram
$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 的設定 JSON與 toolkit/config/win11debloat-config.json 同步)
$Win11DebloatConfigJson = @'
{
"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": "DisableSearchHistory", "Value": true },
{ "Name": "DisableBing", "Value": true },
{ "Name": "DisableStoreSearchSuggestions", "Value": true },
{ "Name": "DisableSearchHighlights", "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": "HideSearchTb", "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 }
]
}
'@
# ---- 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
}
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 '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 'rename-hide-accounts' {
$u = Get-LocalUser -Name 'user' -ErrorAction SilentlyContinue
if ($u -and $u.Name -ceq 'user') {
Rename-LocalUser -Name 'user' -NewName 'User'
Log 'Renamed user -> User'
} else {
Log "Local user 'user' not found or already renamed; skip"
}
$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 '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