Quantcast
Channel: MSDN Blogs
Viewing all articles
Browse latest Browse all 29128

getting a stack trace in PowerShell

$
0
0

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 '^[&amp;.]?$')
                                                    {
                                                        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 ( &amp; { Set-StrictMode -Version 1; $_.PSMessageDetails } ) {
                                            $posmsg = " : " +  $_.PSMessageDetails + $posmsg
                                        }

                                        $indent = 4
                                        $width = $host.UI.RawUI.BufferSize.Width - $indent - 2

                                        $errorCategoryMsg = &amp; { 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 = &amp; { 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>

 

 


Viewing all articles
Browse latest Browse all 29128

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>