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

Syn*_*tik 3 powershell powershell-5.0

我已经阅读了很多关于 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 { ... }
Run Code Online (Sandbox Code Playgroud)

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

mkl*_*nt0 5

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

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

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

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

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

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

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

  • 至于 $ErrorActionPreference = 'Stop'

    • 这将打开所有的错误类型为脚本-terminating错误,至少先进的功能/脚本-那些有望像cmdlet的-应该不会设置

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

      • 警告:功能模块没有看到来电者的偏好变量,如果主叫方是一个模块外或在不同的模块-这一根本问题在讨论GitHub的问题#4568
  • 至于从函数脚本内部传递错误/重新打包它们

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

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

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

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

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

进一步阅读