From 2236fe48e93b3bacfeedc5cb6686950e88ce3abb Mon Sep 17 00:00:00 2001 From: X9 Dev Date: Thu, 16 Apr 2026 14:25:50 +0200 Subject: [PATCH] feat: windows-update step handles multiple reboot rounds via scheduled task First pass runs during deployment (no reboot). Then registers X9-WindowsUpdate scheduled task that fires on every logon, installs remaining updates, and self-deletes when system is fully up to date - covers the typical 2-3 reboot cycles needed on a fresh Windows install. Co-Authored-By: Claude Sonnet 4.6 --- scripts/12-windows-update.ps1 | 103 +++++++++++++++++++++------------- 1 file changed, 65 insertions(+), 38 deletions(-) diff --git a/scripts/12-windows-update.ps1 b/scripts/12-windows-update.ps1 index b091e41..6752775 100644 --- a/scripts/12-windows-update.ps1 +++ b/scripts/12-windows-update.ps1 @@ -3,14 +3,21 @@ Installs all available Windows Updates via PSWindowsUpdate module. .DESCRIPTION - Installs the PSWindowsUpdate module from PSGallery and runs a full - Windows Update pass. Does not auto-reboot - the operator restarts - manually after all deployment steps complete. Skips drivers (handled - by step 11 Dell Command Update or Windows Update for Business). + First pass: installs all currently available updates without rebooting. + Then registers a scheduled task "X9-WindowsUpdate" that runs on every + logon (as SYSTEM) until no more updates are found - handles the typical + 2-3 reboot cycles required on a fresh Windows installation. + + Operator workflow: + 1. xetup completes all steps + 2. Operator reboots manually + 3. On each subsequent logon the scheduled task runs another update pass + 4. Task removes itself automatically when system is fully up to date .ITEMS - nainstalovat-pswindowsupdate-modul: Installs NuGet provider and PSWindowsUpdate module from PSGallery. Required only on first run - subsequent runs reuse the cached module. - spustit-windows-update-vsechny-aktualizace: Calls Install-WindowsUpdate -AcceptAll -IgnoreReboot. Installs all Quality, Security and Feature updates. Skips reboot - operator restarts manually after deployment completes. + nainstalovat-pswindowsupdate-modul: Installs NuGet provider and PSWindowsUpdate module from PSGallery. + spustit-prvni-kolo-windows-update: First update pass without reboot - installs all currently available updates. + registrovat-scheduled-task-pro-dalsi-kola: Registers X9-WindowsUpdate scheduled task that runs on logon, handles post-reboot update rounds, and self-deletes when no more updates are found. #> param( [object]$Config, @@ -30,21 +37,11 @@ function Write-Log { Write-Log "=== Step 12 - Windows Update ===" -Level STEP # ----------------------------------------------------------------------- -# 1. NuGet provider (required for Install-Module from PSGallery) +# 1. NuGet provider + PSWindowsUpdate module # ----------------------------------------------------------------------- -Write-Log "Installing NuGet provider..." -Level INFO +Write-Log "Setting up PSWindowsUpdate module..." -Level INFO try { Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Scope AllUsers | Out-Null - Write-Log " NuGet provider ready" -Level OK -} catch { - Write-Log " NuGet provider install failed: $_" -Level WARN -} - -# ----------------------------------------------------------------------- -# 2. PSWindowsUpdate module -# ----------------------------------------------------------------------- -Write-Log "Installing PSWindowsUpdate module..." -Level INFO -try { $existing = Get-Module -ListAvailable -Name PSWindowsUpdate | Select-Object -First 1 if ($existing) { Write-Log " PSWindowsUpdate $($existing.Version) already installed" -Level INFO @@ -54,35 +51,65 @@ try { } Import-Module PSWindowsUpdate -Force } catch { - Write-Log " PSWindowsUpdate module setup failed: $_" -Level ERROR - Write-Log " Skipping Windows Update step" -Level WARN + Write-Log " Module setup failed: $_" -Level ERROR exit 1 } # ----------------------------------------------------------------------- -# 3. Run Windows Update +# 2. First update pass (no reboot) # ----------------------------------------------------------------------- -Write-Log "Checking for available updates..." -Level INFO - +Write-Log "Running first Windows Update pass..." -Level INFO try { - $updates = Get-WindowsUpdate -AcceptAll -IgnoreReboot 2>&1 - if (-not $updates) { - Write-Log " No updates available - system is up to date" -Level OK + $result = Install-WindowsUpdate -AcceptAll -IgnoreReboot -Verbose 2>&1 + $installed = @($result | Where-Object { $_ -match 'KB\d+|Downloaded|Installed' }) + if ($installed.Count -gt 0) { + $result | Where-Object { "$_" -match '\S' } | ForEach-Object { Write-Log " $_" -Level INFO } + Write-Log " First pass complete - reboot required for remaining rounds" -Level OK } else { - $count = ($updates | Measure-Object).Count - Write-Log " Found $count update(s) - installing..." -Level INFO - - Install-WindowsUpdate -AcceptAll -IgnoreReboot -Verbose 2>&1 | ForEach-Object { - if ($_ -match '\S') { - Write-Log " $_" -Level INFO - } - } - - Write-Log " Windows Update complete ($count updates processed)" -Level OK + Write-Log " System already up to date" -Level OK } } catch { - Write-Log " Windows Update failed: $_" -Level ERROR - exit 1 + Write-Log " First pass failed: $_" -Level ERROR +} + +# ----------------------------------------------------------------------- +# 3. Scheduled task for post-reboot update rounds (self-deleting) +# ----------------------------------------------------------------------- +Write-Log "Registering post-reboot update task..." -Level INFO + +$taskName = "X9-WindowsUpdate" + +# PowerShell block that runs on each logon until no more updates found +$updateScript = @' +Import-Module PSWindowsUpdate -Force -ErrorAction Stop +$updates = Get-WindowsUpdate -AcceptAll -IgnoreReboot +if ($updates) { + Install-WindowsUpdate -AcceptAll -IgnoreReboot | Out-File "C:\Windows\Setup\Scripts\wu-pass-$(Get-Date -Format 'yyyyMMdd-HHmmss').log" -Encoding UTF8 +} else { + # No more updates - remove this task + Unregister-ScheduledTask -TaskName "X9-WindowsUpdate" -Confirm:$false +} +'@ + +try { + $action = New-ScheduledTaskAction -Execute "powershell.exe" ` + -Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -Command `"$updateScript`"" + $trigger = New-ScheduledTaskTrigger -AtLogOn + $settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Hours 2) ` + -MultipleInstances IgnoreNew + $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest + + # Remove existing task first (idempotent) + Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue + + Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger ` + -Settings $settings -Principal $principal -Force | Out-Null + + Write-Log " Task '$taskName' registered - runs on each logon until fully updated" -Level OK +} catch { + Write-Log " Failed to register scheduled task: $_" -Level WARN + Write-Log " Manual Windows Update rounds will be needed after reboot" -Level WARN } Write-Log "Step 12 - Windows Update complete" -Level OK +Write-Log " ACTION REQUIRED: Reboot the machine to complete remaining update rounds" -Level WARN