13

在这篇精彩的文章中,Keith 解释了 Powershell 中终止错误和非终止错误之间的区别。根据 Keith 的说法,调用 .NET 对象或类型的成员所引发的异常是非终止错误。

事实上,如果我们定义这个 .NET 类进行测试:

$a = Add-Type 'public class bla { public static void bl() { throw new System.ApplicationException("test"); }}' -PassThru

然后这个函数:

function tst { 1 | write-host; $a::bl(); 2 | Write-host }

我们将看到,当调用 tst 函数时,异常似乎是非终止的:第二个Write-Host有效。

但是考虑一下:

function tst2 { try { tst } catch { "Catch!" } }

如果我们打开文档,我们可以看到 catch 响应或处理脚本中的终止错误。在文章的全文中,文章处理的错误在许多地方都被称为“终止”。

因此,当我们运行第二个 Write-Host 上方的行时,不会运行但 catch 块会运行。似乎我们的非终止错误突然变成了终止。

怎么来的?

另一个观察结果是,使用好的旧陷阱仍然是非终止错误:

function tst3 { tst trap { "Trap!" } }

现在,从实际的角度来看,我想要实现的目标如下。在一段代码中,我想终止 .NET 代码引发的异常。我想保留来自 cmdlet 终止的终止错误和来自 cmdlet 非终止的非终止错误。

我如何实现这一目标?

例子:

Do-Something
Call::Something()
Do-SomethingElse
Call::SomethingElse()
Do-YetMoreSomething
Call::YetMoreSomething()

我想终止上述 .NET 调用的所有异常。我还想终止 cmdlet 的终止错误。我不想因 cmdlet 的非终止错误而终止。

4

4 回答 4

11

是的,这在今年早些时候出现在 PowerShell MVP 电子邮件列表中。PowerShell 根据是否存在外部 try/catch 来更改其对 .NET 异常的错误处理行为。这只是猜测,但我猜这是针对简单的脚本场景。也就是说,如果脚本编写者(管理员)搞砸了调用 .NET 方法并生成异常,PowerShell 团队不希望它停止整个脚本的执行。一旦 V2 出现并引入了适当的 try/catch,我猜他们必须重新考虑该决定并提出当前的折衷方案。

也就是说,正如您所发现的那样,解决这个问题是一种痛苦。您可以Stop在脚本级别将 $ErrorActionPreference 设置为,然后为每个可以生成非终止错误的 cmdlet 使用该-ErrorAction Continue参数。或者,您可以将所有 .NET 调用放在一个高级函数中,然后使用参数调用该函数-ErrorAction Stop。我希望有一个更好的答案,但是在查看了那个 MVP 线程之后,我没有看到任何更好的解决方案。

于 2013-07-25T04:56:19.807 回答
4

Try块将 .NET 异常转换为终止错误,因此您可以将 .NET 调用包含在这样的块中:

Try { Class::RunSomeCode() } Catch { Throw }

因此,在您的示例中,您的tst功能变为

function tst { 1 | write-host; Try { $a::bl() } Catch { Throw } 2 | Write-host }

您的 .NET 异常现在将终止。

于 2014-12-10T14:52:15.193 回答
4

为了补充基思希尔的有用答案

PowerShell 的错误处理很复杂,而且由于文档将两种类型的终止错误混为一谈,事情变得更糟:

  • 语句终止错误:

    • 默认情况下,它们仅中止当前语句继续执行(使用下一条语句)。
  • 脚本终止错误:

    • 默认情况下,它们是致命的:它们中止整个脚本(更准确地说,是整个调用堆栈);直接在 PowerShell 中生成它们的唯一方法是使用Throw语句。

顺便说一句:第三类错误是非终止错误,其主要目的是允许管道处理 cmdlet 报告与特定输入对象有关的错误,其中此类错误不会绝对阻止进一步输入对象的潜在成功处理。


未处理的 .NET 异常是语句终止错误,就像cmdlet报告的语句终止错误一样。

因此,您不能从根本上使用try/以不同的方式处理它们catch(它作用于语句终止和脚本终止错误,但非终止错误):

虽然您可以测试块中可用的实例,因为它的属性[ErrorRecord]类型为 ( ), 但要恢复 cmdlet 引发的语句终止错误的默认行为为时已晚,因为块已经退出。$_catch.Exception[System.Management.Automation.MethodInvocationExeption]
$_.Exception -is [System.Management.Automation.MethodInvocationExeption]
try


将所有语句终止错误视为致命错误

我想终止上述 .NET 调用的所有异常。我还想终止 cmdlet 的终止错误。我不想因 cmdlet 的非终止错误而终止。

在您的脚本中使用以下内容:

# Escalate any statement-terminating error to a script-terminating one,
# but leave non-terminating ones alone.
# (Already script-terminating errors still terminate the script.)
trap { break }

trap,如try/ catch,作用于语句终止和脚本终止错误(但不是非终止错误)。

break为了终止脚本,使用是至关重要的;默认情况下,或者当您使用 时,会继续continue执行导致错误的语句之后的下一条语句,这甚至适用于脚本终止错误;另外抑制错误记录的输出(尽管它仍然在自动收集中收集)。continue$Error


所有错误视为致命错误

注意:外部程序通过其退出代码发出的失败信号,反映在自动变量$LASTEXITCODE中, PowerShell从不将其视为错误(但是,正在讨论更改的选择加入机制;此外,当外部程序的 stderr 输出时,存在极端情况间接触发 PowerShell 错误 - 请参阅此 GitHub 问题)。

环境:

 # !! Covers many, but NOT ALL scenarios - see below.
 $ErrorActionPreference = 'Stop'

几乎足以使所有错误类型默认为致命(脚本终止),但遗憾的是,并非在所有情况下都如此

注意:记录的行为声称$ErrorActionPreference 偏好变量仅作用于非终止错误,但实际上它也作用于语句终止错误;相比之下,公共参数确实只作用于非终止错误。 因此,您不需要偏好变量与.-ErrorAction
trap { break }

假设,因此默认情况下,上述所有PowerShell 错误都是致命的,同时允许您有 选择地覆盖该行为:

  • 对于非终止错误:在每个命令的基础上使用公共参数-ErrorAction Continue/SilentlyContinue/Ignore(再次注意,这不适用于语句/脚本终止错误)
  • 对于语句/脚本终止错误:带有命令封闭try/catch

然而,在实践中,对于碰巧在脚本模块中作为高级功能实现的命令,该方法会失败,其中包括生成的脚本模块,这些脚本模块包装远程 cmdlet 调用以进行隐式远程处理

由于变量范围在脚本模块中的工作方式,位于(不同)脚本模块中的函数看不到调用者的首选项变量(编译的 cmdlet 没有这个问题):请参阅这个 GitHub 问题

换句话说:

  • 来自不同模块的函数将有效地忽略调用者的$ErrorActionPreference = 'Stop'值。
  • 您甚至可能看不到它的到来,因为不清楚哪些命令是作为二进制 cmdlet 实现的,哪些是作为函数实现的。
  • 即使您确实知道哪些命令需要解决方法,该解决方法也很麻烦 - 请参阅我的这个答案

这个GitHub 文档问题试图全面概述 PowerShell 的错误处理及其陷阱

于 2018-05-14T15:13:15.630 回答
1

经过一些测试,似乎/块之外 抛出的异常以一种特殊的方式表现,这既不是终止也不是非终止错误。trycatch

比较以下输出:

# Functions which create simple non-terminating errors.
function E1 { [CmdletBinding()] param() Write-Error "Error"; "E1" }
function E2 { [CmdletBinding()] param() Write-Error "Error" -ErrorAction:Stop; "E2" }

# Functions which throw .NET exceptions, inside and outside try/catch blocks.
function F1 { [CmdletBinding()] param() [DateTime]""; "F1" }
function F2 { [CmdletBinding()] param() try { [DateTime]"" } catch { throw } "F2" }

# Test functions.
function TestE1 { [CmdletBinding()] param() E1; "TestE1" }
function TestF1 { [CmdletBinding()] param() F1; "TestF1" }
function TestE2 { [CmdletBinding()] param() E2; "TestE2" }
function TestF2 { [CmdletBinding()] param() F2; "TestF2" }
function StopE1 { [CmdletBinding()] param() E1 -ErrorAction:Stop; "StopE1" }
function StopF1 { [CmdletBinding()] param() F1 -ErrorAction:Stop; "StopF1" }
function StopE2 { [CmdletBinding()] param() E2 -ErrorAction:Stop; "StopE2" }
function StopF2 { [CmdletBinding()] param() F2 -ErrorAction:Stop; "StopF2" }

# All the following call pairs display similar behavior.
TestE1        # Returns "E1", "TestE1".
TestF1        # Returns "F1", "TestF1".

TestE2        # Halts and returns nothing.
TestF2        # Halts and returns nothing.

StopE2        # Halts and returns nothing.
StopF2        # Halts and returns nothing.

# The following display different behavior.
StopE1        # Halts and returns nothing.
StopF1        # F1 halts but StopF1 doesn't; the call returns "StopF1".

这表明在try/catch块之外抛出的 .NET 异常不是非终止错误,或者至少不是由Write-Error.

为了获得一致的结果,到目前为止,唯一的方法是catch异常处理,要么重新throw处理(创建终止错误) ,要么重新处理(创建Write-Error非终止错误)。例如:

function F1 { [CmdletBinding()] param() try { [DateTime]"" } catch { Write-Error -ErrorRecord:$_ } "F1" }

# Now everything's fine.
TestE1        # Returns "E1", "TestE1".
TestF1        # Returns "F1", "TestF1".

StopE1        # Halts and returns nothing.
StopF1        # Halts and returns nothing.
于 2014-02-01T19:03:31.120 回答