One of the most annoying “features” of PowerShell is that when the script crashes, it prints no stack trace, so finding the cause of the error is quite difficult. The exception object System.Management.Automation.ErrorRecord actually has the property ScriptStackTrace that contains the trace, it’s just that the trace doesn’t get printed on error. You can wrap your code into your own try/catch and print the trace. Or you can define a different default formatting for this class, and get the stack trace printed by default.
How to change the default formatting. First I’ll tell how it was done, and then will show the whole contents.
If you want to start from scratch, open $PSHOMEPowerShellCore.format.ps1xml and copy the definition of formatting for the type System.Management.Automation.ErrorRecord to a your own separate file Format.ps1xml. After the last entry <ExpressionBinding>, add your own:
<ExpressionBinding> <ScriptBlock> $_.ScriptStackTrace </ScriptBlock> </ExpressionBinding>
That’s basically it. Well, plus a minor fix: the default implementation doesn’t always include the LF at the end of the message, and if it doesn’t, the stack trace ends up stuck directly to the end of the last line. To fix it, add the “`n” in the previous clause:
elseif (! $_.ErrorDetails -or ! $_.ErrorDetails.Message) { $_.Exception.Message + $posmsg + "`n" # SB-changed } else { $_.ErrorDetails.Message + $posmsg + "`n" # SB-changed }
After you have your Format.ps1xml ready, import it from your script:
$spath = Split-Path -parent $PSCommandPath Update-FormatData -PrependPath "$spathFormat.ps1xml"
Once imported, it will affect the whole PowerShell example. Personally I also import it in ~DocumentsWindowsPowerShellprofile.ps1, so that at least on my machine I get the messages with the stack trace from all the normal running of PowerShell.
A weird thing is that if I do
Get-FormatData -TypeName System.Management.Automation.ErrorRecord
I get nothing. But it works. I guess some special magic is associated with this class.
And now for convenience the whole format file. The comment in $PSHOMEPowerShellCore.format.ps1xml says that it’s the sample code, so it’s got to be fine to use as another sample:
<?xml version="1.0" encoding="utf-8" ?> <!-- ******************************************************************* These sample files contain formatting information used by the Windows PowerShell engine. Do not edit or change the contents of this file directly. Please see the Windows PowerShell documentation or type Get-Help Update-FormatData for more information. Copyright (c) Microsoft Corporation. All rights reserved. THIS SAMPLE CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,WHETHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. IF THIS CODE AND INFORMATION IS MODIFIED, THE ENTIRE RISK OF USE OR RESULTS IN CONNECTION WITH THE USE OF THIS CODE AND INFORMATION REMAINS WITH THE USER. ******************************************************************** --> <Configuration> <ViewDefinitions> <View> <Name>ErrorInstance</Name> <OutOfBand /> <ViewSelectedBy> <TypeName>System.Management.Automation.ErrorRecord</TypeName> </ViewSelectedBy> <CustomControl> <CustomEntries> <CustomEntry> <CustomItem> <ExpressionBinding> <ScriptBlock> if ($_.FullyQualifiedErrorId -ne "NativeCommandErrorMessage" -and $ErrorView -ne "CategoryView") { $myinv = $_.InvocationInfo if ($myinv -and $myinv.MyCommand) { switch -regex ( $myinv.MyCommand.CommandType ) { ([System.Management.Automation.CommandTypes]::ExternalScript) { if ($myinv.MyCommand.Path) { $myinv.MyCommand.Path + " : " } break } ([System.Management.Automation.CommandTypes]::Script) { if ($myinv.MyCommand.ScriptBlock) { $myinv.MyCommand.ScriptBlock.ToString() + " : " } break } default { if ($myinv.InvocationName -match '^[&.]?$') { if ($myinv.MyCommand.Name) { $myinv.MyCommand.Name + " : " } } else { $myinv.InvocationName + " : " } break } } } elseif ($myinv -and $myinv.InvocationName) { $myinv.InvocationName + " : " } } </ScriptBlock> </ExpressionBinding> <ExpressionBinding> <ScriptBlock> if ($_.FullyQualifiedErrorId -eq "NativeCommandErrorMessage") { $_.Exception.Message } else { $myinv = $_.InvocationInfo if ($myinv -and ($myinv.MyCommand -or ($_.CategoryInfo.Category -ne 'ParserError'))) { $posmsg = $myinv.PositionMessage } else { $posmsg = "" } if ($posmsg -ne "") { $posmsg = "`n" + $posmsg } if ( & { Set-StrictMode -Version 1; $_.PSMessageDetails } ) { $posmsg = " : " + $_.PSMessageDetails + $posmsg } $indent = 4 $width = $host.UI.RawUI.BufferSize.Width - $indent - 2 $errorCategoryMsg = & { Set-StrictMode -Version 1; $_.ErrorCategory_Message } if ($errorCategoryMsg -ne $null) { $indentString = "+ CategoryInfo : " + $_.ErrorCategory_Message } else { $indentString = "+ CategoryInfo : " + $_.CategoryInfo } $posmsg += "`n" foreach($line in @($indentString -split "(.{$width})")) { if($line) { $posmsg += (" " * $indent + $line) } } $indentString = "+ FullyQualifiedErrorId : " + $_.FullyQualifiedErrorId $posmsg += "`n" foreach($line in @($indentString -split "(.{$width})")) { if($line) { $posmsg += (" " * $indent + $line) } } $originInfo = & { Set-StrictMode -Version 1; $_.OriginInfo } if (($originInfo -ne $null) -and ($originInfo.PSComputerName -ne $null)) { $indentString = "+ PSComputerName : " + $originInfo.PSComputerName $posmsg += "`n" foreach($line in @($indentString -split "(.{$width})")) { if($line) { $posmsg += (" " * $indent + $line) } } } if ($ErrorView -eq "CategoryView") { $_.CategoryInfo.GetMessage() } elseif (! $_.ErrorDetails -or ! $_.ErrorDetails.Message) { $_.Exception.Message + $posmsg + "`n" # SB-changed } else { $_.ErrorDetails.Message + $posmsg + "`n" # SB-changed } } </ScriptBlock> </ExpressionBinding> <ExpressionBinding> <ScriptBlock> $_.ScriptStackTrace </ScriptBlock> </ExpressionBinding> </CustomItem> </CustomEntry> </CustomEntries> </CustomControl> </View> </ViewDefinitions> </Configuration>