Home > front end >  Powershell: terminating exception still continues to next command
Powershell: terminating exception still continues to next command

Time:06-30

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 placing trap { break } in your scope:
# 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 use throw from the catch 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.

  • Related