そのようなスクリプトの実行:
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
自動変数$StackTrace
がありますが、実際にスクリプトを気にするよりも内部PSの詳細に少し特有のように見えるので、あまり役に立ちません。
Get-PSCallStack
もありますが、残念ながら例外に遭遇するとすぐに消えてしまいます。ただし、スクリプトのすべてのスローの前にGet-PSCallStack
を置くことができます。そうすれば、例外にヒットする直前にスタックトレースを取得できます。
Powershellのデバッグ機能とトレース機能を使用してこのような機能をスクリプト化できると思いますが、それは簡単ではないでしょう。
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
}
}
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リストから除外できます。
キャッチブロックから呼び出された場合、Get-PSCallstackは、スローサイトとキャッチブロックの間のフレームを見逃すことに注意してください。したがって、フレームごとの詳細が少ない場合でも、PS 3.0方式を好みます。
.NETオブジェクトからのみ、スクリプトのPowerShellコードの例外からスタックトレースを取得することはできません。そのためには、次のいずれかのような例外オブジェクトを取得する必要があります。
$Error[0].Exception.StackTrace
$Error[0].Exception.InnerException.StackTrace
$Error[0].StackTrace
ここで見つけたものをインスピレーションとし、誰でもコードにドロップできるNice関数を作成しました。
これは私がそれを呼び出す方法です: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 {}
}
私はそれを理解しました。 $ _は、catchブロックでキャッチされた例外です。
$errorString= $_ | Out-String
エラーオブジェクトのデフォルトのフォーマットを変更して、スタックトレースを含めることもできます。基本的に、$ 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 /
ああ、もう1つ、これはリモートセッションに自動的に伝達されません。オブジェクトはリモート側で文字列にフォーマットされます。リモートセッションのスタックトレースについては、このファイルをそこにアップロードし、Update-FormatDataを再度呼び出す必要があります。
メソッドの呼び出しや.Invoke()
を使用した関数の呼び出しなど、PowerShellがバックトレースを保持していないように見える場合があります。そのためには、_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)をトレースしてInvokesをトレースできます。
_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."
_
組み込みのソリューションを探してこれにつまずいた。私は簡単な解決策を考えています。 PowerShellを使用する前に、トレースブロックを追加するだけです。これにより、コールスタックが表示されます。この欠点は、エラーメッセージの前にスタックが表示されることです。
Trace {
$_.ScriptStackTrace
}