# 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