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

发布于 2024-07-18 00:35:12 字数 362 浏览 4 评论 0原文

运行这样的脚本:

 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

Can I get aDetailed stack trace 相反?

At bar() in C:\test.ps1:8
At foo() in C:\test.ps1:3 
At C:\test.ps1:11

Runing such script:

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

I see

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

Can I get a detailed stack trace instead?

At bar() in C:\test.ps1:8
At foo() in C:\test.ps1:3 
At C:\test.ps1:11

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

一刻暧昧 2024-07-25 00:35:12

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

There is a function up on the PowerShell Team blog called Resolve-Error which will get you all kinds of details

Note that $error is an array of all the errors you have encountered in your PSSession. This function will give you details on the last error you encountered.

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
   }
}
木森分化 2024-07-25 00:35:12

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

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

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

There is the automatic variable $StackTrace but it seems to be a little more specific to internal PS details than actually caring about your script, so that won't be of much help.

There is also Get-PSCallStack but that's gone as soon as you hit the exception, unfortunately. You could, however, put a Get-PSCallStack before every throw in your script. That way you get a stack trace immediately before hitting an exception.

I think one could script such functionality by using the debugging and tracing features of Powershell but I doubt it'd be easy.

帅哥哥的热头脑 2024-07-25 00:35:12

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 块之间的任何帧。 因此,我更喜欢 PS 3.0 方法,尽管每帧的细节较少。

Powershell 3.0 adds a ScriptStackTrace property to the ErrorRecord object. I use this function for error reporting:

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

The Skip parameter lets me leave Write-Callstack or any number of error-handling stack frames out of the Get-PSCallstack listing.

Note that if called from a catch block, Get-PSCallstack will miss any frames between the throw site and the catch block. Hence I prefer the PS 3.0 method even though we have fewer details per frame.

迷路的信 2024-07-25 00:35:12

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

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

You can not get a stack trace from exceptions of the PowerShell code of scripts, only from .NET objects. To do that, you will need to get the Exception object like one of these:

$Error[0].Exception.StackTrace
$Error[0].Exception.InnerException.StackTrace
$Error[0].StackTrace
献世佛 2024-07-25 00:35:12

我以在这里找到的东西为灵感,创建了一个很好的函数,任何人都可以将其放入他们的代码中。

我就是这样称呼它的:
Write-Host“无法写入日志文件`n$(解决错误)”-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 {}
}

I took what I found here as inspiration and created a nice function anyone can drop into their code.

This is how I call it:
Write-Host "Failed to write to the log file `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 {}
}
铃予 2024-07-25 00:35:12

此代码:

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

This code:

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

Will echo an error in a following format:

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
奶气 2024-07-25 00:35:12

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

其核心是这段代码:

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

Here's a way: Tracing the script stack

The core of it is this code:

    1..100 | %{ $inv = &{ gv -sc $_ myinvocation }
梦初启 2024-07-25 00:35:12

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

You can also change the default formatting for the error object to include the stack trace. Basically, make your format file by copying the chunk for System.Management.Automation.ErrorRecord from $PSHOME\PowerShellCore.format.ps1xml and add your own
element that adds the trace. Then load it with Update-FormatData. For more details, I've just written a blog post about it: https://blogs.msdn.microsoft.com/sergey_babkins_blog/2016/12/28/getting-a-stack-trace-in-powershell/

Oh, one more thing: this doesn't propagate automatically into the remote sessions. The objects get formatted into strings on the remote side. For stack traces in the remote sessions you'll have to upload this file there and call Update-FormatData there again.

我不会写诗 2024-07-25 00:35:12

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

$errorString= $_ | Out-String 

I just figured it out. The $_ is the exception caught in the catch block.

$errorString= $_ | Out-String 
此岸叶落 2024-07-25 00:35:12

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

Maybe I've misunderstood something, but my issue here is that I've not been seeing powershell script stack traces for inner exceptions.

In the end I ended up searching $Global:Error for an exception's Error Eecord object to retrieve the script stack trace.

<#
.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
       }
   }
}

Example Usage:

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 $_
}

Example output:



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)
不如归去 2024-07-25 00:35:12

在某些情况下,PowerShell 似乎不保留回溯,例如使用 .Invoke() 调用方法或调用函数。 为此,Set-PSDebug -Trace 2 可能会派上用场。 它将打印正在运行的脚本的每个执行行。

尝试翻转 (1) 和 (2) 上的 # 并运行 WrapStackTraceLog({ function f{ 1/0 } ; & f }) # 让我们除以零

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

Branch (1) 捕获异常:

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

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

There are cases where PowerShell doesn't seem to keep a backtrace, like calling a method or calling a function with .Invoke(). For that, Set-PSDebug -Trace 2 may come in handy. It will print every executed line of the running script.

Try flipping # on (1) and (2) and running 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)
    }
}

Branch (1) exception caught:

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

Branch (2) is more informative:

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

But, you can still trace your Invokes with tracing on, branch (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."
同尘 2024-07-25 00:35:12

偶然发现这个问题正在寻找内置解决方案。 我将采用简单的解决方案。 只需在使用任何 powershell 之前添加跟踪块即可。 这将确保显示调用堆栈。 这样做的缺点是堆栈将在错误消息之前显示。

Trace {
   $_.ScriptStackTrace
}

Stumbled upon this looking for a built in solution. I am going with simple solution. Just add trace block before using any powershell. This will ensure a call stack is shown. Down side of this is the stack will be displayed before the error message.

Trace {
   $_.ScriptStackTrace
}
烟织青萝梦 2024-07-25 00:35:12

该线程很旧,但我们实际上可以得到相同的结果:

function PrintError([Object] $errorHandler) {
    Write-Host $errorHandler.Message -Foreground "Red"
    if ($errorHandler.InnerException) {
        PrintError($errorHandler.InnerException)
    }
}

此外,为了捕获脚本的任何异常,您可以使用它:

trap {
    PrintError $_.Exception
    Write-Host $_.ScriptStackTrace -Foreground "DarkGray"
    exit 1
}

工作得很好。

The thread is very old but we can get the same actually with this:

function PrintError([Object] $errorHandler) {
    Write-Host $errorHandler.Message -Foreground "Red"
    if ($errorHandler.InnerException) {
        PrintError($errorHandler.InnerException)
    }
}

Additionally, to trap any Exception of your script, you can use that:

trap {
    PrintError $_.Exception
    Write-Host $_.ScriptStackTrace -Foreground "DarkGray"
    exit 1
}

Worked very well.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文