From fb5de6f59f8a633375d413541218d62ab09c0e6f Mon Sep 17 00:00:00 2001 From: Simone Date: Wed, 10 Jun 2026 00:48:00 +0200 Subject: [PATCH] fix(toolchain): pin WU disabled across the DISM /ResetBase window MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The disk-cleanup function stopped wuauserv before DISM /StartComponentCleanup /ResetBase but only re-enforced Start=4 AFTER it. /ResetBase drives heavy CBS servicing (TiWorker); during that window WaaSMedicSvc woke, flipped wuauserv Start back to Manual, and a "Schedule Scan" task started it. If the pass was interrupted there (reboot/crash), the template was left with WU running and Manual startup — a half-prepared state where Windows Update keeps firing on a supposedly-locked-down build template. Move the lockdown ahead of DISM: set wuauserv Start=4, apply the WaaSMedicSvc registry write-deny ACL (the only reliable lever — the service is protected and cannot itself be disabled), and disable the WindowsUpdate / UpdateOrchestrator scan tasks, all BEFORE /ResetBase rather than only after. The post-DISM re-enforce stays as a belt-and-suspenders check. Applied identically to the WinBuild2025 and WinBuild2022 toolchains. --- template/Install-CIToolchain-WinBuild2022.ps1 | 35 +++++++++++++++++++ template/Install-CIToolchain-WinBuild2025.ps1 | 35 +++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/template/Install-CIToolchain-WinBuild2022.ps1 b/template/Install-CIToolchain-WinBuild2022.ps1 index 5d72d0e..a45b31f 100644 --- a/template/Install-CIToolchain-WinBuild2022.ps1 +++ b/template/Install-CIToolchain-WinBuild2022.ps1 @@ -253,6 +253,41 @@ function Invoke-DiskCleanup { foreach ($svc in 'wuauserv', 'UsoSvc', 'bits', 'dosvc', 'TrustedInstaller') { Stop-Service -Name $svc -Force -ErrorAction SilentlyContinue } + + # Pin Windows Update down for the WHOLE DISM /ResetBase window below. /ResetBase + # drives heavy CBS servicing (TiWorker); with wuauserv merely *stopped* and no + # ACL yet, WaaSMedicSvc wakes, flips wuauserv Start back to Manual, and a + # "Schedule Scan" task starts it mid-cleanup — so an interruption here leaves WU + # running (exactly the half-prepared state seen on stuck templates). Force + # Start=4, deny WaaSMedicSvc the registry write, and disable the scan tasks + # BEFORE DISM, not only in the re-enforce after it. + Set-Service -Name wuauserv -StartupType Disabled -ErrorAction SilentlyContinue + Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\wuauserv' ` + -Name Start -Value 4 -Type DWord -Force -ErrorAction SilentlyContinue + try { + $svcRegPath = 'HKLM:\SYSTEM\CurrentControlSet\Services\wuauserv' + $acl = Get-Acl $svcRegPath + $denyRule = New-Object System.Security.AccessControl.RegistryAccessRule( + ([System.Security.Principal.NTAccount]'NT SERVICE\WaaSMedicSvc'), + [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 (pre-DISM)." + } catch { + Write-Warning "wuauserv pre-DISM ACL deny skipped (non-fatal): $_" + } + foreach ($t in @( + '\Microsoft\Windows\WindowsUpdate\Scheduled Start', + '\Microsoft\Windows\UpdateOrchestrator\Schedule Scan', + '\Microsoft\Windows\UpdateOrchestrator\Schedule Scan Static Task' + )) { + schtasks /Change /TN $t /Disable 2>&1 | Out-Null + } + $sdDownload = 'C:\Windows\SoftwareDistribution\Download' Remove-Item $sdDownload -Recurse -Force -ErrorAction SilentlyContinue New-Item -ItemType Directory -Path $sdDownload -Force | Out-Null diff --git a/template/Install-CIToolchain-WinBuild2025.ps1 b/template/Install-CIToolchain-WinBuild2025.ps1 index 203ba1d..82ef2b5 100644 --- a/template/Install-CIToolchain-WinBuild2025.ps1 +++ b/template/Install-CIToolchain-WinBuild2025.ps1 @@ -254,6 +254,41 @@ function Invoke-DiskCleanup { foreach ($svc in 'wuauserv', 'UsoSvc', 'bits', 'dosvc', 'TrustedInstaller') { Stop-Service -Name $svc -Force -ErrorAction SilentlyContinue } + + # Pin Windows Update down for the WHOLE DISM /ResetBase window below. /ResetBase + # drives heavy CBS servicing (TiWorker); with wuauserv merely *stopped* and no + # ACL yet, WaaSMedicSvc wakes, flips wuauserv Start back to Manual, and a + # "Schedule Scan" task starts it mid-cleanup — so an interruption here leaves WU + # running (exactly the half-prepared state seen on stuck templates). Force + # Start=4, deny WaaSMedicSvc the registry write, and disable the scan tasks + # BEFORE DISM, not only in the re-enforce after it. + Set-Service -Name wuauserv -StartupType Disabled -ErrorAction SilentlyContinue + Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\wuauserv' ` + -Name Start -Value 4 -Type DWord -Force -ErrorAction SilentlyContinue + try { + $svcRegPath = 'HKLM:\SYSTEM\CurrentControlSet\Services\wuauserv' + $acl = Get-Acl $svcRegPath + $denyRule = New-Object System.Security.AccessControl.RegistryAccessRule( + ([System.Security.Principal.NTAccount]'NT SERVICE\WaaSMedicSvc'), + [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 (pre-DISM)." + } catch { + Write-Warning "wuauserv pre-DISM ACL deny skipped (non-fatal): $_" + } + foreach ($t in @( + '\Microsoft\Windows\WindowsUpdate\Scheduled Start', + '\Microsoft\Windows\UpdateOrchestrator\Schedule Scan', + '\Microsoft\Windows\UpdateOrchestrator\Schedule Scan Static Task' + )) { + schtasks /Change /TN $t /Disable 2>&1 | Out-Null + } + $sdDownload = 'C:\Windows\SoftwareDistribution\Download' Remove-Item $sdDownload -Recurse -Force -ErrorAction SilentlyContinue New-Item -ItemType Directory -Path $sdDownload -Force | Out-Null