Могу ли я получить подробное исключение stacktrace в PowerShell?
Запуск такого script:
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
Ответы
Ответ 1
Существует автоматическая переменная $StackTrace
, но она кажется немного более специфичной для внутренних деталей PS, чем фактическая забота о вашем script, поэтому это не поможет.
Существует также Get-PSCallStack
, но это ушло, как только вы попали в исключение, к сожалению. Однако вы можете поставить Get-PSCallStack
перед каждым нажатием script. Таким образом, вы получаете трассировку стека непосредственно перед удалением исключения.
Я думаю, что можно было бы script использовать такую функциональность, используя функции отладки и трассировки Powershell, но я сомневаюсь, что это будет легко.
Ответ 2
В блоге PowerShell Team есть функция 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
}
}
Ответ 3
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, хотя у нас меньше деталей для каждого кадра.
Ответ 4
Вы не можете получить трассировку стека из исключений кода сценариев PowerShell, только из объектов .NET. Для этого вам нужно будет получить объект Exception как один из следующих:
$Error[0].Exception.StackTrace
$Error[0].Exception.InnerException.StackTrace
$Error[0].StackTrace
Ответ 5
Я взял то, что нашел здесь в качестве вдохновения, и создал хорошую функцию, которую любой может занять в своем коде.
Вот как я это называю:
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 {}
}
Ответ 6
Вот путь: Отслеживание стека script
Ядро этого кода:
1..100 | %{ $inv = &{ gv -sc $_ myinvocation }
Ответ 7
Я просто понял это. $_ Является исключением, пойманным в блоке catch.
$errorString= $_ | Out-String
Ответ 8
Вы также можете изменить форматирование по умолчанию для объекта ошибки, чтобы включить трассировку стека. В принципе, сделайте свой файл формата, скопировав кусок для System.Management.Automation.ErrorRecord из $PSHOME\PowerShellCore.format.ps1xml и добавьте свои собственные
который добавляет трассировку. Затем загрузите его с помощью Update-FormatData. Для более подробной информации я только что написал сообщение в блоге об этом: https://blogs.msdn.microsoft.com/sergey_babkins_blog/2016/12/28/getting-a-stack-trace-in-powershell/
О, еще одна вещь: это не распространяется автоматически на удаленные сеансы. Объекты форматируются в строки на удаленной стороне. Для отслеживания стека в удаленных сеансах вам придется загрузить этот файл там и снова вызвать Update-FormatData.
Ответ 9
Существуют случаи, когда PowerShell, похоже, не сохраняет обратный след, например, вызов метода или вызов функции с помощью .Invoke()
. Для этого может пригодиться Set-PSDebug -Trace 2
. Он будет печатать каждую исполняемую строку запущенного скрипта.
Попробуйте переключить # on (1) и (2) и запустить WrapStackTraceLog({ function f{ 1/0 }; & f }) # let 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
Но вы все равно можете отследить ваши Invokes с помощью трассировки на 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."
Ответ 10
Наткнулся на это в поисках встроенного решения. Я иду с простым решением. Просто добавьте блок трассировки перед использованием любой PowerShell. Это обеспечит отображение стека вызовов. Обратная сторона этого стека будет отображаться перед сообщением об ошибке.
Trace {
$_.ScriptStackTrace
}
Ответ 11
Этот код:
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