# Apply all available Windows Updates via the Windows Update COM API in Audit Mode. # Handles reboots automatically — re-registers via RunOnce and continues updating. # Compacts the image afterward to flatten the COW chain. # # Usage: # essentials.windowsUpdate {} # essentials.windowsUpdate { maxRounds = 5; } { pkgs, lib, ... }: { maxRounds ? 3 }: { name = "windows-update"; compact = true; memSize = 4096; qemuTimeout = 7200; auditScript = '' @echo off setlocal :: Track update round via a counter file set "ROUND_FILE=C:\vmix-update-round.txt" set "MAX_ROUNDS=${toString maxRounds}" if exist "%ROUND_FILE%" ( set /p ROUND=<"%ROUND_FILE%" ) else ( set "ROUND=1" ) echo === vmix: Windows Update round %ROUND% of %MAX_ROUNDS% === :: Ensure Windows Update service is running net start wuauserv 2>nul sc config wuauserv start= auto :: Remove any update-blocking policies (LTSC may have these) reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" /f 2>nul reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /f 2>nul :: Give the service time to initialize on first round if "%ROUND%"=="1" ( echo Waiting for Windows Update service to initialize... timeout /t 30 /nobreak >nul ) :: Run Windows Update via PowerShell COM API powershell -ExecutionPolicy Bypass -Command ^ "try {" ^ " $session = New-Object -ComObject Microsoft.Update.Session;" ^ " $searcher = $session.CreateUpdateSearcher();" ^ " Write-Host 'Searching for updates...';" ^ " $result = $searcher.Search('IsInstalled=0');" ^ " $count = $result.Updates.Count;" ^ " Write-Host \"Found $count updates\";" ^ " if ($count -eq 0) { exit 0 };" ^ " foreach ($u in $result.Updates) { Write-Host \" - $($u.Title)\" };" ^ " $updatesToInstall = New-Object -ComObject Microsoft.Update.UpdateColl;" ^ " foreach ($u in $result.Updates) {" ^ " if ($u.EulaAccepted -eq $false) { $u.AcceptEula() };" ^ " $updatesToInstall.Add($u) | Out-Null" ^ " };" ^ " $downloader = $session.CreateUpdateDownloader();" ^ " $downloader.Updates = $updatesToInstall;" ^ " Write-Host 'Downloading...';" ^ " $downloader.Download() | Out-Null;" ^ " $installer = $session.CreateUpdateInstaller();" ^ " $installer.Updates = $updatesToInstall;" ^ " Write-Host 'Installing...';" ^ " $installResult = $installer.Install();" ^ " Write-Host \"Result: $($installResult.ResultCode)\";" ^ " for ($i = 0; $i -lt $updatesToInstall.Count; $i++) {" ^ " $hr = $installResult.GetUpdateResult($i).HResult;" ^ " Write-Host \" $($updatesToInstall.Item($i).Title): code=$hr\"" ^ " };" ^ " if ($installResult.RebootRequired) { exit 3010 } else { exit 0 }" ^ "} catch {" ^ " Write-Host \"ERROR: $_\";" ^ " exit 1" ^ "}" set "WU_EXIT=%ERRORLEVEL%" echo Windows Update exit code: %WU_EXIT% :: Cleanup component store echo Cleaning up component store... dism /Online /Cleanup-Image /StartComponentCleanup /ResetBase /Quiet 2>nul :: Check if we need to reboot and continue set /a "NEXT_ROUND=%ROUND%+1" if "%WU_EXIT%"=="3010" ( if %ROUND% LSS %MAX_ROUNDS% ( echo Reboot required, scheduling round %NEXT_ROUND%... echo %NEXT_ROUND% > "%ROUND_FILE%" :: Copy script to a path the wrapper won't delete copy /y "%~f0" "C:\vmix-update-continue.cmd" >nul reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce" /v vmixUpdate /t REG_SZ /d "cmd /c C:\vmix-update-continue.cmd" /f :: Immediate reboot (preempts wrapper shutdown) shutdown /r /f /t 0 exit /b ) else ( echo Max update rounds reached. ) ) :: Done — clean up and shutdown del /q "%ROUND_FILE%" 2>nul del /q "C:\vmix-update-continue.cmd" 2>nul echo === vmix: Windows Update complete === shutdown /s /f /t 10 /c "vmix: windows-update complete" ''; }