fix(installer): stop PS 5.1 stderr wrapping from aborting version probes

Distilled from #1255 by @MasterFede5, rebased onto the single-binary
installer and completed with the two review conditions.

PowerShell 5.1 wraps redirected native stderr into ErrorRecords, so under
the script's global ErrorActionPreference=Stop a HEALTHY binary that
prints a single warning to stderr while exiting 0 aborts both
`--version 2>&1` probes with "downloaded binary failed to run". Both
probes now relax EAP to Continue for the probe call only, with an
exception-safe restore; failure detection stays on $LASTEXITCODE.

The review-required pre-seed ($global:LASTEXITCODE = 1) closes the window
the relaxation would otherwise open: a binary that fails to START under
Continue may leave $LASTEXITCODE untouched, and the installed-binary probe
runs after earlier successful native calls, so a stale 0 would have turned
a corrupt install into "Installed: ...". Seeding 1 makes start-failure
read as failure on both probes.

The bundle contract pins the guard's two load-bearing parts (the scoped
relaxation with restore, and the pre-seed), verified failing when either
is stripped. The change adds zero non-ASCII bytes; the file remains pure
ASCII per the PS 5.1 ANSI-decode rule.

Co-authored-by: MasterFede5 <friedrickms@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
Martin VogelandMasterFede5 committed 2026-08-28 10:11:56 +02:00
1 parent 4149c5044c
commit 3f92699729
2 files changed
+38 -2

No files matched your search

+25 -2
View File
@@ -281,8 +281,21 @@ if ($binaryItem.Attributes -band [System.IO.FileAttributes]::ReparsePoint) {
}
# Prove the downloaded binary runs before touching an existing installation.
# PS 5.1 wraps redirected native stderr into ErrorRecords, so under the global
# ErrorActionPreference=Stop a HEALTHY binary that prints one warning while
# exiting 0 becomes a terminating error here. Relax to Continue for the probe
# only; failure detection stays on $LASTEXITCODE, and the pre-seed guarantees
# a binary that fails to START (stale $LASTEXITCODE from an earlier native
# call) can never read as success.
try {
$candidateVersion = & $DownloadedBinary --version 2>&1
$ProbeEap = $ErrorActionPreference
try {
$ErrorActionPreference = "Continue"
$global:LASTEXITCODE = 1
$candidateVersion = & $DownloadedBinary --version 2>&1
} finally {
$ErrorActionPreference = $ProbeEap
}
if ($LASTEXITCODE -ne 0) { throw "candidate exited with $LASTEXITCODE" }
Write-Host "Verified candidate: $candidateVersion"
} catch {
@@ -349,8 +362,18 @@ if (Test-Path -LiteralPath $DownloadedInstaller -PathType Leaf) {
}
# Verify
# Same PS 5.1 stderr-wrapping guard as the candidate probe above; the pre-seed
# matters MOST here, because prior successful native calls leave a stale
# $LASTEXITCODE=0 that a start-failure would otherwise inherit.
try {
$ver = & $Dest --version 2>&1
$ProbeEap = $ErrorActionPreference
try {
$ErrorActionPreference = "Continue"
$global:LASTEXITCODE = 1
$ver = & $Dest --version 2>&1
} finally {
$ErrorActionPreference = $ProbeEap
}
if ($LASTEXITCODE -ne 0) { throw "installed binary exited with $LASTEXITCODE" }
Write-Host "Installed: $ver"
} catch {
+13
View File
@@ -229,6 +229,19 @@ require(
"install.ps1 must verify and install through the downloaded binary",
)
# PS 5.1 wraps redirected native stderr into ErrorRecords; under the global
# ErrorActionPreference=Stop a healthy binary that warns on stderr aborts the
# version probes (#1255). Pin the guard's two load-bearing parts so neither
# can be silently dropped: the probe-scoped relaxation with an exception-safe
# restore, and the $LASTEXITCODE pre-seed that keeps a start-failure from
# inheriting a stale 0 and reading as success.
require(
installer.count('$ErrorActionPreference = "Continue"') == 2
and installer.count("$global:LASTEXITCODE = 1") == 2
and installer.count("$ErrorActionPreference = $ProbeEap") == 2,
"install.ps1 version probes must relax EAP with restore and pre-seed LASTEXITCODE (#1255)",
)
# ── 4. Package-manager shims resolve the single Windows binary ───────────────
single_binary_contracts = {
"pkg/npm/install.js": (