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,
$LASTEXITCODEmay 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/catchstatement: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:
$LASTEXITODEis set in response toexit $numbercalls in scripts in-session, and$numberis reported as the specific exit code in-FileCLI 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
$LASTEXITCODEvariable, 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.exeis a notable example).$PSNativeCommandErrorActionPreferencepreference 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 theexitkeyword so that PowerShell scripts can communicate an exit code to the outside world when invoked via PowerShell's CLI (powershell.exefor Windows PowerShell,pwshfor 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
-Fileor the-Commandparameter is used (the default parameter is-Filein PowerShell (Core) 7+ vs.-Commandin Windows PowerShell):Note: If a fatal (script-terminating) error occurs - such as when
throwis used - both-Fileand-Commandinvocations report1as the exit code.With
-File- for invoking a single*.ps1file, optionally with arguments - the exit code is0by default, except if explicitly specified via anexit $numbercall in the script, where$numberrepresents 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-Commandthat determines the exit code: success is reported as exit code0, and failure as1; exit $numberat the end of the-Commandstring(s); if the last (or only) command is a call to an external program or to a script that usesexit $numberitself, 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$LASTEXITCODEto that value (justexitsets0), 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$LASTEXITCODEwon'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
exitcalls set$LASTEXITCODE, not the execution of a script file per se.Caveat: If you do not use
exitand 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
exitorexit $numberstatement.By contrast, when the CLI is used, with
-Filein the case of a*.ps1file, PowerShell defaults to0as the exit code (reported to the outside caller) in the absence of anexit $numbercall in the script ($numberrepresenting the desired exit code).Either way, it is important to note that for outside callers it is only ever an explicit
exit $numbercall in a*.ps1file 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,
exitquietly 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 asexitand therefore sets$LASTEXITCODE/ the exit code to0