83 lines
3.5 KiB
PowerShell
83 lines
3.5 KiB
PowerShell
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
|