I was expecting that with a terminating exception, the script execution will hault in powershell.
Since try/catch will only capture terminating exception(other than ErroActionPreference etc) I had the below code
try {
$hashtableJson = "asdfg.kjhgf" | ConvertFrom-Json
} catch {
Write-Host "inside catch block"
}
and I can see "inside catch block" printed.
Which means the exception System.ArgumentException
which comes due to invalid json is terminating.
Now my doubt is in the below script
$hashtableJson = "asdfg.kjhgf" | ConvertFrom-Json
Write-Host "after terminating"
In the above script I expected the run to break on the first line itself but the after terminating
still gets printed.
Any explanation why its not exiting the flow? or how to better figure out if exception is terminating or not?
CodePudding user response:
I think either the documentation of $ErrorActionPreference
or the error classification of PS is wrong, but when I have Continue
then I can reproduce the above, but setting it to Stop
, "after terminating" is no longer popping up (tested for v5.1. with a .ps1 file).
CodePudding user response:
PowerShell has two types of terminating errors, which the documentation currently conflates, unfortunately:
Statement-terminating errors, as reported by compiled cmdlets (as opposed to written-in-PowerShell advanced functions)[1] and .NET methods, which only terminate the current statement by default; that is, by default execution of the script continues, with the next statement.
Script-terminating (runspace-terminating, fatal) errors, as raised by
throw
and when$ErrorActionPreference = 'Stop'
is in effect or when-ErrorAction Stop
is passed to cmdlets and advanced functions / scripts and a non-terminating error occurs.
The third error type - the most common one - is a non-terminating error, which doesn't affect the control flow at all: by default even processing of the current statement (with remaining pipeline input) continues.
Note:
Arguably, there should only ever have been one type of terminating error: script-terminating (fatal), which, if needed, could be handled as merely statement-terminating, with
try
/catch
(see below), but it's too late to change that.For a comprehensive overview of PowerShell's surprisingly complex error handling, see this GitHub docs issue.
ConvertFrom-Json
, as a compiled cmdlet, emits a statement-terminating error when invalid JSON is provided as input, which explains why execution of your script continues.
You can use try
/ catch
or trap
to catch both statement- and script-terminating errors - but not non-terminating ones by default, unless you promote them to script-terminating ones with -ErrorAction Stop
.
By contrast, $ErrorActionPreference = 'Stop'
promotes all errors to script-terminating (fatal) ones, including non-terminating ones.
Confusingly, the seeming per-command equivalent, namely passing -ErrorAction Stop
to an individual cmdlet, does not promote a statement-terminating error to a script-terminating one - you still need try
/ catch
or trap
for that - see below.
The upshot:
- To treat all errors occurring in your scope - including non-terminating ones - as fatal, use
$ErrorActionPreference = 'Stop'
# Treat *all* errors in this scope (and descendant scopes)
# as *script*-terminating (fatal).
$ErrorActionPreference = 'Stop'
# Trigger a *non*-terminating error, which is *also* promoted
# to a script-terminating one.
Get-Item NoSuchFile
# Trigger a *statement*-terminating error, which is promoted
# to a script-terminating one.
# Note: Because of the Get-Item error, this statement isn't reached,
# Comment it out to see that this statement too causes a
# script-terminating error.
$hashtableJson = "asdfg.kjhgf" | ConvertFrom-Json
'This statement is now never reached.'
By contrast, use the following techniques to make only terminating errors fatal while keeping non-terminating errors non-terminating:
- Scope-wide, you can use the (rarely used)
trap
statement to that effect, namely by placingtrap { break }
in your scope:
- Scope-wide, you can use the (rarely used)
# Trap both kinds of *terminating* errors and abort.
trap { break }
# Trigger a *non*-terminating error, which is left alone (not
# caught by the trap).
Get-Item NoSuchFile
# Trigger a *statement*-terminating error, which triggers the trap.
$hashtableJson = "asdfg.kjhgf" | ConvertFrom-Json
'This statement is now never reached.'
- Alternatively, especially if you need you more fine-grained control, use
try
/catch
around statements of interest and usethrow
from thecatch
block to trigger a script-terminating error.
try {
# Trigger a *non*-terminating error, which is left alone (does
# not trigger the catch block).
Get-Item NoSuchFile
# Trigger a *statement*-terminating error, which triggers the catch block.
$hashtableJson = "asdfg.kjhgf" | ConvertFrom-Json
} catch {
throw # Re-throw the *statement*-terminating error as *script*-terminating.
}
'This statement is now never reached.'
[1] It is possible to make an advanced function / script emit a statement-terminating error, namely via $PSCmdlet.ThrowTerminatingError()
, but that is both obscure and cumbersome; in practice, it is far more common to use the script-terminating throw
statement.