代码之家  ›  专栏  ›  技术社区  ›  alex2k8

我可以在PowerShell中获取详细的异常堆栈跟踪吗?

  •  41
  • alex2k8  · 技术社区  · 16 年前

    运行这样的脚本:

     1: function foo()
     2: {
     3:    bar
     4: }
     5: 
     6: function bar()
     7: {
     8:     throw "test"
     9: }
    10: 
    11: foo
    

    我懂了

    test
    At C:\test.ps1:8 char:10
    

    At bar() in C:\test.ps1:8
    At foo() in C:\test.ps1:3 
    At C:\test.ps1:11
    
    8 回复  |  直到 16 年前
        1
  •  48
  •   Community Mohan Dere    6 年前

    有一个 function up on the PowerShell Team blog 名为Resolve Error,它将为您提供各种详细信息

    请注意,$error是PSSession中遇到的所有错误的数组。此函数将为您提供上次遇到错误的详细信息。

    function Resolve-Error ($ErrorRecord=$Error[0])
    {
       $ErrorRecord | Format-List * -Force
       $ErrorRecord.InvocationInfo |Format-List *
       $Exception = $ErrorRecord.Exception
       for ($i = 0; $Exception; $i++, ($Exception = $Exception.InnerException))
       {   "$i" * 80
           $Exception |Format-List * -Force
       }
    }
    
        2
  •  20
  •   Joey Gumbo    16 年前

    这是自动变量 $StackTrace 但它似乎比实际关心您的脚本更具体地关注内部PS细节,所以这不会有多大帮助。

    还有 Get-PSCallStack 但不幸的是,一旦你遇到异常,它就消失了。然而,你可以放一个 获取PSCallStack

        3
  •  19
  •   Tim Sparkles Andrew Barrett    11 年前

    Powershell 3.0将ScriptStackTrace属性添加到ErrorRecord对象。我使用此功能进行错误报告:

    function Write-Callstack([System.Management.Automation.ErrorRecord]$ErrorRecord=$null, [int]$Skip=1)
    {
        Write-Host # blank line
        if ($ErrorRecord)
        {
            Write-Host -ForegroundColor Red "$ErrorRecord $($ErrorRecord.InvocationInfo.PositionMessage)"
    
            if ($ErrorRecord.Exception)
            {
                Write-Host -ForegroundColor Red $ErrorRecord.Exception
            }
    
            if ((Get-Member -InputObject $ErrorRecord -Name ScriptStackTrace) -ne $null)
            {
                #PS 3.0 has a stack trace on the ErrorRecord; if we have it, use it & skip the manual stack trace below
                Write-Host -ForegroundColor Red $ErrorRecord.ScriptStackTrace
                return
            }
        }
    
        Get-PSCallStack | Select -Skip $Skip | % {
            Write-Host -ForegroundColor Yellow -NoNewLine "! "
            Write-Host -ForegroundColor Red $_.Command $_.Location $(if ($_.Arguments.Length -le 80) { $_.Arguments })
        }
    }
    

    Skip参数允许我将Write Callstack或任意数量的错误处理堆栈帧保留在Get PSCallstack列表之外。

    请注意,如果从catch块调用Get-PSCallstack,则会丢失抛出站点和catch块之间的任何帧。因此,我更喜欢PS3.0方法,尽管我们每帧的细节更少。

        4
  •  12
  •   JasonMArcher TWE    16 年前

    $Error[0].Exception.StackTrace
    $Error[0].Exception.InnerException.StackTrace
    $Error[0].StackTrace
    
        5
  •  10
  •   user1367200    11 年前

    我把我在这里发现的东西作为灵感,创建了一个很好的函数,任何人都可以把它放到他们的代码中。

    写入主机“未能写入日志文件`n$(解决错误)”-ForegroundColor红色

    Function Resolve-Error
    {
    <#
    .SYNOPSIS
        Enumerate error record details.
    
    .DESCRIPTION
        Enumerate an error record, or a collection of error record, properties. By default, the details
        for the last error will be enumerated.
    
    .PARAMETER ErrorRecord
        The error record to resolve. The default error record is the lastest one: $global:Error[0].
        This parameter will also accept an array of error records.
    
    .PARAMETER Property
        The list of properties to display from the error record. Use "*" to display all properties.
        Default list of error properties is: Message, FullyQualifiedErrorId, ScriptStackTrace, PositionMessage, InnerException
    
        Below is a list of all of the possible available properties on the error record:
    
        Error Record:               Error Invocation:           Error Exception:                    Error Inner Exception(s):
        $_                          $_.InvocationInfo           $_.Exception                        $_.Exception.InnerException
        -------------               -----------------           ----------------                    ---------------------------
        writeErrorStream            MyCommand                   ErrorRecord                         Data
        PSMessageDetails            BoundParameters             ItemName                            HelpLink
        Exception                   UnboundArguments            SessionStateCategory                HResult
        TargetObject                ScriptLineNumber            StackTrace                          InnerException
        CategoryInfo                OffsetInLine                WasThrownFromThrowStatement         Message
        FullyQualifiedErrorId       HistoryId                   Message                             Source
        ErrorDetails                ScriptName                  Data                                StackTrace
        InvocationInfo              Line                        InnerException                      TargetSite
        ScriptStackTrace            PositionMessage             TargetSite                          
        PipelineIterationInfo       PSScriptRoot                HelpLink                            
                                    PSCommandPath               Source                              
                                    InvocationName              HResult                             
                                    PipelineLength              
                                    PipelinePosition            
                                    ExpectingInput              
                                    CommandOrigin               
                                    DisplayScriptPosition       
    
    .PARAMETER GetErrorRecord
        Get error record details as represented by $_
        Default is to display details. To skip details, specify -GetErrorRecord:$false
    
    .PARAMETER GetErrorInvocation
        Get error record invocation information as represented by $_.InvocationInfo
        Default is to display details. To skip details, specify -GetErrorInvocation:$false
    
    .PARAMETER GetErrorException
        Get error record exception details as represented by $_.Exception
        Default is to display details. To skip details, specify -GetErrorException:$false
    
    .PARAMETER GetErrorInnerException
        Get error record inner exception details as represented by $_.Exception.InnerException.
        Will retrieve all inner exceptions if there is more then one.
        Default is to display details. To skip details, specify -GetErrorInnerException:$false
    
    .EXAMPLE
        Resolve-Error
    
        Get the default error details for the last error
    
    .EXAMPLE
        Resolve-Error -ErrorRecord $global:Error[0,1]
    
        Get the default error details for the last two errors
    
    .EXAMPLE
        Resolve-Error -Property *
    
        Get all of the error details for the last error
    
    .EXAMPLE
        Resolve-Error -Property InnerException
    
        Get the "InnerException" for the last error
    
    .EXAMPLE
        Resolve-Error -GetErrorInvocation:$false
    
        Get the default error details for the last error but exclude the error invocation information
    
    .NOTES
    .LINK
    #>
        [CmdletBinding()]
        Param
        (
            [Parameter(Mandatory=$false, Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
            [ValidateNotNullorEmpty()]
            [array]$ErrorRecord,
    
            [Parameter(Mandatory=$false, Position=1)]
            [ValidateNotNullorEmpty()]
            [string[]]$Property = ('Message','InnerException','FullyQualifiedErrorId','ScriptStackTrace','PositionMessage'),
    
            [Parameter(Mandatory=$false, Position=2)]
            [switch]$GetErrorRecord = $true,
    
            [Parameter(Mandatory=$false, Position=3)]
            [switch]$GetErrorInvocation = $true,
    
            [Parameter(Mandatory=$false, Position=4)]
            [switch]$GetErrorException = $true,
    
            [Parameter(Mandatory=$false, Position=5)]
            [switch]$GetErrorInnerException = $true
        )
    
        Begin
        {
            ## If function was called without specifying an error record, then choose the latest error that occured
            If (-not $ErrorRecord)
            {
                If ($global:Error.Count -eq 0)
                {
                    # The `$Error collection is empty
                    Return
                }
                Else
                {
                    [array]$ErrorRecord = $global:Error[0]
                }
            }
    
            ## Define script block for selecting and filtering the properties on the error object
            [scriptblock]$SelectProperty = {
                Param
                (
                    [Parameter(Mandatory=$true)]
                    [ValidateNotNullorEmpty()]
                    $InputObject,
    
                    [Parameter(Mandatory=$true)]
                    [ValidateNotNullorEmpty()]
                    [string[]]$Property
                )
                [string[]]$ObjectProperty = $InputObject | Get-Member -MemberType *Property | Select-Object -ExpandProperty Name
                ForEach ($Prop in $Property)
                {
                    If ($Prop -eq '*')
                    {
                        [string[]]$PropertySelection = $ObjectProperty
                        Break
                    }
                    ElseIf ($ObjectProperty -contains $Prop)
                    {
                        [string[]]$PropertySelection += $Prop
                    }
                }
                Write-Output $PropertySelection
            }
    
            # Initialize variables to avoid error if 'Set-StrictMode' is set
            $LogErrorRecordMsg      = $null
            $LogErrorInvocationMsg  = $null
            $LogErrorExceptionMsg   = $null
            $LogErrorMessageTmp     = $null
            $LogInnerMessage        = $null
        }
        Process
        {
            ForEach ($ErrRecord in $ErrorRecord)
            {
                ## Capture Error Record
                If ($GetErrorRecord)
                {
                    [string[]]$SelectedProperties = &$SelectProperty -InputObject $ErrRecord -Property $Property
                    $LogErrorRecordMsg = $ErrRecord | Select-Object -Property $SelectedProperties
                }
    
                ## Error Invocation Information
                If ($GetErrorInvocation)
                {
                    If ($ErrRecord.InvocationInfo)
                    {
                        [string[]]$SelectedProperties = &$SelectProperty -InputObject $ErrRecord.InvocationInfo -Property $Property
                        $LogErrorInvocationMsg = $ErrRecord.InvocationInfo | Select-Object -Property $SelectedProperties
                    }
                }
    
                ## Capture Error Exception
                If ($GetErrorException)
                {
                    If ($ErrRecord.Exception)
                    {
                        [string[]]$SelectedProperties = &$SelectProperty -InputObject $ErrRecord.Exception -Property $Property
                        $LogErrorExceptionMsg = $ErrRecord.Exception | Select-Object -Property $SelectedProperties
                    }
                }
    
                ## Display properties in the correct order
                If ($Property -eq '*')
                {
                    # If all properties were chosen for display, then arrange them in the order
                    #  the error object displays them by default.
                    If ($LogErrorRecordMsg)     {[array]$LogErrorMessageTmp += $LogErrorRecordMsg    }
                    If ($LogErrorInvocationMsg) {[array]$LogErrorMessageTmp += $LogErrorInvocationMsg}
                    If ($LogErrorExceptionMsg)  {[array]$LogErrorMessageTmp += $LogErrorExceptionMsg }
                }
                Else
                {
                    # Display selected properties in our custom order
                    If ($LogErrorExceptionMsg)  {[array]$LogErrorMessageTmp += $LogErrorExceptionMsg }
                    If ($LogErrorRecordMsg)     {[array]$LogErrorMessageTmp += $LogErrorRecordMsg    }
                    If ($LogErrorInvocationMsg) {[array]$LogErrorMessageTmp += $LogErrorInvocationMsg}
                }
    
                If ($LogErrorMessageTmp)
                {
                    $LogErrorMessage  = 'Error Record:'
                    $LogErrorMessage += "`n-------------"
                    $LogErrorMsg      = $LogErrorMessageTmp | Format-List | Out-String
                    $LogErrorMessage += $LogErrorMsg
                }
    
                ## Capture Error Inner Exception(s)
                If ($GetErrorInnerException)
                {
                    If ($ErrRecord.Exception -and $ErrRecord.Exception.InnerException)
                    {
                        $LogInnerMessage  = 'Error Inner Exception(s):'
                        $LogInnerMessage += "`n-------------------------"
    
                        $ErrorInnerException = $ErrRecord.Exception.InnerException
                        $Count = 0
    
                        While ($ErrorInnerException)
                        {
                            $InnerExceptionSeperator = '~' * 40
    
                            [string[]]$SelectedProperties = &$SelectProperty -InputObject $ErrorInnerException -Property $Property
                            $LogErrorInnerExceptionMsg = $ErrorInnerException | Select-Object -Property $SelectedProperties | Format-List | Out-String
    
                            If ($Count -gt 0)
                            {
                                $LogInnerMessage += $InnerExceptionSeperator
                            }
                            $LogInnerMessage += $LogErrorInnerExceptionMsg
    
                            $Count++
                            $ErrorInnerException = $ErrorInnerException.InnerException
                        }
                    }
                }
    
                If ($LogErrorMessage) { $Output += $LogErrorMessage }
                If ($LogInnerMessage) { $Output += $LogInnerMessage }
    
                Write-Output $Output
    
                If (Test-Path -Path 'variable:Output'            ) { Clear-Variable -Name Output             }
                If (Test-Path -Path 'variable:LogErrorMessage'   ) { Clear-Variable -Name LogErrorMessage    }
                If (Test-Path -Path 'variable:LogInnerMessage'   ) { Clear-Variable -Name LogInnerMessage    }
                If (Test-Path -Path 'variable:LogErrorMessageTmp') { Clear-Variable -Name LogErrorMessageTmp }
            }
        }
        End {}
    }
    
        6
  •  3
  •   Vitaliy Ulantikov Peter Miehle    6 年前

    此代码:

    try {
        ...
    }
    catch {
        Write-Host $_.Exception.Message -Foreground "Red"
        Write-Host $_.ScriptStackTrace -Foreground "DarkGray"
        exit 1
    }
    

    将以以下格式回显错误:

    No match was found for the specified search criteria and module names 'psake'.
    at Get-InstalledModule<Process>, ...\PSModule.psm1: line 9251
    at Import-ModuleThirdparty, ...\Import-ModuleThirdparty.psm1: line 3
    at <ScriptBlock>, ...\index.ps1: line 13
    
        7
  •  2
  •   Jay Bazuzi Buck Hodges    12 年前

    这里有一个方法: Tracing the script stack

    其核心是该代码:

        1..100 | %{ $inv = &{ gv -sc $_ myinvocation }
    
        8
  •  2
  •   mjquito    8 年前

    我刚想出来。$是在catch块中捕获的异常。

    $errorString= $_ | Out-String 
    
        9
  •  1
  •   Richard Meadows    6 年前

    偶然发现这一点,寻找一个内置的解决方案。我将用简单的解决方案。只需在使用任何powershell之前添加跟踪块。这将确保显示调用堆栈。下面是将在错误消息之前显示的堆栈。

    Trace {
       $_.ScriptStackTrace
    }
    
        10
  •  1
  •   JonoB    5 年前

    也许我误解了什么,但我这里的问题是,我没有看到powershell脚本堆栈对内部异常的跟踪。

    最后,我在$Global:Error中搜索异常的Error-Eecord对象,以检索脚本堆栈跟踪。

    <#
    .SYNOPSIS
       Expands all inner exceptions and provides Script Stack Traces where available by mapping Exceptions to ErrorRecords
    
    .NOTES
       Aggregate exceptions aren't full supported, and their child exceptions won't be traversed like regular inner exceptions
       
    #>
    function Get-InnerErrors ([Parameter(ValueFrompipeline)] $ErrorObject=$Global:Error[0])
    {
       # Get the first exception object from the input error
       $ex = $null
       if( $ErrorObject -is [System.Management.Automation.ErrorRecord] ){
           $ex = $ErrorObject.Exception 
       }
       elseif( $ErrorObject -is [System.Exception] ){
           $ex = $ErrorObject
       } 
       else
       {
           throw "Unexpected error type for Get-InnerErrors: $($ErrorObject.GetType()):`n $ErrorObject"
       }
    
       Write-Debug "Walking inner exceptions from exception"
       for ($i = 0; $ex; $i++, ($ex = $ex.InnerException))
       {  
           $ErrorRecord = $null
    
           if( $ex -is [System.Management.Automation.IContainsErrorRecord] ){ 
    
               Write-Debug "Inner Exception $i : Skipping search for ErrorRecord in `$Global:Error, exception type implements IContainsErrorRecord" 
               $ErrorRecord = ([System.Management.Automation.IContainsErrorRecord]$ex).ErrorRecord
           }
           else {
    
               # Find ErrorRecord for exception by mapping exception to ErrorRecrods in $Global:Error
    
               ForEach( $err in $Global:Error ){# Need to use Global scope when referring from a module
                   if( $err -is [System.Management.Automation.ErrorRecord] ){
                      if( $err.Exception -eq $ex ){
                           $ErrorRecord = $err 
                           Write-Debug "Inner Exception $i : Found ErrorRecord in `$Global:Error" 
                           break
                       }
                   } 
                   elseif( $err -is [System.Management.Automation.IContainsErrorRecord] ) {
                       if( $err -eq $ex -or $err.ErrorRecord.Exception -eq $ex ){
                           $ErrorRecord = $err.ErrorRecord
                           Write-Debug "Inner Exception $i : Found ErrorRecord in `$Global:Error" 
                           break
                       }
                   } 
                   else {
                       Write-Warning "Unexpected type in `$Global:Error[]. Expected `$Global:Error to always be an ErrorRecrod OR exception implementing IContainsErrorRecord but found type: $($err.GetType())"
                   }
               }
           }
    
           if( -not($ErrorRecord) ){
               Write-Debug "Inner Exception $i : No ErrorRecord could be found for exception of type: $($ex.GetType())" 
           }
           
          
           # Return details as custom object
    
           [PSCustomObject] @{
               ExceptionDepth      = $i
               Message             = $ex.Message
               ScriptStackTrace    = $ErrorRecord.ScriptStackTrace  # Note ErrorRecord will be null if exception was not from Powershell
               ExceptionType       = $ex.GetType().FullName
               ExceptionStackTrace = $ex.StackTrace
           }
       }
    }
    

    用法示例:

    function Test-SqlConnection
    {
        $ConnectionString = "ThisConnectionStringWillFail"
        try{
            $sqlConnection = New-Object System.Data.SqlClient.SqlConnection $ConnectionString;
            $sqlConnection.Open();
        }
        catch
        {
            throw [System.Exception]::new("Sql connection failed with connection string: '$ConnectionString'", $_.Exception)
        }
        finally
        {
            if($sqlConnection){
                $sqlConnection.Close();
            }
        }
    }
    
    
    try{
        Test-SqlConnection
    }
    catch {
        Get-InnerErrors $_
    }
    

    示例输出:

    
    
    ExceptionDepth      : 0
    Message             : Sql connection failed with connection string: 'ThisConnectionStringWillFail'
    ScriptStackTrace    : at Test-SqlConnection, <No file>: line 11
                          at <ScriptBlock>, <No file>: line 23
    ExceptionType       : System.Exception
    ExceptionStackTrace : 
    
    ExceptionDepth      : 1
    Message             : Exception calling ".ctor" with "1" argument(s): "Format of the initialization string does not conform to specification starting at index 0."
    ScriptStackTrace    : 
    ExceptionType       : System.Management.Automation.MethodInvocationException
    ExceptionStackTrace :    at System.Management.Automation.DotNetAdapter.AuxiliaryConstructorInvoke(MethodInformation methodInformation, Object[] arguments, Object[] originalArguments)
                             at System.Management.Automation.DotNetAdapter.ConstructorInvokeDotNet(Type type, ConstructorInfo[] constructors, Object[] arguments)
                             at Microsoft.PowerShell.Commands.NewObjectCommand.CallConstructor(Type type, ConstructorInfo[] constructors, Object[] args)
    
    ExceptionDepth      : 2
    Message             : Format of the initialization string does not conform to specification starting at index 0.
    ScriptStackTrace    : 
    ExceptionType       : System.ArgumentException
    ExceptionStackTrace :    at System.Data.Common.DbConnectionOptions.GetKeyValuePair(String connectionString, Int32 currentPosition, StringBuilder buffer, Boolean useOdbcRules, String& keyname, String& 
                          keyvalue)
                             at System.Data.Common.DbConnectionOptions.ParseInternal(Hashtable parsetable, String connectionString, Boolean buildChain, Hashtable synonyms, Boolean firstKey)
                             at System.Data.Common.DbConnectionOptions..ctor(String connectionString, Hashtable synonyms, Boolean useOdbcRules)
                             at System.Data.SqlClient.SqlConnectionString..ctor(String connectionString)
                             at System.Data.SqlClient.SqlConnectionFactory.CreateConnectionOptions(String connectionString, DbConnectionOptions previous)
                             at System.Data.ProviderBase.DbConnectionFactory.GetConnectionPoolGroup(DbConnectionPoolKey key, DbConnectionPoolGroupOptions poolOptions, DbConnectionOptions& userConnectionOptions)
                             at System.Data.SqlClient.SqlConnection.ConnectionString_Set(DbConnectionPoolKey key)
                             at System.Data.SqlClient.SqlConnection.set_ConnectionString(String value)
                             at System.Data.SqlClient.SqlConnection..ctor(String connectionString, SqlCredential credential)
    
        11
  •  0
  •   Sergey Babkin    9 年前

    您还可以更改错误对象的默认格式,以包括堆栈跟踪。基本上,通过从$PSHOME\PowerShellCore.format.ps1xml复制System.Management.Automation.ErrorRecord的区块来创建您的格式文件,并添加您自己的 添加跟踪的元素。然后用updateformatdata加载它。关于更多细节,我刚刚写了一篇博客: https://blogs.msdn.microsoft.com/sergey_babkins_blog/2016/12/28/getting-a-stack-trace-in-powershell/

    哦,还有一件事:这不会自动传播到远程会话中。对象在远程端被格式化为字符串。对于远程会话中的堆栈跟踪,您必须将此文件上载到那里,然后在那里再次调用Update FormatData。

        12
  •  0
  •   Rabash    6 年前

    在某些情况下,PowerShell似乎不保留回溯,例如调用方法或使用 .Invoke() . 为此,, Set-PSDebug -Trace 2 也许有用。它将打印运行脚本的每一行。

    试着翻动(1)和(2)并跑步 WrapStackTraceLog({ function f{ 1/0 } ; & f }) # let's divide by zero

    Function WrapStackTraceLog($func) {
        try {
            # return $func.Invoke($args)  # (1)
            return (& $func $args)  # (2)
        } catch {
            Write-Host ('=' * 70)
            Write-Host $_.Exception.Message
            Write-Host ('-' * 70)
            Write-Host $_.ScriptStackTrace
            Write-Host ('-' * 70)
            Write-Host "$StackTrace"
            Write-Host ('=' * 70)
        }
    }
    

    捕获到分支(1)异常:

    Exception calling "Invoke" with "1" argument(s): "Attempted to divide by zero."
    

    at f, <No file>: line 1
    at <ScriptBlock>, <No file>: line 1
    at global:WrapStackTraceLog, <No file>: line 4
    at <ScriptBlock>, <No file>: line 1
    

    但是,您仍然可以在跟踪打开的情况下跟踪调用,分支(1):

    DEBUG:     ! CALL function 'f'
    DEBUG:    1+ WrapStackTraceLog({ function f{  >>>> 1/0 } ; & f })
    DEBUG:    6+          >>>> Write-Host ('=' * 70)
    ======================================================================
    DEBUG:    7+          >>>> Write-Host $_.Exception.Message
    Exception calling "Invoke" with "1" argument(s): "Attempted to divide by zero."
    
    推荐文章