I wondered that scripts whose parameter are validated by the ValidateSet
attribute do not return any invalid $LASTEXITCODE
when invalid parameters are given.
Let's say I have the following PowerShell script, called try.ps1
:
#INPUT Variables for the Script
Param(
[Parameter(Mandatory)]
[ValidateSet("a", "b")]
[string] $first_param,
[Parameter(Mandatory)]
[ValidateSet("c", "d")]
[string] $second_param
)
exit 0
If I call it now in PS via PS C:\Users\MyUser> .\try.ps1 -first_param n -second_param c
(invalid $first_param
) I get a ParameterBindingValidationException
with an Error called ParameterArgumentValidationError
but this seems not not set my $LASTEXITCODE
variable, but why not?
So how is it possible for me (without bloating my code and validating each of my variables in some if-else
statements to trigger an error) to get $LASTEXITCODE=1
back when calling my script with invalid params?
tl;dr
Don't rely on exit codes for error handling in PowerShell; use them to only to communicate success vs. failure to the outside world.
For scripts you know to be explicitly designed to report an exit code to the outside world, you can query $LASTEXITCODE
, but that alone isn't enough to handle all error conditions, as your example shows.
Even then, as discussed in detail in the next section, $LASTEXITCODE
may contain an unrelated value inside a PowerShell session.
In your invocation attempt, your script doesn't even get to execute, because the parameter-validation failure generates a statement-terminating error before the body of your script is entered.
When a statement-terminating error occurs, execution continues by default.
You can query the automatic $?
variable (a Boolean success indicator) to see if (at least one) error occurred in the previous statement:
.\try.ps1 -first_param n -second_param
if (-not $?) { # An error occurred
# Handle the error here.
# E.g., for a script designed to be called from outside PowerShell:
exit 2 # Signal failure to an outside caller.
}
However, by the time you check $?
, the error message has already printed (at least by default); if you want to intercept the error, so as to print either no message, a different message, or a modified message, you need a conceptual try
/ catch
statement:
try {
.\try.ps1 -first_param n -second_param
} catch {
# Handle the error
# $_ contains the error at hand, as an [System.Management.Automation.ErrorRecord] instance.
# E.g., for a script designed to be called from outside PowerShell:
Write-Error "Invocation of try.ps1 failed unexpectedly: $_"
exit 2 # Signal failure to an outside caller.
}
See also:
For a comprehensive overview of PowerShell's bewilderingly complex error handling, see GitHub docs issue #1583.
For a comprehensive discussion of exit codes in PowerShell, see this post.
Read on for some additional conceptual information.
Unfortunately, the excerpt from the documentation in Josef Z's answer is only partly correct (in short: $LASTEXITODE
is set in response to exit $number
calls in scripts in-session, and $number
is reported as the specific exit code in -File
CLI calls - GitHub docs issue #10046 aims to get the documentation corrected).
Let me present what I believe to be the correct (bigger) picture:
PowerShell itself does not use exit codes for its error handling.
PowerShell uses exit codes only in order to interact with the outside world:
It stores the exit code of the most recently executed external program (child process) in the automatic $LASTEXITCODE
variable, so you can check whether it signaled success (exit code 0
) or failure (any non-zero exit code, by convention; sometimes, non-zero exit codes are used to communicate specific success conditions too; robocopy.exe
is a notable example).
$PSNativeCommandErrorActionPreference
preference is being considered as of PowerShell 7.3.x; it is currently an experimental feature, available in preview versions of PowerShell 7.4 - which means that it may or may not become an actual feature - see this answer for more information.For use in script files(*.ps1
), PowerShell offers the exit
keyword so that PowerShell scripts can communicate an exit code to the outside world when invoked via PowerShell's CLI (powershell.exe
for Windows PowerShell, pwsh
for PowerShell (Core) 7+)), which is important to signal failure vs. success, particularly in CI/CD environments.
The PowerShell CLI's exit-code reporting works differently depending on whether the -File
or the -Command
parameter is used (the default parameter is -File
in PowerShell (Core) 7+ vs. -Command
in Windows PowerShell):
Note: If a fatal (script-terminating) error occurs - such as when throw
is used - both -File
and -Command
invocations report 1
as the exit code.
With -File
- for invoking a single *.ps1
file, optionally with arguments - the exit code is 0
by default, except if explicitly specified via an exit $number
call in the script, where $number
represents the desired exit code.
With -Command
- for executing arbitrary PowerShell commands - it is by default the implied success status (as it would be reflected in $?
) of the last command in the command string(s) passed to -Command
that determines the exit code: success is reported as exit code 0
, and failure as 1
; exit $number
at the end of the -Command
string(s); if the last (or only) command is a call to an external program or to a script that uses exit $number
itself, and you want to relay that specific exit code, use ; exit $LASTEXITCODE
, but note the caveat below.While an intra-session call to a script (*.ps1
) that uses exit
- either without an argument or with a numeric argument[1] - e.g. exit 1
- does cause PowerShell to set $LASTEXITCODE
to that value (just exit
sets 0
), you shouldn't generally rely on that, because:
Scripts designed only for intra-session use (for calls from inside a PowerShell session) typically do not use exit
, so $LASTEXITCODE
won't have a meaningful value, because it then doesn't get set - and may therefore reflect an unrelated exit code from an earlier call to an external program / script.
That is, intra-session only explicit exit
calls set $LASTEXITCODE
, not the execution of a script file per se.
Caveat: If you do not use exit
and your script happens to call an external program (or another script that does use exit
) its exit code will be reflected in $LASTEXITCODE
- (a) this may not be your intent and (b) such an incidental exit code is not communicated to outside callers via the CLI (see below).
Therefore, if your script must reliably report an exit code, make sure that all code paths end with an exit
or exit $number
statement.
By contrast, when the CLI is used, with -File
in the case of a *.ps1
file, PowerShell defaults to 0
as the exit code (reported to the outside caller) in the absence of an exit $number
call in the script ($number
representing the desired exit code).
Either way, it is important to note that for outside callers it is only ever an explicit exit $number
call in a *.ps1
file that sets a non-zero exit code.
bash
, where, in the absence of exit
, it is the exit code of the script's last command that determines the script's exit code as a whole.[1] Curiously, exit
quietly accepts and effectively ignores an argument that either isn't numeric or cannot be coerced to a number, so a call such as exit (Get-Date)
is effectively the same as exit
and therefore sets $LASTEXITCODE
/ the exit code to 0