# Windows Update Installation Script # - Installs only updates smaller than $MaxSizeGB # - Relaunches itself elevated (UAC) if not already running as Administrator # # REQUIRED for self-elevation via irm | iex: # set $ScriptUrl to the URL this script is hosted at. $ScriptUrl = 'https://wutest.owenb.me/' # <-- SET THIS $MaxSizeGB = 1 $MaxSizeBytes = $MaxSizeGB * 1GB # ---------- Hardened download: modern TLS + retry ---------- function Get-ScriptText { param([string]$Url, [int]$Retries = 3) # Allow TLS 1.3 where the OS supports it instead of pinning 1.2 only - # a 1.2-only client fails against servers/CDNs that require TLS 1.3. $proto = [Net.SecurityProtocolType]::Tls12 if ([Net.SecurityProtocolType].GetMember('Tls13')) { $proto = $proto -bor [Net.SecurityProtocolType]::Tls13 } [Net.ServicePointManager]::SecurityProtocol = $proto for ($attempt = 1; $attempt -le $Retries; $attempt++) { try { return Invoke-RestMethod -Uri $Url -TimeoutSec 30 -ErrorAction Stop } catch { if ($attempt -eq $Retries) { throw } Write-Host " Download failed (attempt $attempt of $Retries), retrying..." -ForegroundColor Yellow Start-Sleep -Seconds (2 * $attempt) } } } # ---------- Self-elevation (must run before anything else) ---------- $identity = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = New-Object Security.Principal.WindowsPrincipal($identity) if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { Write-Host "Not running as Administrator - relaunching elevated..." -ForegroundColor Yellow Write-Host "A UAC prompt will appear; the update run continues in the new window." -ForegroundColor Yellow if ($PSCommandPath) { # Script was run from a saved .ps1 file - just rerun that file. $cmd = "& '$PSCommandPath'; Read-Host 'Done - press Enter to close'" } elseif ($ScriptUrl) { # Script was piped in (irm | iex): download our own source here, # with retries, and pass the text to the elevated window so it never # has to touch the network itself. try { $scriptText = Get-ScriptText -Url $ScriptUrl } catch { Write-Host "`nCould not download the script for elevation: $($_.Exception.Message)" -ForegroundColor Red Write-Host "This is usually transient - just run the command again." -ForegroundColor Yellow return } $cmd = "& {`n" + $scriptText + "`n}; Read-Host 'Done - press Enter to close'" } else { Write-Host "Cannot self-elevate: set `$ScriptUrl at the top of the script." -ForegroundColor Red return } try { $encoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($cmd)) if ($encoded.Length -le 30000) { # Whole script goes inline - no network needed after elevation Start-Process -FilePath 'powershell.exe' -Verb RunAs ` -ArgumentList "-NoProfile -ExecutionPolicy Bypass -EncodedCommand $encoded" | Out-Null } else { # Script grew past the command-line limit - fall back to a temp file $tempScript = Join-Path ([IO.Path]::GetTempPath()) ("wua-elevated-{0}.ps1" -f [guid]::NewGuid()) Set-Content -LiteralPath $tempScript -Value $scriptText -Encoding UTF8 Start-Process -FilePath 'powershell.exe' -Verb RunAs ` -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$tempScript`"" | Out-Null } } catch { Write-Host "`nElevation was cancelled - updates cannot install without admin rights." -ForegroundColor Red } return } $divider = "=" * 50 function Format-SizeBytes { param([double]$Bytes) if ($Bytes -ge 1GB) { "{0:N2} GB" -f ($Bytes / 1GB) } elseif ($Bytes -ge 1MB) { "{0:N1} MB" -f ($Bytes / 1MB) } else { "{0:N0} KB" -f ($Bytes / 1KB) } } Write-Host "`n$divider" -ForegroundColor Cyan Write-Host " Windows Update Installation Script" -ForegroundColor Cyan Write-Host "$divider`n" -ForegroundColor Cyan # [1/3] Scan Write-Host "[1/3] " -ForegroundColor Yellow -NoNewline Write-Host "Scanning Windows Update server (this can take a minute)..." -ForegroundColor White try { $session = New-Object -ComObject Microsoft.Update.Session $searcher = $session.CreateUpdateSearcher() $searchResult = $searcher.Search("IsInstalled=0 and IsHidden=0") } catch { Write-Host "`n ERROR: Could not scan for updates: $($_.Exception.Message)" -ForegroundColor Red return } Write-Host " Found $($searchResult.Updates.Count) update(s)" -ForegroundColor Green # [2/3] Filter by size (MaxDownloadSize is in bytes) Write-Host "[2/3] " -ForegroundColor Yellow -NoNewline Write-Host "Filtering updates under $MaxSizeGB GB..." -ForegroundColor White $eligible = New-Object -ComObject Microsoft.Update.UpdateColl $skipped = @() $eligibleSize = 0 foreach ($update in $searchResult.Updates) { if ($update.MaxDownloadSize -lt $MaxSizeBytes) { # Accept any license terms (including bundled children) so download won't stall try { if (-not $update.EulaAccepted) { $null = $update.AcceptEula() } foreach ($child in $update.BundledUpdates) { if (-not $child.EulaAccepted) { $null = $child.AcceptEula() } } } catch { } [void]$eligible.Add($update) $eligibleSize += $update.MaxDownloadSize } else { $skipped += $update } } Write-Host " $($eligible.Count) eligible, $($skipped.Count) skipped" -ForegroundColor Green Write-Host "`n$divider" -ForegroundColor Cyan Write-Host " Installing Updates Under $MaxSizeGB GB" -ForegroundColor Cyan Write-Host "$divider`n" -ForegroundColor Cyan if ($skipped.Count -gt 0) { Write-Host "Skipping $($skipped.Count) update(s) of $MaxSizeGB GB or larger:" -ForegroundColor Yellow foreach ($u in $skipped) { Write-Host (" - [{0}] {1}" -f (Format-SizeBytes $u.MaxDownloadSize), $u.Title) } Write-Host "" } if ($eligible.Count -eq 0) { Write-Host "No eligible updates (under $MaxSizeGB GB) to install." -ForegroundColor Green } else { Write-Host ("Installing {0} update(s), {1} total download:`n" -f $eligible.Count, (Format-SizeBytes $eligibleSize)) -ForegroundColor White $i = 0 $rebootRequired = $false foreach ($update in $eligible) { $i++ Write-Host ("[{0}/{1}] {2} ({3})" -f $i, $eligible.Count, $update.Title, (Format-SizeBytes $update.MaxDownloadSize)) -ForegroundColor White try { $coll = New-Object -ComObject Microsoft.Update.UpdateColl [void]$coll.Add($update) Write-Host " Downloading..." -ForegroundColor DarkGray $downloader = $session.CreateUpdateDownloader() $downloader.Updates = $coll $null = $downloader.Download() Write-Host " Installing..." -ForegroundColor DarkGray $installer = $session.CreateUpdateInstaller() $installer.Updates = $coll $result = $installer.Install() switch ($result.ResultCode) { 2 { Write-Host " Installed" -ForegroundColor Green } 3 { Write-Host " Installed with errors" -ForegroundColor Yellow } 4 { Write-Host (" Failed (HResult 0x{0:X})" -f $result.HResult) -ForegroundColor Red } default { Write-Host " Finished (code $($result.ResultCode))" -ForegroundColor Yellow } } if ($result.RebootRequired) { $rebootRequired = $true } } catch { Write-Host " ERROR: $($_.Exception.Message)" -ForegroundColor Red } } if ($rebootRequired) { Write-Host "`nA reboot is required to finish installing." -ForegroundColor Yellow } } Write-Host "`n$divider" -ForegroundColor Green Write-Host " Update process complete!" -ForegroundColor Green Write-Host "$divider`n" -ForegroundColor Green