PowerShell and process exit codes
Current as of PowerShell [Core] 7.2.1
PowerShell-internal use of exit codes:
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 GitHub issue #3359 - and because usingWrite-Error
in user functions doesn't set$?
to$false
- see GitHub issue #3629; 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 of0
(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.-
Caveat: Due to
cmd.exe
's quirks, a batch file's exit code isn't reliably reported, but you can work around that withcmd /c <batch-file> ... `& exit
- see this answer; GitHub issue #15143 additionally suggests building this workaround into PowerShell itself. - Also, up to and as of v7.1,
$?
can report false negatives if the external program reports exit code0
while also producing stderr output and there is also a PowerShell redirection involving2>
or*>
- see this answer and GitHub issue #3996; as of PowerShell PowerShell Core 7.2.0-preview.4; the corrected behavior is a available as experimental featurePSNotApplyErrorActionToStderr
.
-
Caveat: Due to
-
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.- Better integration of external programs into PowerShell's error handling is being proposed in RFC #277.
How to control what PowerShell reports as its exit code when it is called from the outside:
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.-
Executing
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>
. -
If a given script file exits implicitly or with just
exit
(without an exit-code argument), exit code0
is reported.
-
-
-Command <powershell-code>
expects a string containing one or more PowerShell commands.- To be safe, use
exit <n>
as a direct part of that command string - typically, as the last statement.
- To be safe, use
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 (fatal) 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 atry
/catch
statement and useexit <n>
from thecatch
block.
The exact rules for how PowerShell sets its process exit code are complex; find a summary below.
How PowerShell sets its process exit code:
-
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 code0
-
$false
-> exit code1
- 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.- For instance, to faithfully relay the exit code reported by an external program, append
; exit $LASTEXITCODE
to the string you pass to-Command
.
- For instance, to faithfully relay the exit code reported by an external program, append
-
-
Inconsistencies and pitfalls as of PowerShell 7.0:
-
Arguably,
-Command
(-c
) should report the specific exit code of the last statement - provided it has one - instead of the abstract0
vs.1
. For instance,pwsh -c 'findstr'; $LASTEXITCODE
should report2
,findstr.exe
's specific exit code, instead of the abstract1
- see GitHub issue #13501. -
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 anexit
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 anexit
statement is always0
(except in the exceptional Ctrl-C /throw
cases, where it becomes1
). -
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:
- With
-File
, unlike with-Command
, the exit code is categorically set to0
in the absence of anexit
statement (barring abnormal termination). - In-session, the exit code (as reflected in
$LASTEXITCODE
) is not set at all for the script as a whole in the absence of anexit
statement.
- With
-
See GitHub issue #11712.
-