首页 > 解决方案 > 在 powershell 模块函数中发出错误的正确方法是什么?

问题描述

我已经阅读了很多关于 powershell 错误处理的内容,现在我很困惑在任何给定情况下我应该做什么(错误处理)。我正在使用 powershell 5.1(不是核心)。话虽如此:假设我有一个模块,其函数看起来像这样模拟:

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 { ... }

假设对于传递给此函数的每个计算机名称,我有 4 个任务要完成,但如果任何任务失败,我将无法继续执行剩余的任务。我应该如何产生错误(最佳实践),以便它停止此特定计算机名称的“进程”但有效地继续处​​理管道?

标签: powershellpowershell-5.0

解决方案


  • 如果要继续处理来自管道的输入,则必须发出非终止错误

    • Write-Error写入非终止错误;它写入 PowerShell 的错误流,而不会在后台生成异常;继续正常执行。

      • 如果 .NET 方法调用是错误源,如您的情况,请将其包装在try/中,然后在块中catch调用:Write-Error -ErrorRecord $_catch

        • try { <#task 1 #>; ... } catch { Write-Error -ErrorRecord $_ }
      • 不幸的是,仍然从 PowerShell Core 7.0.0-preview.4 开始,Write-Error它的行为并没有完全符合预期,因为它没有在调用者的上下文中将自动成功状态变量 , 设置为应有的$?值。$false目前唯一的解决方法是确保您的函数/脚本是高级的并使用$PSCmdlet.WriteError(); 从一个catch你可以简单地使用的$PSCmdlet.WriteError($_)开始,但是从头开始制作你自己的错误很麻烦 - 请参阅GitHub 问题 #3629

  • 如果您想立即停止处理,请使用终止错误

    • throw产生终止错误。
      • 不幸的是,它会产生一种比二进制 cmdlet 发出throw的更基本的终止错误:与(编译的)cmdlet 发出的语句终止错误不同,throw它会产生脚本终止(致命)错误

        • 也就是说,默认情况下,二进制 cmdlet 的语句终止错误只会终止手头的语句(管道)并继续执行封闭脚本,而throw默认情况下会中止整个脚本(及其调用者)。
        • GitHub 问题 #14819讨论了这种不对称性。
      • 同样,解决方法要求您的脚本/函数是高级脚本/函数,它使您能够调用$PSCmdlet.ThrowTerminatingError()而不是throw正确地生成语句终止错误;与 一样$PSCmdlet.WriteError(),您可以简单地使用$PSCmdlet.ThrowTerminatingError($_) from a catchblock,但是从头开始制作自己的语句终止错误很麻烦。

  • 至于$ErrorActionPreference = 'Stop'

    • 这会将所有错误类型转换为脚本终止错误,并且至少高级函数/脚本(预期行为类似于 cmdlet)不应设置

    • 相反,让您的脚本/函数发出适当类型的错误,并让调用者通过公共-ErrorAction参数或$ErrorActionPreference变量控制对它们的响应。

      • 警告:模块中的函数看不到调用者的偏好变量,如果调用者在模块之外或在不同的模块中 - 这个基本问题在GitHub 问题 #4568中讨论。
  • 至于从函数脚本内部传递/重新打包错误:

    • 非终止错误会自动通过。

      • -ErrorAction Ignore如果需要,您可以使用或来抑制它们,也可以选择使用公共参数(与 结合)2>$null收集它们以供以后处理。-ErrorVariable-ErrorAction SilentlyContinue
    • 在默认情况下整个调用堆栈与您的代码一起终止的意义上,脚本终止错误被传递。

    • 语句终止错误被写入错误流,但默认情况下您的脚本/函数会继续运行。

      • 用于try { ... } catch { throw }将它们变成脚本终止错误,或者...

      • ...使用$PSCmdlet.ThrowTerminatingError($_)而不是throw将错误作为语句终止一个。

进一步阅读

  • 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 GitHub docs issue #1583.


推荐阅读