In this great article Keith explains the difference between terminating and non-terminating errors in Powershell. According to Keith exceptions thrown from calls to a member of a .NET object or type are non-terminating errors.
Indeed if we define this .NET class for testing:
$a = Add-Type 'public class bla { public static void bl() { throw new System.ApplicationException("test"); }}' -PassThru
And then this function:
function tst { 1 | write-host; $a::bl(); 2 | Write-host }
we will see that when tst function is called the exception appears to be non-terminating: the second Write-Host
works.
But consider this:
function tst2 { try { tst } catch { "Catch!" } }
If we open the documentation, we can read that the catch responds to or handles terminating errors in scripts. Throughout the text of the articles the errors the article is dealing with qualified as "terminating" in numerous places.
So when we run the line above the second Write-Host does NOT run but the catch block does. It seems that our non-terminating error suddenly becomes terminating.
How come?
Another observation, is that with good old trap it's still non-terminating error:
function tst3 { tst trap { "Trap!" } }
Now, from practical perspective, what I want to achieve is the following. In a block of code I want to terminate on exceptions thrown from the .NET code. I want to leave terminating errors from cmdlets terminating and non-terminating errors from cmdlets non-terminating.
How do I achieve this?
Example:
Do-Something
Call::Something()
Do-SomethingElse
Call::SomethingElse()
Do-YetMoreSomething
Call::YetMoreSomething()
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.
In . NET, an exception is an object that inherits from the System. Exception class. An exception is thrown from an area of code where a problem has occurred. The exception is passed up the stack until the application handles it or the program terminates.
The Anatomy of C# Exceptions If any code throws an exception within that try block, the exception will be handled by the corresponding catch. catch – When an exception occurs, the Catch block of code is executed. This is where you are able to handle the exception, log it, or ignore it.
Non-Terminating Error − This error is generally generated by internal cmdlets and automatically handled by them only but the error doesn't terminate the execution of the pipeline.
Use a try block around the statements that might throw exceptions. Once an exception occurs in the try block, the flow of control jumps to the first associated exception handler that is present anywhere in the call stack. In C#, the catch keyword is used to define an exception handler.
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:
Script-terminating errors:
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:
-ErrorAction Continue/SilentlyContinue/Ignore
(again, note that this does not act on statement/script-terminating errors)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:
$ErrorActionPreference = 'Stop'
value.This GitHub docs issue tries to give a comprehensive overview of PowerShell's error handling and its pitfalls.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With