BekaertDeslee-InstallWU.ps1
|
<#PSScriptInfo
.VERSION 3.0 .GUID 8b665e36-b497-4349-bc08-7e41a72ee3b0 .AUTHOR admin.descampd .COMPANYNAME .COPYRIGHT .TAGS WindowsUpdate WSUS PSWindowsUpdate .LICENSEURI .PROJECTURI .ICONURI .EXTERNALMODULEDEPENDENCIES PSWindowsUpdate .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES .PRIVATEDATA #> <# .DESCRIPTION Bekaert Deslee Windows Update installer. Scans WSUS/WU for available updates, lets you choose or exclude specific items, installs each one by unique UpdateID to prevent unwanted co-installs, and handles pending-reboot state cleanly. #> #Requires -Version 5.1 #Requires -RunAsAdministrator [CmdletBinding()] param( [switch]$AcceptAll ) #region Module Check if (-not (Get-Module -ListAvailable -Name PSWindowsUpdate)) { Write-Host "`n[!] PSWindowsUpdate module not found." -ForegroundColor Yellow $install = Read-Host "Install it now from PSGallery? (Y/N)" if ($install -match '^[Yy]') { Write-Host "Installing PSWindowsUpdate..." -ForegroundColor Cyan try { Install-Module -Name PSWindowsUpdate -Scope AllUsers -Force -ErrorAction Stop Write-Host "Module installed successfully.`n" -ForegroundColor Green } catch { Write-Error "Failed to install PSWindowsUpdate: $_" return } } else { Write-Host "Cannot continue without PSWindowsUpdate." -ForegroundColor Red return } } Import-Module PSWindowsUpdate -ErrorAction Stop #endregion #region Get Updates Write-Host "`n========================================" -ForegroundColor Cyan Write-Host " Windows Update Scanner" -ForegroundColor Cyan Write-Host "========================================`n" -ForegroundColor Cyan Write-Host "Scanning for available updates..." -ForegroundColor Yellow try { $updates = Get-WUList -ErrorAction Stop } catch [System.NullReferenceException] { Write-Host "`n[!] Cannot scan for updates - a reboot is pending from a previous installation." -ForegroundColor Yellow Write-Host " Please restart and re-run this script." -ForegroundColor Yellow return } catch { Write-Error "Failed to retrieve update list: $_" return } if (-not $updates -or $updates.Count -eq 0) { Write-Host "`n[OK] No updates available. System is up to date." -ForegroundColor Green return } #endregion #region Display Updates Write-Host "`nFound $($updates.Count) update(s):`n" -ForegroundColor Green Write-Host ("{0,-4} {1,-12} {2,-10} {3}" -f "#", "KB Article", "Size", "Title") -ForegroundColor Cyan Write-Host ("-" * 80) -ForegroundColor DarkGray for ($i = 0; $i -lt $updates.Count; $i++) { $u = $updates[$i] $kb = if ($u.KBArticleIDs) { "KB$($u.KBArticleIDs -join ',')" } else { "N/A" } $sizeMB = if ($u.MaxDownloadSize) { "$([math]::Round($u.MaxDownloadSize / 1MB, 1)) MB" } else { "?" } Write-Host ("{0,-4} {1,-12} {2,-10} {3}" -f "[$($i+1)]", $kb, $sizeMB, $u.Title) -ForegroundColor White } Write-Host ("-" * 80) -ForegroundColor DarkGray #endregion #region Selection $toInstall = [System.Collections.Generic.List[object]]::new() $proceed = $true if ($AcceptAll) { $toInstall.AddRange($updates) Write-Host "`n-AcceptAll specified. All $($updates.Count) updates queued." -ForegroundColor Yellow } else { Write-Host "`nOptions:" -ForegroundColor Cyan Write-Host " [A] Install ALL updates" Write-Host " [S] Select updates manually" Write-Host " [Q] Quit`n" do { $choice = Read-Host "Choose" } until ($choice -match '^[AaSsQq]$') switch ($choice.ToUpper()) { 'Q' { Write-Host "Aborted." -ForegroundColor Yellow $proceed = $false } 'A' { Write-Host "`nEnter numbers to EXCLUDE (comma-separated), or press Enter to keep all:" -ForegroundColor Yellow $excludeInput = Read-Host "Exclude" $excludeIndices = @() if ($excludeInput.Trim() -ne '') { $excludeIndices = $excludeInput -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ -match '^\d+$' } | ForEach-Object { [int]$_ } } for ($i = 0; $i -lt $updates.Count; $i++) { if (($i + 1) -notin $excludeIndices) { $toInstall.Add($updates[$i]) } } Write-Host "`nUpdates queued for installation:" -ForegroundColor Cyan Write-Host ("-" * 80) -ForegroundColor DarkGray foreach ($u in $toInstall) { $kb = if ($u.KBArticleIDs) { "KB$($u.KBArticleIDs -join ',')" } else { "N/A" } Write-Host " [+] $kb - $($u.Title)" -ForegroundColor White } if ($excludeIndices.Count -gt 0) { Write-Host "`nExcluded:" -ForegroundColor DarkGray foreach ($idx in $excludeIndices) { if ($idx -ge 1 -and $idx -le $updates.Count) { $u = $updates[$idx - 1] $kb = if ($u.KBArticleIDs) { "KB$($u.KBArticleIDs -join ',')" } else { "N/A" } Write-Host " [-] $kb - $($u.Title)" -ForegroundColor DarkGray } } } Write-Host ("-" * 80) -ForegroundColor DarkGray do { $confirm = Read-Host "`nProceed with installing $($toInstall.Count) update(s)? (Y/N)" } until ($confirm -match '^[YyNn]$') if ($confirm -match '^[Nn]') { Write-Host "Aborted." -ForegroundColor Yellow $proceed = $false } } 'S' { Write-Host "`nFor each update, press Y to queue it or N to skip it.`n" -ForegroundColor Cyan for ($i = 0; $i -lt $updates.Count; $i++) { $u = $updates[$i] $kb = if ($u.KBArticleIDs) { "KB$($u.KBArticleIDs -join ',')" } else { "N/A" } Write-Host "[$($i+1)/$($updates.Count)] $kb - $($u.Title)" -ForegroundColor White do { $ans = Read-Host " Install? (Y/N)" } until ($ans -match '^[YyNn]$') if ($ans -match '^[Yy]') { $toInstall.Add($u) Write-Host " -> Queued." -ForegroundColor Green } else { Write-Host " -> Skipped." -ForegroundColor DarkGray } } } } } #endregion #region Install if ($proceed -and $toInstall.Count -eq 0) { Write-Host "`nNo updates selected." -ForegroundColor Yellow $proceed = $false } if ($proceed) { Write-Host "`n========================================" -ForegroundColor Cyan Write-Host " Installing $($toInstall.Count) update(s)" -ForegroundColor Cyan Write-Host "========================================`n" -ForegroundColor Cyan $index = 0 $succeeded = [System.Collections.Generic.List[string]]::new() $failed = [System.Collections.Generic.List[string]]::new() foreach ($update in $toInstall) { $index++ $kb = if ($update.KBArticleIDs) { "KB$($update.KBArticleIDs -join ',')" } else { "N/A" } $title = $update.Title Write-Host "[$index/$($toInstall.Count)] Installing: $title" -ForegroundColor Cyan Write-Host " KB: $kb" -ForegroundColor DarkGray Write-Host " Started: $(Get-Date -Format 'HH:mm:ss')" -ForegroundColor DarkGray try { Install-WindowsUpdate -UpdateID $update.Identity.UpdateID ` -AcceptAll ` -IgnoreReboot ` -Verbose ` -ErrorAction Stop Write-Host " [OK] Completed successfully at $(Get-Date -Format 'HH:mm:ss')" -ForegroundColor Green $succeeded.Add("$kb - $title") } catch { Write-Host " [FAIL] Error: $_" -ForegroundColor Red $failed.Add("$kb - $title") } Write-Host "" } #endregion #region Summary Write-Host "========================================" -ForegroundColor Cyan Write-Host " Installation Summary" -ForegroundColor Cyan Write-Host "========================================" -ForegroundColor Cyan if ($succeeded.Count -gt 0) { Write-Host "`nSucceeded ($($succeeded.Count)):" -ForegroundColor Green $succeeded | ForEach-Object { Write-Host " [+] $_" -ForegroundColor Green } } if ($failed.Count -gt 0) { Write-Host "`nFailed ($($failed.Count)):" -ForegroundColor Red $failed | ForEach-Object { Write-Host " [-] $_" -ForegroundColor Red } } $rebootRequired = (Get-WURebootStatus -Silent -ErrorAction SilentlyContinue) -eq $true if ($rebootRequired) { Write-Host "`n[!] A reboot is required to complete installation. Please restart manually." -ForegroundColor Yellow } else { Write-Host "`n[OK] No reboot required." -ForegroundColor Green } if (-not $rebootRequired) { Write-Host "`n----------------------------------------" -ForegroundColor DarkGray Write-Host "Checking for remaining pending updates..." -ForegroundColor Yellow $pending = Get-WUList -ErrorAction SilentlyContinue if (-not $pending -or $pending.Count -eq 0) { Write-Host "[OK] No pending updates. System is fully up to date." -ForegroundColor Green } else { Write-Host "[!] $($pending.Count) update(s) still pending:" -ForegroundColor Yellow Write-Host ("{0,-4} {1,-12} {2,-10} {3}" -f "#", "KB Article", "Size", "Title") -ForegroundColor Cyan Write-Host ("-" * 80) -ForegroundColor DarkGray for ($i = 0; $i -lt $pending.Count; $i++) { $u = $pending[$i] $kb = if ($u.KBArticleIDs) { "KB$($u.KBArticleIDs -join ',')" } else { "N/A" } $sizeMB = if ($u.MaxDownloadSize) { "$([math]::Round($u.MaxDownloadSize / 1MB, 1)) MB" } else { "?" } Write-Host ("{0,-4} {1,-12} {2,-10} {3}" -f "[$($i+1)]", $kb, $sizeMB, $u.Title) -ForegroundColor White } Write-Host ("-" * 80) -ForegroundColor DarkGray Write-Host "Re-run this script to install the remaining updates." -ForegroundColor Yellow } } #endregion } |