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: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 conceptualtry
/catch
statement: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.
Background information:
Unfortunately, the excerpt from the documentation in Josef Z's answer is only partly correct (in short:
$LASTEXITODE
is set in response toexit $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 code0
) 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 theexit
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 report1
as the exit code.With
-File
- for invoking a single*.ps1
file, optionally with arguments - the exit code is0
by default, except if explicitly specified via anexit $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 code0
, and failure as1
; 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 usesexit $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 usesexit
- either without an argument or with a numeric argument[1] - e.g.exit 1
- does cause PowerShell to set$LASTEXITCODE
to that value (justexit
sets0
), 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 useexit
) 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
orexit $number
statement.By contrast, when the CLI is used, with
-File
in the case of a*.ps1
file, PowerShell defaults to0
as the exit code (reported to the outside caller) in the absence of anexit $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 ofexit
, 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 asexit (Get-Date)
is effectively the same asexit
and therefore sets$LASTEXITCODE
/ the exit code to0