@@ -29,6 +29,21 @@ $script:Fail = 0
2929function Pass ([string ]$m ) { Write-Host " PASS: $m " ; $script :Pass ++ }
3030function Fail ([string ]$m ) { Write-Host " FAIL: $m " ; $script :Fail ++ }
3131
32+ function Invoke-CapturedPowerShell {
33+ param ([string []]$Arguments )
34+
35+ # Windows PowerShell 5.1 promotes a native child's stderr to an error record.
36+ # Rejection tests need to inspect that output and exit code without aborting.
37+ $previousErrorActionPreference = $ErrorActionPreference
38+ try {
39+ $ErrorActionPreference = " Continue"
40+ $output = & $PowerShellExecutable @Arguments 2>&1 | Out-String
41+ [PSCustomObject ]@ { Output = $output ; ExitCode = $LASTEXITCODE }
42+ } finally {
43+ $ErrorActionPreference = $previousErrorActionPreference
44+ }
45+ }
46+
3247function Get-UserPathRegistryState {
3348 $key = [Microsoft.Win32.Registry ]::CurrentUser.OpenSubKey(" Environment" , $false )
3449 if ($null -eq $key ) { throw " Cannot open the current user's Environment registry key." }
@@ -175,9 +190,10 @@ try {
175190 Write-Host " "
176191 Write-Host " === real install ==="
177192
178- $invalidOut = & $PowerShellExecutable - NoProfile - File $InstallPs1 `
179- - InstallDir (Join-Path $Work " invalid;path" ) - DryRun 2>&1 | Out-String
180- if ($LASTEXITCODE -ne 0 -and $invalidOut -match " cannot contain semicolons" ) {
193+ $invalidResult = Invoke-CapturedPowerShell - Arguments @ (
194+ " -NoProfile" , " -File" , $InstallPs1 ,
195+ " -InstallDir" , (Join-Path $Work " invalid;path" ), " -DryRun" )
196+ if ($invalidResult.ExitCode -ne 0 -and $invalidResult.Output -match " cannot contain semicolons" ) {
181197 Pass " PATH: unrepresentable Windows install directory rejected"
182198 } else {
183199 Fail " PATH: Windows install directory containing ';' was accepted"
@@ -269,11 +285,11 @@ try {
269285 $unsafePercentInstall = Join-Path $Work " %TEMP%\netclaw"
270286 Set-UserPathRegistryState $true $expandablePath ([Microsoft.Win32.RegistryValueKind ]::ExpandString)
271287 $expandableStateBefore = Get-UserPathRegistryState
272- $unsafePercentOut = & $PowerShellExecutable - NoProfile - File $InstallPs1 `
273- - InstallDir $unsafePercentInstall 2>&1 | Out-String
288+ $unsafePercentResult = Invoke-CapturedPowerShell - Arguments @ (
289+ " -NoProfile " , " -File " , $InstallPs1 , " -InstallDir " , $unsafePercentInstall )
274290 $expandableStateAfter = Get-UserPathRegistryState
275- if ($LASTEXITCODE -ne 0 `
276- -and $unsafePercentOut -match " cannot be safely added to an expandable User PATH" `
291+ if ($unsafePercentResult .ExitCode -ne 0 `
292+ -and $unsafePercentResult .Output -match " cannot be safely added to an expandable User PATH" `
277293 -and $expandableStateAfter.Value -eq $expandableStateBefore.Value `
278294 -and $expandableStateAfter.Kind -eq $expandableStateBefore.Kind ) {
279295 Pass " PATH: literal percent is rejected before mutating an expandable User PATH"
@@ -340,8 +356,10 @@ try {
340356 Assert-Resolves " -Version pin overrides -Channel" $BetaVersion @ (" -Channel" , " stable" , " -Version" , $BetaVersion )
341357
342358 # An unknown channel must be rejected by the ValidateSet, not silently default.
343- & $PowerShellExecutable - NoProfile - File $InstallPs1 - InstallDir $shouldNotExist - DryRun - Channel bogus 2>&1 | Out-Null
344- if ($LASTEXITCODE -ne 0 ) {
359+ $invalidChannelResult = Invoke-CapturedPowerShell - Arguments @ (
360+ " -NoProfile" , " -File" , $InstallPs1 , " -InstallDir" , $shouldNotExist ,
361+ " -DryRun" , " -Channel" , " bogus" )
362+ if ($invalidChannelResult.ExitCode -ne 0 ) {
345363 Pass " channel: unknown value rejected"
346364 } else {
347365 Fail " channel: unknown value should fail (exit=$LASTEXITCODE )"
@@ -426,8 +444,6 @@ if ($script:Fail -gt 0) {
426444 exit 1
427445}
428446Write-Host " install smoke (ps1): PASSED"
429- # Exit explicitly on the result, not on $LASTEXITCODE — the channel checks above run
430- # a child PowerShell `-Channel bogus` invocation (which exits non-zero by design),
431- # and without this the script
432- # would fall off the end and inherit that non-zero code despite all assertions passing.
447+ # Deliberate rejection checks run child processes that exit nonzero, so return
448+ # the assertion result explicitly instead of inheriting a child's exit code.
433449exit 0
0 commit comments