59

运行这样的脚本:

 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
4

12 回答 12

51

PowerShell 团队博客上有一个名为 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
   }
}
于 2009-04-28T14:07:16.747 回答
21

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 将错过 throw 站点和 catch 块之间的任何帧。因此,我更喜欢 PS 3.0 方法,即使我们每帧的细节更少。

于 2013-03-13T23:48:14.580 回答
20

有自动变量$StackTrace,但它似乎对内部 PS 细节更具体一点,而不是真正关心你的脚本,所以这不会有太大帮助。

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

我认为可以通过使用 Powershell 的调试和跟踪功能编写这样的功能,但我怀疑这很容易。

于 2009-04-28T08:43:25.060 回答
13

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

$Error[0].Exception.StackTrace
$Error[0].Exception.InnerException.StackTrace
$Error[0].StackTrace
于 2009-04-28T20:11:54.810 回答
10

我将在这里找到的内容作为灵感,并创建了一个任何人都可以放入他们的代码中的好功能。

这就是我所说的: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 {}
}
于 2014-07-09T02:52:38.257 回答
3

这段代码:

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
于 2019-09-11T07:27:11.033 回答
2

这是一种方法:跟踪脚本堆栈

它的核心是这段代码:

    1..100 | %{ $inv = &{ gv -sc $_ myinvocation }
于 2010-07-13T15:12:31.943 回答
2

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

$errorString= $_ | Out-String 
于 2017-07-25T14:31:20.713 回答
1

您还可以更改错误对象的默认格式以包含堆栈跟踪。基本上,通过从 $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。

于 2016-12-28T23:30:17.650 回答
1

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

Trace {
   $_.ScriptStackTrace
}
于 2019-08-07T16:00:11.487 回答
1

也许我误解了一些东西,但我的问题是我没有看到内部异常的 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)
于 2020-07-21T08:39:46.703 回答
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."
于 2019-03-09T11:31:02.063 回答