Harden Get-Win11CumulativeUpdates.ps1 (locale, signatures, timeouts)#3
Conversation
Surgical fixes from a holistic review of the CU scraper (the one catalog script intentionally not folded into the new shared engine, since it has CU-specific orchestration: warn-open signatures, multi-.msu checkpoint chain, KB-based target id, SHA256 cache). - Locale date bug that could silently drop the newest CU: [datetime]::Parse is CurrentCulture-bound, so on a non-US host a CU dated after the 12th throws inside the -Parallel try/catch (which only warns) and is dropped from results. Use ParseExact + InvariantCulture (proven: under de-DE the old code drops 6/15/2026 and misreads 6/9/2026 as Sep 6). - Authenticode trust gap: the SHA256-sidecar cache-hit reused a file without re-running Get-AuthenticodeSignature (the sidecar is self-generated, so it proves byte-stability, not Microsoft authenticity). Re-check on cache reuse. - Add per-request -TimeoutSec (60s metadata, 3600s for the large .msu) + UA so a stalled connection can't hang the run. - Anchor the KB target-id match so a shorter KB can't prefix-match a longer one. - Remove dead $downloadedCount; correct the now-inaccurate "checkpoint last" comment (checkpoint position varies: first for 25H2, last for 24H2). Checkpoint-chain logic and CU-query selection verified correct live (target + KB5043080 checkpoint returned by DownloadDialog; newest CU surfaced in the top-25 by the catalog's date-descending default sort). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request improves the robustness and reliability of the Get-Win11CumulativeUpdates.ps1 script by introducing bounded timeouts, a custom user agent, locale-independent date parsing, refined KB matching, and Authenticode signature verification for cached files. The review feedback suggests guarding the Windows-only Get-AuthenticodeSignature cmdlet with $IsWindows to ensure cross-platform compatibility, suppressing the Invoke-WebRequest progress bar to improve download speeds, and expanding the date parsing formats to align with the shared engine.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| $cachedSig = Get-AuthenticodeSignature -FilePath $OutFile | ||
| if ($cachedSig.Status -ne 'Valid') { | ||
| Write-Warning " [!] Authenticode signature for cached $FileName is '$($cachedSig.Status)' (not Valid) — treat this package as UNVERIFIED before servicing." | ||
| } |
There was a problem hiding this comment.
Get-AuthenticodeSignature is a Windows-only cmdlet and is not available in PowerShell Core on Linux or macOS. Since this script is used in a LAN iPXE environment (as indicated by the user agent lan-ipxe-catalog-scrape), it is highly likely to be run on Linux-based pre-staging or PXE servers.
To prevent the script from throwing a CommandNotFoundException and failing on non-Windows platforms, guard the Get-AuthenticodeSignature calls with the $IsWindows automatic variable.
Note: You should also apply this guard to the fresh-download path's signature check (around line 235), which is outside this diff hunk.
if ($IsWindows) {
$cachedSig = Get-AuthenticodeSignature -FilePath $OutFile
if ($cachedSig.Status -ne 'Valid') {
Write-Warning " [!] Authenticode signature for cached $FileName is '$($cachedSig.Status)' (not Valid) — treat this package as UNVERIFIED before servicing."
}
}
| # Bounded timeout + browser UA so a stalled catalog connection can't hang the run | ||
| # (callers can override TimeoutSec, e.g. the large .msu download). | ||
| if (-not $Params.ContainsKey('TimeoutSec')) { $Params['TimeoutSec'] = 60 } | ||
| if (-not $Params.ContainsKey('UserAgent')) { $Params['UserAgent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) lan-ipxe-catalog-scrape' } |
There was a problem hiding this comment.
PowerShell's Invoke-WebRequest renders a progress bar by default, which can severely degrade download performance (often by 10x or more) and cause high CPU usage, especially when downloading large files like the cumulative update .msu packages (which can be 1.5 GB or more).
Setting $ProgressPreference = 'SilentlyContinue' locally within Invoke-CatalogRequest will suppress the progress bar and significantly speed up the downloads without polluting the caller's scope.
# Bounded timeout + browser UA so a stalled catalog connection can't hang the run
# (callers can override TimeoutSec, e.g. the large .msu download).
$ProgressPreference = 'SilentlyContinue'
if (-not $Params.ContainsKey('TimeoutSec')) { $Params['TimeoutSec'] = 60 }
if (-not $Params.ContainsKey('UserAgent')) { $Params['UserAgent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) lan-ipxe-catalog-scrape' }
| # Locale-independent parse: the catalog serves US M/d/yyyy. Plain [datetime]::Parse | ||
| # is CurrentCulture-bound, so on a non-US host a day>12 throws (caught below -> the | ||
| # CU is silently dropped, possibly the newest) and day<=12 silently swaps day/month. | ||
| $DateObj = [datetime]::ParseExact($DateString, [string[]]@('M/d/yyyy', 'MM/dd/yyyy'), [System.Globalization.CultureInfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::None) |
There was a problem hiding this comment.
To ensure robustness and consistency with the shared engine (CatalogScrape.psm1), consider including the same set of date formats when parsing. The shared engine uses:
@('M/d/yyyy', 'MM/dd/yyyy', 'yyyy-MM-dd', 'M/d/yyyy h:mm:ss tt')
This prevents potential parsing failures if the catalog format ever changes or includes a time component.
$DateObj = [datetime]::ParseExact($DateString, [string[]]@('M/d/yyyy', 'MM/dd/yyyy', 'yyyy-MM-dd', 'M/d/yyyy h:mm:ss tt'), [System.Globalization.CultureInfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::None)
Summary
Holistic review + surgical hardening of
Get-Win11CumulativeUpdates.ps1— the one catalog scraper deliberately kept out of the shared engine (it has CU-specific orchestration: warn-open signatures, multi-.msucheckpoint chain, KB-based target id, SHA256 cache).The core machinery was verified correct against the live catalog: the 25H2 CU query selects the true newest CU, and its DownloadDialog returns the full checkpoint chain (target +
KB5043080). These are hardening fixes, not a rewrite.Fixes
[datetime]::ParseisCurrentCulture-bound; on a non-US host a CU dated after the 12th throws inside the-Paralleltry/catch and is silently dropped (possibly the newest one). →ParseExact+InvariantCulture. Proven underde-DE: the old code throws on6/15/2026(dropped) and misreads6/9/2026as Sep 6.Get-AuthenticodeSignature(the sidecar is self-generated → byte-stability, not authenticity). Now re-verified on cache reuse.-TimeoutSecon every request (60s metadata, 3600s for the large.msu) + a browser UA.$downloadedCount; corrected the inaccurate "checkpoint last" comment (position varies: first for 25H2, last for 24H2).Intentionally not changed
CatalogScrape.psm1(timeouts/ConvertTo-CsDate/parse helpers) is a reasonable future cleanup but kept out of this surgical PR.Validation
pwshsyntax-clean; engine unit tests still 68/68; the locale fix demonstrated live under a non-US culture.🤖 Generated with Claude Code