param( [ValidateSet('Apply','Revert')] [string]$Mode = 'Apply', [switch]$SkipDownload # use existing C:\Users\Admin\RemoveWindowsAi.ps1 instead of re-downloading ) [Console]::OutputEncoding = [Text.Encoding]::UTF8 $ErrorActionPreference = 'Stop' # 1. Must be Windows PowerShell 5.1, NOT pwsh 7+ if ($PSVersionTable.PSVersion.Major -ge 7) { throw "This wrapper must run under Windows PowerShell 5.1 (powershell.exe), not pwsh $($PSVersionTable.PSVersion). Relaunch explicitly with powershell.exe." } # 2. Must have Administrator token $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) if (-not $isAdmin) { throw "Current session is NOT running as Administrator. RemoveWindowsAi.ps1 requires admin and its self-elevation (UAC RunAs) will fail over SSH. Ensure the SSH user has full admin token." } # 3. Fetch upstream script $workDir = Join-Path $env:TEMP 'RemoveWindowsAI' $scriptPath = Join-Path $workDir 'RemoveWindowsAi.ps1' $logPath = Join-Path $workDir ("run-$Mode-$(Get-Date -Format 'yyyyMMdd-HHmmss').log") New-Item -ItemType Directory -Force -Path $workDir | Out-Null if (-not $SkipDownload) { $url = 'https://raw.githubusercontent.com/zoicware/RemoveWindowsAI/main/RemoveWindowsAi.ps1' Write-Host "[*] Downloading $url" [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Invoke-WebRequest -Uri $url -OutFile $scriptPath -UseBasicParsing if ((Get-Item $scriptPath).Length -lt 100KB) { throw "Downloaded script seems too small ($((Get-Item $scriptPath).Length) bytes)" } # PowerShell 5.1 reads files without BOM using the system ANSI code page (CP950 on zh-TW), # which mangles non-ASCII content. Force UTF-8 by prepending a BOM if not already present. $bytes = [IO.File]::ReadAllBytes($scriptPath) $hasBom = $bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF if (-not $hasBom) { $bom = [byte[]](0xEF, 0xBB, 0xBF) [IO.File]::WriteAllBytes($scriptPath, $bom + $bytes) Write-Host "[*] Added UTF-8 BOM" } Write-Host "[*] Saved -> $scriptPath ($((Get-Item $scriptPath).Length) bytes)" } elseif (-not (Test-Path $scriptPath)) { throw "-SkipDownload set but $scriptPath does not exist" } # 4. Build args based on mode $psArgs = @('-NoProfile','-ExecutionPolicy','Bypass','-File', $scriptPath, '-nonInteractive', '-EnableLogging') if ($Mode -eq 'Apply') { $psArgs += @('-AllOptions','-backupMode') Write-Host "[*] Mode=Apply: -AllOptions -backupMode (backup saved for later revert)" } else { $psArgs += @('-AllOptions','-revertMode') Write-Host "[*] Mode=Revert: -AllOptions -revertMode (requires prior backup)" } # 5. Execute and tee output to log Write-Host "[*] Executing: powershell.exe $($psArgs -join ' ')" Write-Host "[*] Log -> $logPath" Write-Host ('-' * 60) $proc = Start-Process -FilePath 'powershell.exe' -ArgumentList $psArgs ` -NoNewWindow -Wait -PassThru ` -RedirectStandardOutput $logPath -RedirectStandardError "$logPath.err" Write-Host ('-' * 60) Get-Content $logPath -ErrorAction SilentlyContinue | Select-Object -Last 80 if (Test-Path "$logPath.err") { $errs = Get-Content "$logPath.err" if ($errs) { Write-Host "--- stderr ---" $errs } } Write-Host "" Write-Host "[=] Exit code: $($proc.ExitCode)" Write-Host "[=] Full log: $logPath" Write-Host "[!] A reboot is recommended after this operation." exit $proc.ExitCode