Understanding Error Handling β
TIP
Ultimate reference: about_Error_Handling
TLDR; β
An error have two parts, the error message and the action on error.
Error Handling involves:
Where the error message should go(to be printed/written/redirected)
- access it from
$Errorvariable - assign to variable using
-ErrorVariable - redirect to variable or file using
2>variable:varnameor2>err.log - suppress error message using
SilentlyContinueorIgnoreor2>$null
- access it from
What kind of error can terminate execution, and terminate at what level
- Non-Terminating: does not terminate execution but can be elevated by
$ErrorActionPreferenceor-ErrorActionWrite-Error,$PSCmdlet.WriteErroretc
- Statement-Terminating: can be captured by
try..catchandtrap$PSCmdlet.ThrowTerminatingError(ErrorRecord)- exceptions from PowerShell engine, typically from cmdlet and runtime binding
- exceptions from dotnet method call
- Script-Terminating: stops entire call stack
- on
-ErrorAction Stopor$ErrorActionPreference = 'Stop'
- on
- Non-Terminating: does not terminate execution but can be elevated by
How to identifying specific source of error
- using
catch
- using
How to perform on action
- using
catch
- using
Every Error is Wrapped β
As a dotnet-based engine, PowerShell wraps error as as System.Management.Automation.ErrorRecord type. There's a dedicated ErrorRecord.Exception property for the concrete exception.
NOTE
System.Management.Automation.RuntimeException is the base type specific to PowerShell engine. Most of cmdlets should throw its derived exception.
try {
throw 'foo'
} catch {
$_.GetType().FullName # System.Management.Automation.ErrorRecord
$_.Exception.GetType().FullName # System.Management.Automation.RuntimeException
}2
3
4
5
6
For exception from dotnet method call, it has even more convoluted wrapper System.Management.Automation.MethodInvocationException simply because the engine needs a nominal identity to recognize different types of interoperability. It also helps to identify which source is causing the problem since there's no concrete exception type specified.
try {
[int]::Parse('foo')
} catch {
$_.GetType().FullName # System.Management.Automation.ErrorRecord
$_.Exception.GetType().FullName # System.Management.Automation.MethodInvocationException
$_.Exception.InnerException.GetType().FullName # System.FormatException
}2
3
4
5
6
7
When catch branch has a concrete exception type specified, the error is unwrapped one level up since we already know its concrete type.
try {
[int]::Parse('foo')
} catch [System.FormatException] {
$_.GetType().FullName # System.Management.Automation.ErrorRecord
$_.Exception.GetType().FullName # System.Management.Automation.MethodInvocationException
}2
3
4
5
6
Non-Terminating Error β
Non-Terminating errors does not terminate execution at any condition but can be upgraded depending on $ErrorActionPreference or -ErrorAction.
Statement-Terminating Error β
Statement-Terminating errors stops execution of current scope.
Script-Terminating Error β
Script-Terminating error stops the whole execution immediately. It can be generated by a throw statement or an uncaught Stop error action.
throw can be lowered to Non-Terminating by SilentlyContinue or Ignore, -ErrorAction Stop can't be lowered because it's already a overridden option.
$ErrorActionPreference = 'Ignore'
throw "foo"
"bar" # only bar is printed2
3
4
5
Error Status β
$LASTEXITCODE: last exit code from native command$?:$trueor$falseindicating previous invocation succeeded, would become$falsewhen:- native command returns non-zero exit code
- cmdlet has throw any error(even Non-Terminating)
- dotnet method call throws exception
[int]::Parse('foo')
$? # False #
function foo {
Write-Error "foo error"
$? # false #
}
foo
$? # True #
Get-Item foo -ErrorAction SilentlyContinue
$? # False #2
3
4
5
6
7
8
9
10
11
12
NOTE
The value $? is not affected by any kind of error suppression.
Error Storage β
$Error β
$Error is a ArrayList that stores everything error message even when the message was suppressed by redirection or SilentlyContinue. The error message might come from Write-Error or cmdlet or PowerShell handled native command invocation. It always prepend the error meaning the first item is the latest error triggered.
Get-Item foo -ErrorAction SilentlyContinue
$Error[0] # Cannot find path '...foo' because it does not exist.
Get-Item bar 2>$null
$Error[0] # Cannot find path '...bar' because it does not exist.
Write-Error "foo error"
$Error[0] # Write-Error: foo error
$PSNativeCommandUseErrorActionPreference = $true
robocopy 1>$null 2>$null
$Error[0] # NativeCommandExitException: Program "Robocopy.exe" ended with non-zero exit code: 16 (0x00000010).2
3
4
5
6
7
8
9
10
11
12
NOTE
The only way to prevent Non-Terminating error to be prepended to $Error is using -ErrorAction Ignore.
-ErrorVariable β
-ErrorVariable is pretty much the same as $Error but specific to a cmdlet, if you store multiple errors it same error variable, it will prepend to it. If another command invocation uses the same error variable, the variable is reset, meaning you can't share a same error variable with other command invocations. Even though the error is already stored in the variable, $Error would still capture the same error.
if (Get-Item foo -ErrorVariable err -ErrorAction SilentlyContinue) {
$err[0] # Cannot find path '...bar' because it does not exist.
}
if (Get-Item bar -ErrorVariable err -ErrorAction SilentlyContinue) {
# err variable has been reset
$err.Count # 1
$err[0] # Cannot find path '...bar' because it does not exist.
}
# $Error captures every error
$Error[0] # Cannot find path '...bar' because it does not exist.2
3
4
5
6
7
8
9
10
11
12
Error Stream Redirection β
You can either redirect error stream to a variable or file.
Get-Item foo 2>err.log
Get-Item foo 2>variable:err # available since pwsh 7.6.02
Redirecting to variable isn't very useful fro cmdlets since they already have -ErrorVariable but it could be useful for native commands.
Suppressing Error Message β
NOTE
There's no way to suppress error message on Script-Terminating error since it's only raised once.
-ErrorAction Ignorefor cmdlets-ErrorAction SilentlyContinuefor cmdlets- Discard error stream
2>$null - Redirect error stream to file or variable if intended
SilentlyContinue and 2>$null are equivalent as they don't prevent the error to be prepended to $Error. Ignore completely wipes out the Non-Terminating error from the engine that you can't inspect later.
Native Command Error β
Native command invocation doesn't trigger internal error from PowerShell by default, they only present status using exit code. Since pwsh 7.4 $PSNativeCommandUseErrorActionPreference was added to respect the value of $ErrorActionPreference when invoking native command. That means, when the native command returns a non-zero exit code, pwsh will raise an error internally from the engine. The error stream would also become mixed as it has to include error from the engine as well.
& {
$ErrorActionPreference = 'Continue'
$PSNativeCommandUseErrorActionPreference = $true
robocopy 1>$null 2>err.log
# internal error included in the stream as well
Get-Content err.log # NativeCommandExitException: Program "Robocopy.exe" ended with non-zero exit code: 16 (0x00000010).
}2
3
4
5
6
7
8
9
The error from $PSNativeCommandUseErrorActionPreference is Non-Terminating by default, it can be elevated by $ErrorActionPreference.
& {
$ErrorActionPreference = 'Continue'
$PSNativeCommandUseErrorActionPreference = $true
try {
robocopy 1>$null
} catch {
"not reachable here because it's Non-Terminating!"
}
}2
3
4
5
6
7
8
9
10
Error Action Promotion β
You can either promote a Non-Terminating and Statement-Terminating error to Script-Terminating, or lower them to Non-Terminating. But you can neither promote nor lower an error to Statement-Terminating error.