This self-answered question tries to address two distinct aspects of dealing with process exit codes in PowerShell:
In PowerShell code, how can you query th
Current as of PowerShell [Core] 7.1.0-preview.6.
PowerShell-internally, where native PowerShell commands generally run in-process, exit codes from child processes that run external programs play a very limited role:
Native PowerShell commands generally don't set exit codes and don't act on them.
PowerShell has an abstract counterpart to exit codes: the automatic, Boolean success-status variable $?:
It reflects whether the most recently executed command had any errors, but in practice it is rarely used, not least because - up to version 6.x - something as seemingly inconsequential as enclosing a command in (...)
resets $?
to $true
- see this GitHub issue - and because using Write-Error
in user functions doesn't set $?
to $false
- see this GitHub issue; however, eventually providing the ability for user code to set $? explicitly has been green-lit for a future version.
While $?
also reflects (immediately afterwards) whether an external program reported an exit code of 0
(signaling success, making $?
report $true
) or a nonzero exit code (typically signaling failure, making $?
$false
), it is the automatic $LASTEXICODE
variable that contains the specific exit code as an integer, and that value is retained until another external program, if any, is called in the same session.
$?
can report false negatives if the external program reports exit code 0
while also producing stderr output and there is also a PowerShell redirection involving 2>
or *>
- see this answer and this GitHub issue; as of PowerShell 7.1.0-preview.6; the corrected behavior is a available as experimental feature PSNotApplyErrorActionToStderr
.Unlike terminating errors or non-terminating errors reported by PowerShell-native commands, nonzero exit codes from external programs can not be automatically acted upon by the $ErrorActionPreference
preference variable; that is, you cannot use that variable to silence stderr output from external programs nor can you, more importantly, choose to abort a script via value 'Stop'
when an external program reports a nonzero exit code.
Setting an exit code that at least communicates success (0
) vs. failure (nonzero, typically) is an important mechanism for letting outside callers know whether your PowerShell code succeeded overall or not, such as when being called from a scheduled task or from an automation server such as Jenkins via the PowerShell CLI (command-line interface) - pwsh for PowerShell [Core] vs. powershell.exe for Windows PowerShell.
The CLI offers two ways to execute PowerShell code, and you can use exit <n>
to set an exit code, where <n>
is the desired exit code:
-File <script> [args...]
expects the path of a script file (*.ps1
) to execute, optionally followed by arguments.
exit <n>
directly inside such a script file (not inside another script that you call from that script) makes the PowerShell process report its exit code as <n>
.-Command <powershell-code>
expects a string containing one or more PowerShell commands.
exit <n>
as a direct part of that command string - typically, as the last statement.If your code is called from tools that check success by exit code, make sure that all code paths explicitly use exit <n>
to terminate.
Caveat: If the PowerShell process terminates due to an unhandled script-terminating error - irrespective of whether the CLI was invoked with -File
or -Command
, the exit code is always 1
.
A script-terminating (thread-terminating) error is either generated from PowerShell code with the throw
statement or by escalating a less a severe native PowerShell error with -ErrorAction Stop
or $ErrorActionPreference = 'Stop'
, or by pressing Ctrl-C to forcefully terminate a script.
If exit code 1
isn't specific enough (it usually is, because typically only success vs. failure needs to be communicated), you can wrap your code in a try
/ catch
statement and use exit <n>
from the catch
block.
The exact rules for how PowerShell sets its process exit code are complex; find a summary below.
If an unhandled script-terminating error occurs, the exit code is always 1
.
With -File
, executing a script file (*.ps1
):
If the script directly executes exit <n>
, <n>
becomes the exit code (such statements in nested calls are not effective).
Otherwise, it is 0
, even if non-terminating or statement-terminating errors occurred during script execution.
With -Command
, executing a command string containing one or more statements:
If an exit <n>
statement is executed directly as one of the statements passed in the command string (typically, the last statement), <n>
becomes the exit code.
Otherwise, it is the success status of the last statement executed, as implied by $?
, that determines the exit code:
If $?
is:
$true
-> exit code 0
$false
-> exit code 1
- even in the case where the last executed statement was an external program that reported a different nonzero exit code.Given that the last statement in your command string may not be the one whose success vs. failure you want to signal, use exit <n>
explicitly to reliably control the exit code, which also allows you to report specific nonzero exit codes.
; exit $LASTEXITCODE
to the string you pass to -Command
.Arguably, -Command
(-c
) should report the specific exit code of the last statement - provided it has one - instead of the abstract 0
vs. 1
. For instance, pwsh -c 'findstr'; $LASTEXITCODE
should report 2
, findstr.exe
's specific exit code, instead of the abstract 1
- see this GitHub issue.
Exit-code reporting with *.ps1
files / the -File
CLI parameter:
It is only an explicit exit <n>
statement that meaningfully sets an exit code; instead, it should again be the last statement executed in the script that determines the exit code (which, of course, could be an exit
statement), as is the case in POSIX-compatible shells and with -Command
, albeit in the suboptimal manner discussed.
When you call a *.ps1
script via -File
or as the last statement via -Command
, PowerShell's exit code in the absence of the script exiting via an exit
statement is always 0
(except in the exceptional Ctrl-C / throw
cases, where it becomes 1
).
By contrast, when called in-session, again in the absence of exit
, $LASTEXICODE
reflects the exit code of whatever external program (or other *.ps1
if it set an exit code) was executed last - whether executed inside the script or even before.
In other words:
-File
, unlike with -Command
, the exit code is categorically set to 0
in the absence of an exit
statement (barring abnormal termination).$LASTEXITCODE
) is not set at all for the script as a whole in the absence of an exit
statement.See this GitHub issue.