I've read quite a bit on powershell error handling and now I'm quite confused about what I should be doing on any given situation (error handling). I'm working with powershell 5.1 (not core). With that said: Suppose I have a module with a function that would look like this mock:
function Set-ComputerTestConfig {
[CmdletBinding()]
param(
[Parameter(Position=0, Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string] $Name)
begin { ... }
process {
# task 1
# task 2 => results in a failure that prevents further tasks
# task 3
# task 4
}
end { ... }
Let's say that for each computer name that I pass to this function, I have 4 tasks to complete, but if any of the tasks fail, I can't continue with the remaining tasks. How should I be producing an error (best practice) such that it halts "process" for this particular computer name but effectively continues to process the pipeline?
If you want to continue processing inputs from the pipeline, you must emit a non-terminating error:
Write-Error
writes non-terminating errors; it writes to PowerShell's error stream without generating an exception behind the scenes; execution continues normally.If a .NET method call is the error source, as in your case, wrap it in
try
/catch
, and callWrite-Error -ErrorRecord $_
in thecatch
block:try { <#task 1 #>; ... } catch { Write-Error -ErrorRecord $_ }
Unfortunately, still as of PowerShell Core 7.0.0-preview.4,
Write-Error
doesn't fully behave as expected, in that it doesn't set the automatic success-status variable,$?
, to$false
in the caller's context, as it should. The only workaround at present is to make sure that your function/script is an advanced one and to use$PSCmdlet.WriteError()
; from acatch
block you can simply use$PSCmdlet.WriteError($_)
, but crafting your own error from scratch is cumbersome - see this GitHub issue.If you want processing to stop right away, use a terminating error:
throw
creates terminating errors.Unfortunately,
throw
creates a more fundamental kind of terminating error than binary cmdlets emit: unlike the statement-terminating errors emitted by (compiled) cmdlets,throw
creates a script-terminating error (runspace-terminating error)throw
by default aborts the entire script (and its callers).Again, the workaround requires that your script/function is an advanced one, which enables you to call
$PSCmdlet.ThrowTerminatingError()
instead ofthrow
, which properly generates a statement-terminating error; as with$PSCmdlet.WriteError()
, you can simply use$PSCmdlet.ThrowTerminatingError($_)
from acatch
block, but crafting your own statement-terminating error from scratch is cumbersome.As for
$ErrorActionPreference = 'Stop'
This turns all error types into script-terminating errors, and at least advanced functions / scripts - those expected to act like cmdlets - should not set it.
Instead, make your script / function emit the appropriate types of errors and let the caller control the response to them, either via the common
-ErrorAction
parameter or via the$ErrorActionPreference
variable.As for passing errors through / repackaging them from inside your function script:
Non-terminating errors are automatically passed through.
-ErrorAction Ignore
or2>$null
and optionally also collect them for later processing with the-ErrorVariable
common parameter (combine with-ErrorAction SilentlyContinue
).Script-terminating errors are passed through in the sense that the entire runspace is terminated, along with your code.
Statement-terminating errors are written to the error stream, but by default your script / function continues to run.
Use
try { ... } catch { throw }
to instead turn them into script-terminating errors, or ...... use
$PSCmdlet.ThrowTerminatingError($_)
instead ofthrow
to relay the error as a statement-terminating one.Further reading:
Guidance on when to emit a terminating vs. a non-terminating error is in this answer.
A comprehensive overview of PowerShell's error handling is in this GitHub docs issue.
Use Try...Catch - make sure all commands use
-ErrorAction Stop
switch or set the environment to stop on error e.g.$ErrorActionPreference = 'Stop'