refactor(template): Defender validation-only + auto WU-reboot loop in Prepare

Setup-WinBuild2025.ps1 (Step 2):
- Defender is now disabled at deploy time by Deploy-WinBuild2025.ps1 via
  DisableAntiSpyware=1 GPO + RTP off during unattended install.
- Step 2 reduced to a single Assert-Step sanity check (GPO key present = 1).
  Exclusion paths removed: scanner is not running, they would be moot.

Prepare-WinBuild2025.ps1 (Setup invocation):
- Replace one-shot Invoke-Command + manual 're-run with -SkipWindowsUpdate'
  message with an automated loop (max 10 iterations):
  * Invoke-GuestSetup helper runs Setup inside the VM and returns exit code.
  * Exit 3010 (ERROR_SUCCESS_REBOOT_REQUIRED) triggers Restart-Computer on
    the guest, waits for WinRM via Wait-GuestWinRMReady (20 min timeout),
    reopens PSSession and loops.
  * Exit 0 breaks the loop; any other code throws immediately.
  * Hard cap prevents infinite loops if WU never converges.
This commit is contained in:
Simone
2026-05-10 01:01:30 +02:00
parent 889bd9e41b
commit ab636a0ec1
2 changed files with 96 additions and 100 deletions
+82 -34
View File
@@ -385,43 +385,91 @@ try {
if ($SkipCleanup) { $setupArgs['SkipCleanup'] = $true }
# ── Run Setup-WinBuild2025.ps1 inside the VM ──────────────────────────────
Write-Host "[Prepare] Running Setup-WinBuild2025.ps1 inside VM (this will take a while)..."
Write-Host "[Prepare] Output from guest:"
Write-Host "------------------------------------------------------------"
# Setup may return exit 3010 (ERROR_SUCCESS_REBOOT_REQUIRED) when Windows
# Update applied patches that need a reboot. We loop: reboot guest, wait for
# WinRM, reopen session, re-invoke Setup. Steps 1-5 are idempotent (~30 s);
# Step 6 picks up where the previous run left off (no leftover updates →
# exit 0). Hard cap on iterations to avoid an infinite loop.
$maxWuIterations = 10
$rebootDeadlineMin = 20
$result = Invoke-Command -Session $session -ScriptBlock {
param($scriptPath, $scriptArgs)
Set-ExecutionPolicy Bypass -Scope Process -Force
$exitCode = 0
try {
& $scriptPath @scriptArgs
$exitCode = $LASTEXITCODE
if ($null -eq $exitCode) { $exitCode = 0 }
}
catch {
Write-Error $_
$exitCode = 1
}
return $exitCode
} -ArgumentList $guestScriptPath, $setupArgs
Write-Host "------------------------------------------------------------"
# ── Handle reboot required (exit 3010) ───────────────────────────────────
if ($result -eq 3010) {
Write-Host ""
Write-Warning "*** REBOOT REQUIRED after Windows Update. ***"
Write-Warning " The VM will reboot. Wait for it to come back up, then run:"
Write-Warning " .\Prepare-WinBuild2025.ps1 -VMIPAddress $VMIPAddress -SkipWindowsUpdate"
# Reboot the VM
Write-Host "[Prepare] Sending reboot command to VM..."
Invoke-Command -Session $session -ScriptBlock { Restart-Computer -Force }
exit 3010
function Invoke-GuestSetup {
param([Parameter(Mandatory)] $Session,
[Parameter(Mandatory)] [string] $ScriptPath,
[Parameter(Mandatory)] [hashtable] $ScriptArgs)
Invoke-Command -Session $Session -ScriptBlock {
param($scriptPath, $scriptArgs)
Set-ExecutionPolicy Bypass -Scope Process -Force
$exitCode = 0
try {
& $scriptPath @scriptArgs
$exitCode = $LASTEXITCODE
if ($null -eq $exitCode) { $exitCode = 0 }
} catch {
Write-Error $_
$exitCode = 1
}
return $exitCode
} -ArgumentList $ScriptPath, $ScriptArgs
}
if ($result -ne 0) {
throw "Setup-WinBuild2025.ps1 exited with code $result"
function Wait-GuestWinRMReady {
param([Parameter(Mandatory)] [string] $IP,
[Parameter(Mandatory)] $Cred,
[Parameter(Mandatory)] $SessionOptions,
[int] $TimeoutMin = 20)
$deadline = (Get-Date).AddMinutes($TimeoutMin)
while ((Get-Date) -lt $deadline) {
Start-Sleep -Seconds 10
try {
$r = Test-WSMan -ComputerName $IP -Credential $Cred `
-Authentication Basic -ErrorAction Stop
if ($r) { return $true }
} catch { }
}
return $false
}
Write-Host "[Prepare] Running Setup-WinBuild2025.ps1 inside VM (this will take a while)..."
for ($iter = 1; $iter -le $maxWuIterations; $iter++) {
Write-Host "[Prepare] Setup iteration $iter/$maxWuIterations"
Write-Host "[Prepare] Output from guest:"
Write-Host "------------------------------------------------------------"
$result = Invoke-GuestSetup -Session $session -ScriptPath $guestScriptPath -ScriptArgs $setupArgs
Write-Host "------------------------------------------------------------"
Write-Host "[Prepare] Iteration $iter exit code: $result"
if ($result -eq 0) { break }
if ($result -ne 3010) {
throw "Setup-WinBuild2025.ps1 exited with code $result"
}
# exit 3010 → WU applied updates needing reboot. Reboot guest, wait,
# reopen session, loop. -SkipWindowsUpdate must NOT be set on retry —
# we want the next iteration to drain remaining updates.
if ($iter -eq $maxWuIterations) {
throw "Windows Update did not converge after $maxWuIterations iterations (still returning 3010)"
}
Write-Host "[Prepare] WU applied patches → reboot required. Rebooting guest..."
try {
Invoke-Command -Session $session -ScriptBlock { Restart-Computer -Force } -ErrorAction SilentlyContinue
} catch { }
Remove-PSSession $session -ErrorAction SilentlyContinue
Start-Sleep -Seconds 30 # let WinRM tear down before polling
Write-Host "[Prepare] Waiting for guest WinRM to come back (max $rebootDeadlineMin min)..."
if (-not (Wait-GuestWinRMReady -IP $VMIPAddress -Cred $credential `
-SessionOptions $sessionOptions -TimeoutMin $rebootDeadlineMin)) {
throw "Guest did not come back online within $rebootDeadlineMin min after WU reboot"
}
Write-Host "[Prepare] Guest WinRM ready, reopening session..."
$session = New-PSSession -ComputerName $VMIPAddress -Credential $credential `
-SessionOption $sessionOptions -Authentication Basic -ErrorAction Stop
}
# ── Post-setup remote validation ──────────────────────────────────────────