diff --git a/template/Deploy-WinBuild2025.ps1 b/template/Deploy-WinBuild2025.ps1 index 8002369..360679f 100644 --- a/template/Deploy-WinBuild2025.ps1 +++ b/template/Deploy-WinBuild2025.ps1 @@ -127,7 +127,7 @@ [CmdletBinding()] param( # ── ISOs ── - [string] $WinISO = 'F:\CI\ISO\26100.1742.240906-0331.ge_release_svc_refresh_SERVER_VOL_x64FRE_en-us.iso', + [string] $WinISO = 'F:\CI\ISO\en-us_windows_server_2025_updated_april_2026_x64_dvd_225d826d.iso', [string] $ToolsISO = 'F:\CI\ISO\VMware-tools-windows.iso', # Cache path for the rebuilt no-prompt Windows ISO. Reused across runs when diff --git a/template/Setup-WinBuild2025.ps1 b/template/Setup-WinBuild2025.ps1 index 8a980dc..5032ac8 100644 --- a/template/Setup-WinBuild2025.ps1 +++ b/template/Setup-WinBuild2025.ps1 @@ -573,6 +573,12 @@ Write-Step "Disabling Windows Update permanently (post-update snapshot lockdown) # Stop + disable main WU service Stop-Service -Name wuauserv -Force -ErrorAction SilentlyContinue Set-Service -Name wuauserv -StartupType Disabled +# Write Start=4 directly to the service registry key in addition to the SCM call. +# Set-Service calls ChangeServiceConfig (SCM API) which WaaSMedicSvc can override via its +# own SCM call. The registry Start value requires WaaSMedicSvc to have registry write +# access — denied by the ACL block below. +Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\wuauserv' ` + -Name Start -Value 4 -Type DWord -Force # Stop + disable Update Orchestrator Service (UsoSvc) — WU client in Server 2025 Stop-Service -Name UsoSvc -Force -ErrorAction SilentlyContinue @@ -603,6 +609,30 @@ foreach ($taskName in @('Scheduled Start', 'WakeTimer', 'Automatic App Update', } Write-Host "Windows Update scheduled tasks disabled (best-effort)." +# Deny WaaSMedicSvc write access to the wuauserv service registry key so it cannot +# reset Start back to Manual (2) or Automatic (3) after we set it to Disabled (4). +# WaaSMedicSvc is a protected service that cannot itself be disabled; ACL denial +# is the only reliable way to prevent it from healing the service startup type. +# Best-effort: wrapped in try/catch so that a WinRM session permission failure is +# non-fatal (the GPO keys + Start=4 still provide a good baseline). +try { + $svcRegPath = 'HKLM:\SYSTEM\CurrentControlSet\Services\wuauserv' + $acl = Get-Acl $svcRegPath + $medicSid = [System.Security.Principal.NTAccount]'NT SERVICE\WaaSMedicSvc' + $denyRule = New-Object System.Security.AccessControl.RegistryAccessRule( + $medicSid, + [System.Security.AccessControl.RegistryRights]'SetValue,CreateSubKey,WriteKey', + [System.Security.AccessControl.InheritanceFlags]::None, + [System.Security.AccessControl.PropagationFlags]::None, + [System.Security.AccessControl.AccessControlType]::Deny + ) + $acl.AddAccessRule($denyRule) + Set-Acl $svcRegPath $acl + Write-Host "wuauserv: WaaSMedicSvc write-deny ACL applied." +} catch { + Write-Warning "wuauserv ACL deny skipped (non-fatal — GPO keys + Start=4 still in place): $_" +} + # Validation Assert-Step 'WUDisable' 'wuauserv StartType Disabled' { (Get-Service wuauserv -ErrorAction Stop).StartType -eq 'Disabled' @@ -991,6 +1021,8 @@ Write-Host "Disk cleanup complete." foreach ($svc in 'wuauserv', 'UsoSvc') { Set-Service -Name $svc -StartupType Disabled -ErrorAction SilentlyContinue } +Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\wuauserv' ` + -Name Start -Value 4 -Type DWord -Force -ErrorAction SilentlyContinue # Validation — leftover artifacts from this run should be gone # Note: do NOT assert SoftwareDistribution\Download empty. @@ -1007,6 +1039,27 @@ Assert-Step 'Cleanup' 'wuauserv StartType Disabled after cache clear' { } } # end if (-not $SkipCleanup) +# ── Pre-Final re-affirmation ────────────────────────────────────────────────── +# Steps 7-10 (installs, cleanup, DISM) can take hours. During that window background +# tasks or WaaSMedicSvc may reset AutoAdminLogon or wuauserv. Re-affirm both here +# immediately before the Final gate so the snapshot captures clean state. + +$wlKey = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' +$wlNow = Get-ItemProperty -Path $wlKey -ErrorAction SilentlyContinue +if ($wlNow.AutoAdminLogon -ne '1' -or $wlNow.DefaultUserName -ne 'Administrator') { + Set-ItemProperty -Path $wlKey -Name AutoAdminLogon -Value '1' -Type String -Force + Set-ItemProperty -Path $wlKey -Name DefaultUserName -Value 'Administrator' -Type String -Force + Set-ItemProperty -Path $wlKey -Name DefaultDomainName -Value '.' -Type String -Force +} +if ($AdminPassword -ne '') { + Set-ItemProperty -Path $wlKey -Name DefaultPassword -Value $AdminPassword -Type String -Force +} + +Stop-Service -Name wuauserv -Force -ErrorAction SilentlyContinue +Set-Service -Name wuauserv -StartupType Disabled +Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\wuauserv' ` + -Name Start -Value 4 -Type DWord -Force + # ── Final pre-snapshot validation ──────────────────────────────────────────── Write-Step "Final pre-snapshot validation" @@ -1028,6 +1081,10 @@ Assert-Step 'Final' 'dotnet, python, msbuild all resolvable' { (Test-Path 'C:\Python\python.exe' -PathType Leaf) -and (Test-Path $msbuildPath -PathType Leaf) } +Assert-Step 'Final' 'AutoAdminLogon=1, DefaultUserName=Administrator' { + $wl = Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' -ErrorAction Stop + $wl.AutoAdminLogon -eq '1' -and $wl.DefaultUserName -eq 'Administrator' +} Assert-Step 'Final' 'Windows Update permanently disabled' { (Get-Service wuauserv -ErrorAction Stop).StartType -eq 'Disabled' }