Is an exception from a .NET method a terminating or not-terminating error? Is an exception from a .NET method a terminating or not-terminating error? powershell powershell

Is an exception from a .NET method a terminating or not-terminating error?


Yeah, this came up on the PowerShell MVP email list earlier this year. PowerShell changes its error handling behavior for .NET exceptions depending on whether or not there is an outer try/catch. This is just speculation but I'm guessing that this was for simple scripting scenarios. That is, if the scripter (admin) messes up calling a .NET method and that generates an exception, the PowerShell team didn't want that to cease execution of the entire script. Once V2 came along and introduced proper try/catch I'm guessing they had to revisit that decision and came up with the current compromise.

That said, working around this is a pain as you've discovered. You could set $ErrorActionPreference to Stop at the script level and then for every cmdlet that can generate non-terminating errors use the -ErrorAction Continue parameter. Or you could put all of your .NET invocations within an advanced function(s) and then call that function(s) with the parameter -ErrorAction Stop. I wish there was a better answer but after reviewing that MVP thread, I didn't see any better solutions.


The Try block turns the .NET exception into a terminating error, so you can enclose your .NET call in such a block:

Try { Class::RunSomeCode() } Catch { Throw }

So in your example your tst function becomes

function tst { 1 | write-host; Try { $a::bl() } Catch { Throw } 2 | Write-host }

And your .NET exception will now be terminating.


To complement Keith Hill's helpful answer:

PowerShell's error handling is complex, and matters are made worse by the fact that the documentation conflates two types of terminating errors:

  • Statement-terminating errors:

    • By default, they abort the current statement only and continue execution (with the next statement).
  • Script-terminating errors:

    • By default, they are fatal: they abort the entire script (more accurately, the entire call stack); the only way to generate them directly in PowerShell is with the Throw statement.

As an aside: The third type of error are non-terminating errors, whose primary purpose is to allow pipeline-processing cmdlets to report errors with respect to specific input objects, where such errors do not categorically prevent potentially successful processing of further input objects.


Unhandled .NET exceptions are statement-terminating errors, just like statement-terminating errors reported by cmdlets.

As such, you cannot fundamentally handle them differently using try / catch (which acts on both statement-terminating and script-terminating errors, but non non-terminating ones):

While you can test the [ErrorRecord] instance available as $_ in the catch block for its .Exception property being of type [System.Management.Automation.MethodInvocationExeption]
($_.Exception -is [System.Management.Automation.MethodInvocationExeption]),
it is too late to resume the default behavior for cmdlet-originated statement-terminating errors, because the try block has already been exited.


Treating all statement-terminating errors as fatal:

I want to terminate on all the exceptions from .NET calls above. I also want to terminate on terminating errors from the cmdlets. I do not want to terminate on non-terminating errors from the cmdlets.

Use the following in your script:

# Escalate any statement-terminating error to a script-terminating one,# but leave non-terminating ones alone.# (Already script-terminating errors still terminate the script.)trap { break }

trap, like try / catch, acts on statement-terminating and script-terminating errors (but not non-terminating ones).

Use of break is crucial in order to terminate the script; by default, or when you use continue, execution continues with the next statement after the error-causing one, which even applies to script-terminating errors; continue additionally suppresses output of the error record (though it is still collected in the automatic $Error collection).


Treating all errors as fatal:

Note: Failures signaled by external programs via their exit code, reflected in automatic variable $LASTEXITCODE, are never considered errors by PowerShell (however, an opt-in mechanism to change that is being discussed; also, there are edge cases when an external program's stderr output indirectly triggers PowerShell errors - see this GitHub issue).

Setting:

 # !! Covers many, but NOT ALL scenarios - see below. $ErrorActionPreference = 'Stop'

is almost sufficient to make all error types fatal-by-default (script-terminating), but, sadly, not in all situations.

Note: Documented behavior claims that the $ErrorActionPreference preference variable only acts on non-terminating errors, but in reality it acts on statement-terminating ones as well; by contrast, the -ErrorAction common parameter indeed only acts on non-terminating errors.
As such, you do not strictly need to combine the preference variable with trap { break }.

Hypothetically, the above therefore makes all PowerShell errors fatal by default while allowing you to selectively override the behavior:

  • for non-terminating errors: on a per-command basis with common parameter -ErrorAction Continue/SilentlyContinue/Ignore (again, note that this does not act on statement/script-terminating errors)
  • for statement/script-terminating errors: with a command-enclosing try / catch

In practice, however, the approach fails for commands that happen to be implemented as advanced functions in a script module, which includes generated script modules that wrap remote cmdlets calls for implicit remoting:

Because of how variable scopes work in script modules, functions located in (different) script modules do NOT see the caller's preference variables (compiled cmdlets do not have this problem): see this GitHub issue.

In other words:

  • Functions from a different module will effectively ignore the caller's $ErrorActionPreference = 'Stop' value.
  • You may not even see it coming, given that it's not obvious which commands are implemented as binary cmdlets and which as functions.
  • Even if you do know which commands require a workaround, that workaround is cumbersome - see this answer of mine.

This GitHub docs issue tries to give a comprehensive overview of PowerShell's error handling and its pitfalls.