Skip to content

Consider making in-session exit-code reporting for scripts via via $LASTEXITCODE consistent with the CLI #11712

@mklement0

Description

@mklement0

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 an exit <n> statement (where <n> is an integer).

  • 0 otherwise (unless an unhandled script-terminating error occurs, in which case it is 1).

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; $LASTEXITCODE

Inside 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 $dir

Inside 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: If exit isn't used or used without argument, set $LASTEXITCODE to 0; 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Issue-Enhancementthe issue is more of a feature request than a bugResolution-No ActivityIssue has had no activity for 6 months or moreWG-Enginecore PowerShell engine, interpreter, and runtime

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions