在 powershell 模块函数中发出错误的正确方法是什么?

What's the right way to emit errors in powershell module functions?

我已经阅读了很多关于 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 个任务要完成,但如果其中任何一个任务失败,我将无法继续执行剩余的任务。 我应该如何产生错误(最佳实践)以使其针对此特定计算机名称停止 "process" 但有效地继续处​​理管道?

使用 Try...Catch - 确保所有命令都使用 -ErrorAction Stop 开关或将环境设置为在出现错误时停止,例如$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 { ... }
  • 如果你想继续处理来自管道的输入,你必须发出一个非终止 错误:

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

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

        • try { <#task 1 #>; ... } catch { Write-Error -ErrorRecord $_ }
      • 不幸的是,从 PowerShell Core 7.0.0-preview.4 开始,Write-Error 并未完全按预期运行,因为它没有设置自动成功状态变量 $? 到调用者上下文中的 $false,这是应该的。目前唯一的解决方法是确保您的 function/script 是 advanced one and to use $PSCmdlet.WriteError(); from a catch block you can simply use $PSCmdlet.WriteError($_), but crafting your own error from scratch is cumbersome - see GitHub issue #3629.

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

    • throw 产生终止错误。
      • 不幸的是,throw 产生了比二进制 cmdlet 发出的 更基​​本的 类型的终止错误:statement-terminating errors emitted by (compiled) cmdlets, throw creates a script-terminating (fatal ) 错误.

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

  • 至于$ErrorActionPreference = 'Stop'

    • 这会将所有错误类型变成脚本-终止错误,并且至少高级 功能/脚本 - 那些预期像 cmdlet 一样工作的 - 应该 设置它 .

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

      • 警告模块中的函数查看调用者的偏好变量,如果调用者在模块外部或不同模块中 - 这个基本问题在 GitHub issue #4568.
      • 中讨论
  • 至于通过传递错误/重新打包它们来自您的函数脚本:

    • 非终止错误自动通过。

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

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

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

      • ... 使用 $PSCmdlet.ThrowTerminatingError($_) 而不是 throw 将错误中继为 语句 -终止语句。

进一步阅读

  • 有关何时发出终止错误与非终止错误的指南在 this answer.

  • GitHub docs issue #1583.

    中全面概述了 PowerShell 的错误处理