Resolve-Error.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
<#
 
.SYNOPSIS
Recurses an error record or exception object to flatten nested objects.
 
.DESCRIPTION
Loops through information caught in catch blocks; from an ErrorRecord (and its InvocationInfo), to Exception, and InnerException.
 
.PARAMETER ErrorRecord
An error record or exception. By default the last error is used.
 
.PARAMETER AsString
Return an array of strings for printable output. By default we return an array of objects.
 
.PARAMETER Reverse
Returns items from outermost to innermost. By default we return items innermost to outermost.
 
.INPUTS
By default the last error; otherwise any error record or exception can be passed in by pipeline or first parameter.
 
.OUTPUTS
An array of objects, or an array of strings.
 
.EXAMPLE
Resolve-Error
 
Returns an array of nested objects describing the last error.
 
.EXAMPLE
$_ | Resolve-Error -AsString
 
Returns an array of strings describing the error in $_.
 
#>


function Resolve-Error {
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline = $true)]
        $ErrorRecord = $null,
        [switch] $AsString,
        [switch] $Reverse
    )

    if (!$ErrorRecord) {
        # This is a bit iffy, if it's a nested module it needs $_ as $Error will not be populated yet.
        # If it's not a nested module then it needs a Get-Variable -Scope 2
        $ErrorRecord = (Get-Variable -Name Error -Scope 2).Value | Select-Object -First 1
        <#
        if ($Error.Count -gt 0) {
            $ErrorRecord = $Error[0]
        } else {
            $ErrorRecord = $_
        }
        #>

    }

    $records = @()

    if ($ErrorRecord.psobject.Properties["InnerException"] -and $ErrorRecord.InnerException) {
        $records += Resolve-Error $ErrorRecord.InnerException
    }
    if ($ErrorRecord.psobject.Properties["Exception"] -and $ErrorRecord.Exception) {
        $records += Resolve-Error $ErrorRecord.Exception
    }
    if ($ErrorRecord.psobject.Properties["InvocationInfo"] -and $ErrorRecord.InvocationInfo) {
        $records += Resolve-Error $ErrorRecord.InvocationInfo
    }
    $records += $ErrorRecord

    if ($Reverse) {
        $records = [Array]::Reverse($records)
    }
    if (!$AsString) {
        $records
    } else {
        $string = @()
        $first = $true

        $records | ForEach-Object {
            if ($first) {
                $string += "=" * 40
                $first = $false
            } else {
                $string += "*" * 5
            }
            $string += $_ | Select-Object * | Out-String
        }

        $string += ""

        $stack = Get-PSCallStack
        for ($i = $stack.Count - 1; $i -ge 1; $i--) {
            $string += "-" * 5
            $string += "Depth: $i"
            $string += "Function: $($stack[$i].FunctionName)"
            # In some highly threaded contexts this doesn't appear?
            if ($stack[$i].PSObject.Properties["Arguments"]) {
                try {
                    $lines = $stack[$i].Arguments -split [Environment]::NewLine
                    if ($lines.Count -gt 9) {
                        $lines = $lines[0..8] -join [Environment]::NewLine
                    } else {
                        $lines = $lines -join [Environment]::NewLine
                    }
                    $string += "Arguments: $lines"
                } catch {
                    # It wasn't meant to be
                }
            }
            $string += "Line: $($stack[$i].ScriptLineNumber)"
            try {
                [array] $lines = $stack[$i].Position.Text -split [Environment]::NewLine
                $firstLine = ($stack[$i].ScriptLineNumber - 4) - 1
                $lastLine = ($stack[$i].ScriptLineNumber + 4) - 1
                if ($firstLine -le 0) {
                    $firstLine = 0
                }
                if ($lastLine -gt ($lines.Count - 1)) {
                    $lastLine = $lines.Count - 1
                }
                $lines = $lines[$firstLine..$lastLine]
                $lines = $lines -join [Environment]::NewLine
                $string += "Command: $lines"
            } catch {
                # It wasn't meant to be
            }
        }
        $string += ""
        $string += ""

        $string += "=" * 40
        $string -join [System.Environment]::NewLine
    }
}