Str*_*iax 5 error-handling powershell standards
因此,考虑到有四种*方法来报告错误(不包括$ErrorActionPreference、try/catch和trap块的影响,它们有可能改变另一个源报告错误的方式),PowerShell 错误报告似乎是一个巨大的混乱,分钟和糟糕记录了差异,更糟糕的是,没有关于何时使用不同类型的真正说明。(我意识到这会因情况而异,但“标准”是什么——其他人会期望我做什么?)
但这并不是真正的重点。我决定尽可能使用最简单的选项,因为这是我期望大多数其他人都会使用的选项,所以至少我们会保持一致性 - 但我要么没有正确理解某些内容,要么它已损坏。
根据PowerShell 文档,类Activity的属性ErrorCategoryInfo(在 的CategoryInfo属性中观察到System.Management.Automation.ErrorRecord)应该是“遇到错误的操作的文本描述”。考虑到该值是可设置的,并且(在编译的 cmdlet 的情况下)指示 cmdlet 的名称,除非另有指定,我希望基于脚本的错误报告方法具有相同的功能,但我做错了,或者我我们发现 PowerShell 中存在一个自 PowerShell 1.0 以来就普遍存在的错误。
考虑以下示例:
using namespace System.Management.Automation;
function Invoke-Error1 {
[CmdletBinding()]
param()
process {
$ex = [InvalidOperationException]::new()
$er = [ErrorRecord]::new($ex, 'OperationTest', [ErrorCategory]::InvalidOperation, 'MyErrorTarget')
$er.CategoryInfo.Activity = 'MyActivity'
Write-Error -ErrorRecord $er
}
}
Run Code Online (Sandbox Code Playgroud)
调用此函数将导致以下错误。
invoke-error1 : Operation is not valid due to the current state of the object.
At line:1 char:1
+ invoke-error1
+ ~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (MyErrorTarget:String) [Write-Error], InvalidOperationException
+ FullyQualifiedErrorId : OperationTest,Invoke-Error1
Run Code Online (Sandbox Code Playgroud)
进一步调查时,您会注意到该CategoryInfo.Activity属性已被替换为Write-Error。我设置的值(“MyActivity”)将被忽略。如果这是预料之中的,我不会期望 Write-Error 方法上有一个参数来设置错误类别 - 但似乎存在一个参数。(参见Get-Help Write-Error -Parameter CategoryActivity。我看不出这可能还有什么其他用途。)
这可以通过以下函数来确认。
function Invoke-Error2 {
[CmdletBinding()]
param()
process {
$Params = @{
Exception = [InvalidOperationException]::new()
ErrorId = 'OperationTest'
Category = [ErrorCategory]::InvalidOperation
TargetObject = 'MyErrorTarget'
CategoryActivity = 'MyActivity'
}
Write-Error @Params
}
}
Run Code Online (Sandbox Code Playgroud)
Invoke-Error1除了 cmdlet 的名称和关联的 InitationInfo 之外,报告的错误在所有方面都与错误相同。具体来说,请再次注意,$Error[0].CategoryInfo.Activity -eq 'Write-Error'而不是“MyActivity”。
然而,有一种编写错误的方法可以正确地编写带有错误的错误活动...但它似乎是报告错误的推荐方法列表中最低的,因为它通常与 Write-Error 相同。
function Invoke-Error3 {
[CmdletBinding()]
param()
process {
$ex = [InvalidOperationException]::new()
$er = [ErrorRecord]::new($ex, 'OperationTest', [ErrorCategory]::InvalidOperation, 'MyErrorTarget')
$er.CategoryInfo.Activity = 'MyActivity'
$PSCmdlet.WriteError($er)
}
}
Run Code Online (Sandbox Code Playgroud)
这个函数在所有方面都是相同的,Invoke-Error1除了我使用$PSCmdlet.WriteError而不是Write-Error。根据某些社区标准,最好使用内置函数而不是 .NET 调用,当然,当行为不同时,使用产生所需结果的过程是有意义的。
这是与任何已编译的 cmdlet 写入错误的方式最相似的方法。因此,cmdlet 写入的任何错误都将使用手动设置的Activity值,或使用 cmdlet 名称的默认值。我相信后者在某种程度上是发生在Write-Error.
话虽这么说,我不觉得我在这里错过了什么。自 PowerShell 1.0 起, 的属性CategoryInfo和关联CategoryActivity参数Write-Error就已存在。当然,如果这个功能从来没有起作用,现在有人会注意到吗?那么我做错了什么?我该如何正确报告错误?回到 属性的目的Activity,ErrorCategoryInfo“Write-Error”并不是遇到错误的操作 - 我的函数遇到了错误,并且我想使用 来 Write-Error报告它。这可能吗?
*如果您不知道,PowerShell 脚本或脚本函数可能报告的错误如下:
throw)$PSCmdlet.ThrowTerminatingError)Write-Error)$PSCmdlet.WriteError)。这很难区分,但行为确实不同 - 请注意这个问题,以及它不会影响直接发出的错误的行为的Write-Error事实(尽管如果直接源是脚本,则会影响间接发出的错误的行为) 。这可以通过在继续调用vs的函数内部进行设置来测试$ErrorActionPreference$PSCmdlet.WriteError$ErrorActionPreference$PSCmdlet.WriteError$ErrorActionPreference$PSCmdlet.WriteErrorWrite-Error。$ErrorActionPreference或-ErrorAction公共参数转换为语句终止错误。该$?变量可用于错误报告,但根据该变量结果的变化程度,这似乎是一种糟糕的方法。try/catch块可用于捕获错误并通过$PSItem变量访问它们,但在该块内,如果您从模块中定义的函数执行,您可能会或可能不会看到错误,$Error具体取决于报告错误的命令是否在您的模块中定义。写入捕获的异常的渗透可能会产生两个错误,这些错误被添加到变量中-如果捕获异常的命令是在错误所在的模块之外的模块中定义的,则$global:Error我希望忽略的捕获的异常将被放置在其中$global:Error抛出的错误,以及我报告的新错误。首先,感谢您的深入分析。
您已经找到了一种有效的解决方法,该方法也适用于 Windows PowerShell来尊重您的自定义.Activity值:使用$PSCmdlet.WriteError()- 但请注意,它仅在高级函数$PSCmdlet和脚本中可用。
如果某个.Activity值是完整传递给 参数的实例的一部分,则该值不会受到尊重,而现在如果您在 PowerShell (Core)7+ 中使用该参数(较短的别名:) ,则不会受到尊重,这可以说是我发现的一个错误鼓励您在PowerShell GitHub 存储库中进行报告。System.Management.Automation.ErrorRecordWrite-Error-ErrorRecord-CategoryActivity-Activity
注意:正如您所指出的,以下替代解决方法仅在PowerShell (Core) 7+中有效:
一个稍微不那么晦涩的解决方法不需要$PSCmdlet,因此也可以在非高级函数和脚本中使用:
using namespace System.Management.Automation
$ex = [InvalidOperationException]::new()
$er = [ErrorRecord]::new($ex, 'OperationTest', [ErrorCategory]::InvalidOperation, 'MyErrorTarget')
$er.CategoryInfo.Activity = 'MyActivity'
# Workaround: Specify the activity *also* via -Activity
Write-Error -ErrorRecord $er -Activity $er.CategoryInfo.Activity
Run Code Online (Sandbox Code Playgroud)
注意:要查看结果错误(记录)的友好表示,请将以上内容通过管道传输到
2>&1 | Format-List -Force; 在 PowerShell (Core) 7+ 中,您也可以使用专用Get-Errorcmdlet。
或者,您可以绕过Together的显式[ErrorRecord]构造,并使用不同参数的组合来实现相同的结果:
Write-Error -Exception InvalidOperationException `
-ErrorId OperationTest `
-Activity MyActivity `
-TargetObject MyErrorTarget
Run Code Online (Sandbox Code Playgroud)
我发现 PowerShell 中存在一个自 PowerShell 1.0 以来一直存在的错误。
看起来是这样。
最可能的解释是,在 PowerShell 代码中直接构建需要高级知识,而且很少见 - 事实上,让为您构建这样的实例是其主要目的,提供带有参数的cmdlet 的熟悉的命令行体验,如上面最后的解决方法。[ErrorRecord] Write-Error
另一个因素可能是很少有处理错误记录的调用者会关注诸如.CategoryInfo.Activity;之类的字段。.ErrorId通常,考虑的是字段或包装异常的 .NET 类型。
从更哲学的角度来看,人们可能会认为这种ErrorRecord类型有点过度设计。
至于你更一般的观察:
没有关于何时使用不同类型[错误]的真正说明
GitHub 文档发布《我们的错误处理,我们自己 - 是时候充分理解并正确记录 PowerShell 的错误处理》,旨在提供 PowerShell 极其复杂且部分不一致的错误处理的概述,并要求此类概述成为文档的正式部分。
这个答案试图提供有关何时使用非终止错误和语句终止错误的指导。
throw在PowerShell 代码中仅创建前者,而.ThrowTerminatingError()(通常仅在 C# 代码中使用)仅创建后者,这是一种令人困惑的不对称- GitHub 问题 #14819 上的评论提出了放弃这种区别的理由,转而在未来的 PowerShell 版本中仅提供 脚本终止错误 - 即唯一一种终止错误将是默认情况下致命的错误。这样做会:
-ErrorAction参数之间令人困惑的不对称性,对语句终止错误没有影响,而首选项变量$ErrorActionPreference,当设置为 时'Stop',会影响(将它们提升为脚本终止错误)。| 归档时间: |
|
| 查看次数: |
1643 次 |
| 最近记录: |