如何正确使用 $PSCmdlet.ThrowTerminatingError() 抛出错误?
如果决定使用 $PSCmdlet.ThrowTerminatingError(),可以直接创建一个包含异常、错误标识、错误类别和目标对象的 ErrorRecord,不必先 throw 再 catch。简单的错误消息可以放进 System.Exception 的构造函数。
使用 [CmdletBinding()] 并不意味着必须替换所有 throw 和 Write-Error。具体如何选择,取决于错误要终止的范围,以及是否需要提供结构化错误信息。
直接构造 ErrorRecord
下面是一个完整的高级函数示例:
function Invoke-Example {
[CmdletBinding()]
param()
$exception = [System.Exception]::new('An error occurred!')
$errorRecord = [System.Management.Automation.ErrorRecord]::new(
$exception,
'ExampleOperationFailed',
[System.Management.Automation.ErrorCategory]::NotSpecified,
$null
)
$PSCmdlet.ThrowTerminatingError($errorRecord)
}
这里使用 ErrorRecord(Exception, String, ErrorCategory, Object) 构造函数,四个参数依次表示:
Exception:包含错误消息的异常对象。只有通用消息时,可以使用System.Exception。- 错误标识:示例中的
ExampleOperationFailed,用于标识这类失败,应尽量保持稳定。 ErrorCategory:没有更明确的类别时可以使用NotSpecified;已知失败原因时应选择相应的类别。- 目标对象:发生错误时正在处理的对象;没有相关对象时使用
$null。
ErrorRecord 除了消息,还承载错误类别、目标对象和调用信息,因此需要的信息比一个字符串更多。构造函数及相关属性见 ErrorRecord API 文档。
如果明确知道失败原因,优先选择更具体的异常类型。例如,参数值不合适时可以考虑 System.ArgumentException,操作与当前对象状态不兼容时可以考虑 System.InvalidOperationException。
如何选择 ThrowTerminatingError、throw 和 WriteError
在默认错误偏好设置下,可以按以下目的选择:
| 目的 | 常用方式 | 主要行为 |
|---|---|---|
| 当前高级函数无法继续执行 | $PSCmdlet.ThrowTerminatingError($errorRecord) | 产生语句终止错误,停止当前调用所在的语句或管道 |
| 需要让未处理的错误沿调用栈传播 | throw 'An error occurred!' | 默认产生脚本终止错误 |
| 报告当前项目失败,仍允许继续处理 | $PSCmdlet.WriteError($errorRecord) | 默认产生非终止错误 |
语句终止错误和脚本终止错误都可以被 try/catch 捕获,区别在于未捕获时的影响范围:ThrowTerminatingError() 通常允许调用方在失败语句之后继续执行;throw 默认会沿调用栈传播。错误偏好设置可能改变实际行为,两者并不完全等价。参见 PowerShell 错误处理说明。
在高级函数中使用 $PSCmdlet.WriteError() 报告非终止错误,还能让调用方的 $? 正确反映函数失败。函数内部单独调用 Write-Error 则不具备相同的状态传播效果。参见 自动变量中的 $? 说明。
如果需要报告错误后继续处理,可以将前面示例中的最后一行改为:
$PSCmdlet.WriteError($errorRecord)
调用方使用 -ErrorAction Stop 时,这类非终止错误可以升级为终止错误,因此后续代码是否执行也取决于调用方式。
原来的 try/catch 写法是否有问题
原写法能够工作:throw 产生的错误被捕获后,catch 中的 $_ 是一个 ErrorRecord,可以传给 $PSCmdlet.ThrowTerminatingError()。不过,如果只是为了创建新错误,直接构造 ErrorRecord 更清楚,也便于指定错误标识和类别。
如果要捕获底层操作的失败,可以转交已有的错误记录:
function Get-RequiredItem {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$Path
)
try {
Get-Item -LiteralPath $Path -ErrorAction Stop
}
catch {
$PSCmdlet.ThrowTerminatingError($_)
}
}
这里的 -ErrorAction Stop 用于让 Get-Item 的非终止错误进入 catch,随后由高级函数报告终止错误。如果需要在 catch 中保留 throw 的重新抛出语义,可以直接使用不带参数的 throw。两种方式应按预期的错误传播范围选择。关于传递已有错误记录的用法,参见 PowerShell 异常处理指南。
备注:内容仅供参考。
