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

时间:2009-04-28 00:07:46

标签: exception powershell stack-trace

运行这样的脚本:

 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

12 个答案:

答案 0 :(得分:40)

有一个名为Resolve-Error的function up on the PowerShell Team blog可以为您提供各种详细信息

请注意,$ 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
   }
}

答案 1 :(得分:17)

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参数允许我从Get-PSCallstack列表中留下Write-Callstack或任意数量的错误处理堆栈帧。

请注意,如果从catch块调用,Get-PSCallstack将丢失throw站点和catch块之间的任何帧。因此,我更喜欢PS 3.0方法,即使每帧的细节较少。

答案 2 :(得分:15)

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

还有Get-PSCallStack但不幸的是,一旦你遇到异常,它就会消失。但是,您可以在脚本中的每次抛出之前放置Get-PSCallStack。这样,您可以在遇到异常之前立即获得堆栈跟踪。

我认为可以通过使用Powershell的调试和跟踪功能来编写此类功能的脚本,但我怀疑这很简单。

答案 3 :(得分:11)

您无法从PowerShell脚本代码的异常中获取堆栈跟踪,只能从.NET对象获取。为此,您需要获取Exception对象,如下所示:

$Error[0].Exception.StackTrace
$Error[0].Exception.InnerException.StackTrace
$Error[0].StackTrace

答案 4 :(得分:7)

我把我在这里找到的作为灵感,创造了一个很好的功能,任何人都可以放入他们的代码。

这就是我所说的: Write-Host"无法写入日志文件`n $(Resolve-Error)" -ForegroundColor Red

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 {}
}

答案 5 :(得分:2)

这是一种方式:Tracing the script stack

它的核心是这段代码:

    1..100 | %{ $inv = &{ gv -sc $_ myinvocation }

答案 6 :(得分:1)

我只是想通了。 $ _是catch块中捕获的异常。

$errorString= $_ | Out-String 

答案 7 :(得分:1)

此代码:

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

答案 8 :(得分:0)

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

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

答案 9 :(得分:0)

在某些情况下,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."

分支(2)内容更丰富:

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."

答案 10 :(得分:0)

偶然发现了这种内置解决方案。我要使用简单的解决方案。只需在使用任何Powershell之前添加跟踪块。这将确保显示调用堆栈。缺点是堆栈将在错误消息之前显示。

Trace {
   $_.ScriptStackTrace
}

答案 11 :(得分:0)

也许我误解了一些东西,但是这里的问题是我没有看到关于内部异常的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)