问题
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?
回答1:
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)- That is, by default a binary cmdlet's statement-terminating error only terminates the statement (pipeline) at hand and continues execution of the enclosing script, whereas
throw
by default aborts the entire script (and its callers).
- That is, by default a binary cmdlet's statement-terminating error only terminates the statement (pipeline) at hand and continues execution of the enclosing script, whereas
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.- Caveat: Functions in modules do not see the caller's preference variables, if the caller is outside a module or in a different module - this fundamental problem is discussed in this GitHub issue.
As for passing errors through / repackaging them from inside your function script:
Non-terminating errors are automatically passed through.
- If needed, you can suppress them with
-ErrorAction Ignore
or2>$null
and optionally also collect them for later processing with the-ErrorVariable
common parameter (combine with-ErrorAction SilentlyContinue
).
- If needed, you can suppress them with
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.
回答2:
Use Try...Catch - make sure all commands use -ErrorAction Stop
switch or set the environment to stop on error e.g. $ErrorActionPreference = 'Stop'
function Set-ComputerTestConfig {
[CmdletBinding()]
param(
[Parameter(Position=0, Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string] $Name)
begin { ... }
process {
Try {
# task 1
# task 2 => results in a failure that prevents further tasks
# task 3
# task 4
}
Catch {
Write-Host "One of the tasks has failed`nError Message:"
Write-Host $_
}
}
end { ... }
来源:https://stackoverflow.com/questions/58516065/whats-the-right-way-to-emit-errors-in-powershell-module-functions