-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Description
This has been asked for before, but I couldn't find the original issue.
When the CLI is called with -File and a *.ps1 script, the PowerShell process' own exit code is set as follows:
-
Whatever
<n>is, if the script exits via anexit <n>statement (where<n>is an integer). -
0otherwise (unless an unhandled script-terminating error occurs, in which case it is1).
In other words: Via the CLI, reporting a nonzero exit code from a script requires an explicit exit statement - unlike in POSIX-like shells, where it is derived from the (implied) exit code of the last statement executed in the shell.
As an aside: with the -Command CLI option, it is the last statement that determines the exit code, except that the specific exit code reported by a script / external program is unexpectedly lost: essentially, the abstract Boolean $? value is mapped onto 0 or 1 (any failure): see #13501.
Example of the CLI's exit-code reporting with -File:
$dir = (New-Item -Force -Type Directory temp:/$PID).FullName
@'
ls /nosuch 2>$null
'@ > $dir/t1.ps1
@'
ls /nosuch
exit 5
'@ > $dir/t2.ps1
# -> 0, because t.ps1 did not exit via an `exit` statement,
# even though the `ls` command reported a nonzero exit code as the last statement.
pwsh -file $dir/t1.ps1; $LASTEXITCODE
# -> 5, due to explicit `exit`
pwsh -file $dir/t2.ps1; $LASTEXITCODEInside a PowerShell session, where $LASTEXITCODE must be queried for exit codes, the logic works differently for scripts:
# -> 1 (!), because it is the `ls` utility's exit code that is now reported in $LASTEXITCODE
# However, $? correctly indicates $true, and therefore && and || would behave as intended.
& $dir/t1.ps1 ; $LASTEXITCODE
# Cleanup
Remove-Item -Recurse $dirInside PowerShell, $LASTEXITCODE is always set for calls to external programs - even inside a script - and only overridden if the script explicitly terminates with exit. (If no number is passed to exit, 0 is implied). In other words: Inside PowerShell, if a script doesn't terminate with exit, $LASTEXITCODE (and therefore $?) may be set to an incidental value from a script-internal external-program call or - if neither an eternal-program call is made nor exit $n is called - a preexisting, unrelated $LASTEXITCODE may linger.
While exit codes are a process-level concept (and mostly used to communicate with outside callers), it would still be helpful if a *.ps1 consistently reflected its potentially intentional exit code to the caller - whether via the CLI or PowerShell-internally - even though the inconsistent case is the one where the script doesn't use exit.
Specifically, the suggested change is:
- Make every script-file invocation set
$LASTEXITCODE, based on the effective script-as-a-whole exit code that would be seen via the CLI: Ifexitisn't used or used without argument, set$LASTEXITCODEto0; otherwise (as before), reflect the argument in$LASTEXITCODE.
This is technically a breaking change:
A $LASTEXITCODE value resulting from an external-program call would then no longer be available across script boundaries - you could only check it in the same script scope (before you call another script or external program there).
However, it arguably makes sense to only check $LASTEXITCODE right away anyway, and it's arguably better to conceive of an exit code reported inside a script by an external program as an implementation detail of that script.