【问题标题】:Throw an exception in powershell with nesting original error在嵌套原始错误的PowerShell中引发异常
【发布时间】:2015-06-03 16:51:45
【问题描述】:

我是一名 C# 开发人员,正在尝试使用 PowerShell 构建一些有用的东西。这就是为什么我一直尝试在 PowerShell 中使用来自 .NET 世界的知名习语。

我正在编写一个具有不同抽象层的脚本:数据库操作、文件操作等。有时我想捕捉一个错误并将其包装成对最终用户更有意义的东西。这是 C#/Java/C++ 代码的常见模式:

Function LowLevelFunction($arg)
{
  # Doing something very useful here!
  # but this operation could throw
  if (!$arg) {throw "Ooops! Can't do this!"}
}

现在,我想调用这个函数并包装一个错误:

Function HighLevelFunction
{
  Try
  {
     LowLevelFunction
  }
  Catch
  {
     throw "HighLevelFunction failed with an error!`nPlease check inner exception for more details!`n$_"
  }
}

这种方法几乎是我需要的,因为HighLevelFunction会抛出新的错误,而原来错误的根本原因会丢失!

在 C# 代码中,我总是可以抛出新异常并将原始异常作为内部异常提供。在这种情况下,HighLevelFunction 将能够以对客户更有意义的形式传达他们的错误,但仍会提供内部详细信息用于诊断目的。

在 PowerShell 中打印原始异常的唯一方法是使用存储所有异常的 $Error 变量。这没关系,但我的脚本的用户(现在是我自己)应该做更多我想做的事情。

所以问题是:有没有办法在 PowerShell 中引发异常并将原始错误作为内部错误提供?

【问题讨论】:

  • 可以在PowerShell中抛出异常:try { ... } catch { throw (New-Object System.Exception 'Some message.',$_.Exception) }
  • 谢谢,@PetSerAl。有没有“powershell方式”来做到这一点?
  • 恕我直言,大多数“PowerShell 方式”将使用 $PSCmdlet.WriteError$PSCmdlet.ThrowTerminatingError,它们可用于高级功能。

标签: powershell


【解决方案1】:

不幸的是,如this answer 所述,从catch 块引发新异常时,脚本堆栈跟踪(ErrorRecord.ScriptStackTrace) 将被重置为throw 的位置。这意味着内部异常的根源将丢失,使得复杂代码的调试变得更加困难。

还有一个替代解决方案,它使用ErrorRecord.ErrorDetails 定义高级消息并使用$PSCmdlet.WriteError() 保留脚本堆栈跟踪。它要求代码写成advanced function cmdlet。该解决方案不使用嵌套异常,但仍满足要求“捕获错误并将其包装成对最终用户更有意义的东西”

#------ High-level function ----------------------------------------------

function Start-Foo {
    [CmdletBinding()] param()

    try {
        # Some internal code that throws an exception
        Get-ChildItem ~foo~ -ErrorAction Stop
    }
    catch {
        # Define a more user-friendly error message.
        # This sets ErrorRecord.ErrorDetails.Message
        $_.ErrorDetails = 'Could not start the Foo'

        # Rethrows (if $ErrorActionPreference is 'Stop') or reports the error normally, 
        # preserving $_.ScriptStackTrace.
        $PSCmdlet.WriteError( $_ )
    }
}

#------ Code that uses the high-level function ---------------------------

$DebugPreference = 'Continue'   # Enable the stack trace output

try {
    Start-Foo -ErrorAction Stop
}
catch {
    $ErrorView = 'NormalView'   # to see the original exception info
    Write-Error -ErrorRecord $_
    ''
    Write-Debug "`n--- Script Stack Trace ---`n$($_.ScriptStackTrace)" -Debug
}

输出:

D:\my_temp\ErrorDetailDemo.ps1:无法启动 Foo + CategoryInfo : ObjectNotFound: (D:\my_temp\~foo~:String) [Write-Error], ItemNotFoundException + FullyQualifiedErrorId : PathNotFound,ErrorDetailDemo.ps1 调试: --- 脚本堆栈跟踪 --- 在 Start-Foo,C:\test\ErrorDetailDemo.ps1:第 5 行 在,C:\test\ErrorDetailDemo.ps1:第 14 行

我们的高级错误消息“无法启动 Foo”隐藏了底层异常的错误消息,但不会丢失任何信息(您可以在 catch 处理程序中通过 $_.Exception.Message 访问原始错误消息)。

注意:还有一个字段ErrorDetails.RecommendedAction,您可以根据需要设置它。为简单起见,我没有在示例代码中使用它,但您可以将其设置为 $_.ErrorDetails.RecommendedAction = 'Install the Foo'

【讨论】:

  • 我喜欢这个答案,因为它使用提供的语言功能解决了需求。 Powershell 有一个复杂的机制,它与流的概念相结合,有助于提高对语言习语的认识。
【解决方案2】:

您可以在 catch 块中引发新异常并指定基本异常:

# Function that will throw a System.IO.FileNotFoundExceptiopn
function Fail-Read {
  [System.IO.File]::ReadAllLines( 'C:\nonexistant' )
}

# Try to run the function
try {
  Fail-Read
} catch {
  # Throw a new exception, specifying the inner exception
  throw ( New-Object System.Exception( "New Exception", $_.Exception ) )
}

# Check the exception here, using getBaseException()
$error[0].Exception.getBaseException().GetType().ToString()

【讨论】:

  • 输出包含所有嵌套异常的异常:try{ CodeThatThrows } catch { $_.Exception.ToString() }。这会输出异常类型和消息。也可以添加$_.ScriptStackTrace 来获取堆栈跟踪。
  • 不幸的是,像这样抛出一个新异常会丢弃$_.ScriptStackTrace,因此内部异常的起源将丢失。脚本堆栈跟踪将从throw 的位置开始。我还没有找到好的解决方法。
  • 您不能将原始堆栈跟踪存储在一个新变量中并引用它吗?或者它是对在抛出新异常时被擦除的对象的引用?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多