ps/Modules/Cole.PowerShell.Developer/Public/Write-ExceptionToStringBuilder.ps1

175 lines
11 KiB
PowerShell
Raw Permalink Normal View History

2023-05-30 22:51:22 -07:00
function Write-ExceptionToStringBuilder {
<#
.SYNOPSIS
This function should be used by Write-ErrorObject to convert the inner exception object(s) to a form that can be used for printing out.
.PARAMETER Exception
The exception being processed. Can be an InnerException to a parent object.
.PARAMETER ShortMessageSB
This stringbuilder is used to highlight selected data for the given exception
.PARAMETER ExceptionSB
This stringbuilder is used to append data for the given exception.
.PARAMETER StackTraceSB
This stringbuilder is used to append stack trace data for the given exception.
.PARAMETER SelectedProperties
This object contains selected properties that may be worth investigating
.OUTPUTS
This function returns a dereferenceable array of the ShortMessage stringbuilder, the Exception stringbuilder, the StackTrace stringbuilder, and the dictionary of selected properties.
#>
[CmdletBinding()]
[OutputType([object[]])]
param (
[System.Exception]$Exception,
[System.Text.StringBuilder]$ShortMessageSB,
[System.Text.StringBuilder]$ExceptionSB,
[System.Text.StringBuilder]$StackTraceSB,
[int]$InnerExceptionDepth,
[Object]$SelectedProperties
)
if ($null -eq $SelectedProperties) {
$SelectedProperties = @{}
}
if ($null -eq $ShortMessageSB) {
$ShortMessageSB = New-Object System.Text.StringBuilder
}
if ($null -eq $ExceptionSB) {
$ExceptionSB = New-Object System.Text.StringBuilder
}
if ($null -eq $StackTraceSB) {
$StackTraceSB = New-Object System.Text.StringBuilder
}
$selectedPropertiesNewKey = "Exception"
if ($InnerExceptionDepth -gt 0) {
$selctedPropertiesNewKey = "InnerException #$InnerExceptionDepth"
}
$selectedPropertiesNewObject = @{}
$padLeftSize = ($InnerExceptionDepth * 2)
$paddingSpaces = "$(''.PadLeft($padLeftSize))"
if ($null -ne $exception) {
if ($null -ne $exception.Reason) {
Write-Host "recursing exception.reason"
$ShortMessageSB, $ExceptionSB, $StackTraceSB, $SelectedProperties = (Write-ExceptionToStringBuilder -Exception $exception.Reason -InnerExceptionDepth ($InnerExceptionDepth + 1) -ShortMessageSB $ShortMessageSB -ExceptionSB $ExceptionSB -StackTraceSB $StackTraceSB -SelectedProperties $scriptProperties)
}
if ($null -ne $exception.InnerException) {
$ShortMessageSB, $ExceptionSB, $StackTraceSB, $SelectedProperties = (Write-ExceptionToStringBuilder -Exception $exception.InnerException -InnerExceptionDepth ($InnerExceptionDepth + 1) -ShortMessageSB $ShortMessageSB -ExceptionSB $ExceptionSB -StackTraceSB $StackTraceSB -SelectedProperties $scriptProperties)
}
if ($exception.WasThrownFromThrowStatement) {
$ShortMessageSB.Insert(0,"[This error was thrown via throw]")
}
$ExceptionSB.Append($paddingSpaces).AppendLine("Exception of type $($exception.GetType().FullName) thrown") | Out-Null
$ExceptionSB.Append($paddingSpaces).AppendLine("Message: $($exception.Message)") | Out-Null
if (![string]::IsNullOrWhiteSpace($exception.StackTrace)) {
if ($InnerExceptionDepth -eq 0) {
$StackTraceSB.AppendLine("---------Base Exception Stacktrace:") | Out-Null
} else {
$StackTraceSB.AppendLine("---------Inner (depth: $InnerExceptionDepth) Stacktrace:") | Out-Null
}
$StackTraceSB.AppendLine($exception.StackTrace) | Out-Null
}
$stringRecords = 'HelpLink','ItemName','ObjectName','ErrorId','ParamName','CommandName','ParameterName','Source','HelpTopic','RequiresShellPath','TypeName','RedirectLocation','Error','Label','AssemblyName','RequiresShellId','TransportMessage','HelpCategory'
$simpleRecords = 'HResult','TypeSpecified','ErrorCode','CallDepth','RequiresPSVersion','WasThrownFromThrowStatement','ParameterType'
$complexRecords = @(
<#System.Object#>'ActualValue'
<#System.Object#>'Argument'
<#System.Management.Automation.PSObject#>'SerializedRemoteException'
<#System.Management.Automation.InvocationInfo#>'CommandInvocation'
<#System.Management.Automation.ErrorRecord#>'ErrorRecord'
<#System.Management.Automation.PSObject#>'SerializedRemoteInvocationInfo'
<#System.Management.Automation.ProviderInvocationException#>'ProviderInvocationException'
<#System.Management.Automation.Language.ScriptExtent#>'DisplayScriptPosition'
<#System.Management.Automation.SessionStateCategory#>'SessionStateCategory'
<#System.Reflection.MethodBase#>'TargetSite'
<#System.Management.Automation.ProviderInfo#>'ProviderInfo'
)
$complexArrayRecords = @(
<#System.Collections.ObjectModel.ReadOnlyCollection`1[[System.Management.Automation.ProviderInfo, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]#>'PossibleMatches'
<#System.Collections.ObjectModel.ReadOnlyCollection`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]#>'MissingPSSnapIns'
<#System.Management.Automation.PSDataCollection`1[[System.Management.Automation.ErrorRecord, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]#>'ErrorRecords'
)
$ExceptionSB.AppendLine("$($paddingSpaces)Exception Object Data:") | Out-Null
foreach ($propertyName in $stringRecords) {
if (![string]::IsNullOrWhiteSpace($exception.$propertyName)) {
$ExceptionSB.AppendLine("$paddingSpaces$propertyName : $($exception.$propertyName)") | Out-Null
}
}
foreach ($propertyName in $simpleRecords) {
if (($null -ne $exception.$propertyName) -and ($false -ne $exception.$propertyName)) {
$ExceptionSB.AppendLine("$paddingSpaces$propertyName : $(($exception.$propertyName).ToString())") | Out-Null
}
}
foreach ($propertyName in $complexRecords) {
<#
try {
if ($null -ne $exception.$propertyName) {
$value = ConvertTo-Json $exception.$propertyName -Depth 10
$ExceptionSB.AppendLine("$paddingSpaces$propertyName : $value")
}
} catch {
Write-Warning "Error while attempting to transform error object. This code is untested in some places because the exceptions haven't been seen before."
#>
if ($null -ne $exception.$propertyName) {
Write-information "The value with key [$propertyName] has been attached to the complex data return object for evaluation."
$selectedPropertiesNewObject[$propertyName] = $exception.$propertyName
}
<#
# Error members were captured with this:
# $b = [System.Management.Automation.ParameterBindingException].Assembly.GetExportedTypes().Where({[System.Exception].IsAssignableFrom($_)})
# $b.GetProperties().Name | Sort-Object -Unique | Set-Clipboard
# $c = $b.GetProperties()
# $d = @{};foreach ($property in $c) { if ($null -eq $d[$property.Name]) { $d[$property.Name] = $property } }
# foreach ($property in $c) { if ($d[$property.Name].PropertyType -ne $property.PropertyType) { Write-Host $property.Name + " " + $property.PropertyType + " " + $d[$property.Name].PropertyType } }
# $e = @();foreach ($key in $d.PSObject.Properties["Keys"].Value) { $value = $d[$key]; if ($null -ne $value) { if ($value.PropertyType.FullName -eq 'System.String') { $e += "if (![string]::IsNullOrWhiteSpace(`$exception.$key)) { `n `$ExceptionSB.Append(`$paddingSpaces)`n #$($value.PropertyType.FullName)`n `$ExceptionSB.AppendLine(`"$key : `$(`$exception.$Key)`")`n}"} else {$e += "if (`$null -eq `$exception.$key) { `n `$ExceptionSB.Append(`$paddingSpaces)`n #$($value.PropertyType.FullName)`n `$ExceptionSB.AppendLine(`"$key : `$(`$exception.$Key)`")`n}"} } }; $e | Set-Clipboard
# This let me get the names of the System.Management.Automation.*Exception properties
# I did not write full translators for each item, so if you need those, you will need to expand this section
Write-Host $_.Exception.Message
Write-Host $_.ToString()
}
}
#>
foreach ($propertyName in $complexRecords) {
<#
try {
if ($null -ne $exception.$propertyName) {
$value = ConvertTo-Json $exception.$propertyName -Depth 10
$ExceptionSB.AppendLine("$paddingSpaces$propertyName : $value")
}
} catch {
Write-Warning "Error while attempting to transform error object. This code is untested in some places because the exceptions haven't been seen before."
#>
if ($null -ne $exception.$propertyName) {
Write-information "The value with key [$propertyName] has been attached to the complex data return object for evaluation."
$selectedPropertiesNewObject[$propertyName] = $exception.$propertyName
}
<#
# Error members were captured with this:
# $b = [System.Management.Automation.ParameterBindingException].Assembly.GetExportedTypes().Where({[System.Exception].IsAssignableFrom($_)})
# $b.GetProperties().Name | Sort-Object -Unique | Set-Clipboard
# $c = $b.GetProperties()
# $d = @{};foreach ($property in $c) { if ($null -eq $d[$property.Name]) { $d[$property.Name] = $property } }
# foreach ($property in $c) { if ($d[$property.Name].PropertyType -ne $property.PropertyType) { Write-Host $property.Name + " " + $property.PropertyType + " " + $d[$property.Name].PropertyType } }
# $e = @();foreach ($key in $d.PSObject.Properties["Keys"].Value) { $value = $d[$key]; if ($null -ne $value) { if ($value.PropertyType.FullName -eq 'System.String') { $e += "if (![string]::IsNullOrWhiteSpace(`$exception.$key)) { `n `$ExceptionSB.Append(`$paddingSpaces)`n #$($value.PropertyType.FullName)`n `$ExceptionSB.AppendLine(`"$key : `$(`$exception.$Key)`")`n}"} else {$e += "if (`$null -eq `$exception.$key) { `n `$ExceptionSB.Append(`$paddingSpaces)`n #$($value.PropertyType.FullName)`n `$ExceptionSB.AppendLine(`"$key : `$(`$exception.$Key)`")`n}"} } }; $e | Set-Clipboard
# This let me get the names of the System.Management.Automation.*Exception properties
# I did not write full translators for each item, so if you need those, you will need to expand this section
Write-Host $_.Exception.Message
Write-Host $_.ToString()
#>
}
}
}
$SelectedProperties.$selectedPropertiesNewKey = $selectedPropertiesNewObject
return @($ShortMessageSB, $ExceptionSB, $StackTraceSB, $SelectedProperties)
}