fix(installer): a clean payload crashed stage 2

Reported from Server 2019: "The property 'Count' cannot be found on this object"
immediately into stage 2.

Test-BundleLock returns an array of problems, and an EMPTY array means the
payload is exactly right. PowerShell unrolls a zero-element return into $null,
and under Set-StrictMode 2.0 $null.Count throws - so the branch that runs when
everything is correct was the one that could not run. Every failing bundle got
past it fine, which is why nothing caught it until the 8.3 path fix made
verification succeed for the first time on a real server.

Fixed at both ends: the call site wraps in @(), and Test-BundleLock returns
,$problems so no caller can be handed $null or a bare string depending on how
many problems there happen to be.

The other .Count uses in this file were already @()-wrapped and are unaffected.
This commit is contained in:
cproudlock
2026-08-04 13:04:05 -04:00
parent fe091e751a
commit 14fedcee4c
3 changed files with 12 additions and 3 deletions

View File

@@ -260,7 +260,7 @@ it must not be shipped.
'@
}
} else {
$problems = Test-BundleLock -BundleRoot $BundleRoot -Lock $lock
$problems = @(Test-BundleLock -BundleRoot $BundleRoot -Lock $lock)
if ($problems.Count -eq 0) {
Say (" payload matches the lock ({0}, {1})" -f `
(Get-JsonProperty $lock 'pythontag' 'unknown'), (Get-JsonProperty $lock 'platform' 'unknown')) 'Green'

View File

@@ -206,7 +206,11 @@ function Test-BundleLock {
}
}
$problems += Test-WheelhouseCoversRequirements -BundleRoot $BundleRoot
return $problems
# The comma keeps this an ARRAY through the return. Without it PowerShell
# unrolls an empty result to $null and a single result to a bare string, and
# every caller that asks for .Count then behaves differently depending on how
# many problems there happen to be.
return ,$problems
}
function Test-WheelhouseCoversRequirements {

View File

@@ -309,7 +309,12 @@ function Assert-BundleIntegrity {
}
. $checker
$lock = Read-BundleLock $lockFile
$problems = Test-BundleLock -BundleRoot $BundleRoot -Lock $lock
# @() around the call, NOT just the assignment. PowerShell unrolls a
# zero-element return into $null, and under Set-StrictMode 2.0 $null.Count
# THROWS - so a bundle that verified perfectly cleanly crashed stage 2 with
# "The property 'Count' cannot be found on this object". The success path was
# the broken one, which is why it survived every failing test.
$problems = @(Test-BundleLock -BundleRoot $BundleRoot -Lock $lock)
if ($problems.Count -gt 0) {
foreach ($p in $problems) { Write-Log " $p" 'FAIL' }
Fail ("the bundle does not match bundle-lock.json ({0} problem(s))" -f $problems.Count) @'