.NET方法的例外是终止或不终止错误吗?

时间:2013-07-25 01:02:21

标签: powershell exception error-handling

在此great article中,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!" } }

如果我们打开the documentation,我们可以读到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 个答案:

答案 0 :(得分:9)

是的,今年早些时候出现在PowerShell MVP电子邮件列表中。 PowerShell更改其.NET异常的错误处理行为,具体取决于是否存在外部try / catch。这只是推测,但我猜这是针对简单的脚本方案。也就是说,如果脚本编写者(admin)混淆了调用.NET方法并产生异常,那么PowerShell团队不希望停止执行整个脚本。一旦V2出现并引入了正确的尝试/捕获,我猜他们必须重新审视这个决定并提出当前的妥协。

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

答案 1 :(得分:3)

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

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

因此,在您的示例中,您的tst函数变为

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

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

答案 2 :(得分:1)

经过一些测试后,似乎在 try / catch块之外抛出的异常行为以一种特殊的方式运行,这既不是终止错误也不是非终止错误。

比较以下内容的输出:

# 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异常是非终止错误,或者至少不是由{{{{}}生成的同类错误1}}。

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

Write-Error

答案 3 :(得分:0)

补充Keith Hill's helpful answer

PowerShell的错误处理非常复杂,因为文档会混淆两种类型的终止错误,所以事情会变得更糟:

  • 声明 - 终止错误:

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

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

暂且不说:第三种类型的错误是非终止错误,其主要目的是允许管道处理cmdlet报告与特定输入对象相关的错误,此类错误不会明确阻止进一步输入对象的潜在成功处理。

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

因此,使用try / catch(它对语句终止和脚本终止错误起作用,但非非终止错误)不能从根本上处理它们:

虽然您可以在[ErrorRecord]块中测试$_ catch .Exception [System.Management.Automation.MethodInvocationExeption]类型$_.Exception -is [System.Management.Automation.MethodInvocationExeption]属性的try实例。 (# 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 } ),
太晚了以恢复cmdlet发起的语句终止错误的默认行为,因为trap块已经退出。

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

  

我想终止上面.NET调用的所有异常。我还想终止从cmdlet终止错误。我不想终止cmdlet中的非终止错误。

在脚本中使用以下内容:

try

catchbreak / continue一样,对语句终止和脚本终止错误(但不是非终止错误)起作用。

使用continue对于终止脚本至关重要;默认情况下,或者当您使用$Error时,执行继续与导致错误的语句之后的下一个语句,这甚至适用于 script - 终止错误; $LASTEXITCODE还会抑制错误记录的输出(尽管它仍会在自动 # !! Covers many, but NOT ALL scenarios - see below. $ErrorActionPreference = 'Stop' 集合中收集)。

所有错误视为致命

注意:外部程序通过退出代码发出的失败信号(自动变量$ErrorActionPreference中反映出来)被PowerShell 从未视为错误(但是,{ {3}};另外,当外部程序的stderr输出间接触发PowerShell错误时会出现边缘情况 - 请参阅opt-in mechanism to change that is being discussed)。

环境:

-ErrorAction

几乎足以使所有错误类型致命 - 默认(脚本终止),但遗憾的是,并非全部的情况。

注意:已记录的行为声称trap { break } 首选项变量仅对非终止错误起作用,但实际上它会作用于语句< / em> -terminating ones 以及;相比之下,-ErrorAction Continue/SilentlyContinue/Ignore 通用参数确实只对非终止错误起作用。
因此,您不必严格要求首选项变量与try结合使用。

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

  • 对于非终止错误:在每个命令的基础上使用公共参数catch(再次注意,对语句/脚本执行操作 - 终止错误)
  • for statement / script-terminatedating 错误:使用命令封闭$ErrorActionPreference = 'Stop' / {{1}}

在实践中,对于在脚本模块中高级函数实现的命令,该方法失败 < / strong>,包括生成的脚本模块,用于包装远程cmdlet调用隐式远程处理

由于变量作用域在脚本模块中的工作方式,位于(不同)脚本模块中的函数看不到调用者的首选项变量(编译的cmdlet没有此问题):请参阅this GitHub issue。< / p>

换句话说:

  • 来自其他模块的功能将有效忽略来电者的{{1}}值。
  • 你可能甚至看不到它,因为不明显哪些命令是作为二进制cmdlet实现的,哪些是函数。
  • 即使您确实知道哪些命令需要解决方法,这种解决方法也很麻烦 - 请参阅我的this GitHub issue

this answer尝试对PowerShell的错误处理及其缺陷进行全面概述