Resolve-Error.ps1

<#
 
.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
    }
}