Skip to content

Instantly share code, notes, and snippets.

@jhoneill
Created February 6, 2022 16:17
Show Gist options
  • Save jhoneill/6e79c210f78eda01f72426c173fa2bf4 to your computer and use it in GitHub Desktop.
Save jhoneill/6e79c210f78eda01f72426c173fa2bf4 to your computer and use it in GitHub Desktop.
function Get-Error {
[cmdletbinding(DefaultParameterSetName='Newest')]
param (
[Parameter(Position = 0, ValueFromPipeline=$true, ParameterSetName= "Error")]
[ValidateNotNullOrEmpty()]
$InputObject,
[Parameter(ParameterSetName = "Newest", ValueFromPipelineByPropertyName = $true)]
[Alias('Last')]
[ValidateRange(1, [int]::MaxValue)]
[int]$Newest = 1,
[string]$errorVTCode ,
[string]$accentVTCode ,
[int]$maxDepth = 10
)
begin {
$ellipsis = "`u{2026}"
$newline = [Environment]::Newline
$expandTypes = @('Microsoft.Rest.HttpRequestMessageWrapper',
'Microsoft.Rest.HttpResponseMessageWrapper',
'System.Management.Automation.InvocationInfo')
if ($env:__SuppressAnsiEscapeSequences -or -not $Host.UI.SupportsVirtualTerminal ) {
$resetColor = $errorVTCode = $accentVTCode = ''}
else {
$resetColor = [System.Management.Automation.VTUtility]::GetEscapeSequence(
[System.Management.Automation.VTUtility+VT]::Reset
)
if ($null -ne $psstyle -and -not $errorVTCode) {$errorVTCode = $psstyle.Formatting.Error }
if ($null -ne $Host.PrivateData) {
if (-not $accentVTCode) {
$accentVTCode = [System.Management.Automation.VTUtility]::GetEscapeSequence(
$Host.PrivateData.FormatAccentColor ?? $Host.PrivateData.ErrorForegroundColor
)
}
if (-not $errorVTCode) {
$errorVTCode = [System.Management.Automation.VTUtility]::GetEscapeSequence(
$Host.PrivateData.ErrorForegroundColor
)
}
}
}
function ShowErrorRecord {
param (
$obj,
[int]$indent = 0,
[int]$depth = 1,
[int]$minPropLength
)
$output = [System.Text.StringBuilder]::new()
# first find the longest property so we can indent properly
$propLength = ($obj.psobject.Properties.where({![string]::IsNullOrEmpty($_.value)}).name | Measure-Object -Maximum -Property length).maximum
if ($propLength -lt $minPropLength) {$propLength = $minPropLength}
if ($obj -is [Exception]) {
$null = $output.Append( ("{0,$indent}{1}{2,-$proplength} :{3} {4}{5}" -f '' , $accentVTCode , 'Type', $resetColor, $obj.GetType().FullName,$newline) )
}
$addedProperty = $false
# don't show empty properties or our added property for $error[index]
foreach ($prop in $obj.PSObject.Properties.where({$_.Value.Count -gt 0 -and -not [string]::IsNullOrEmpty($_.Value)}) ) {
$addedProperty = $true
$null = $output.Append( ("{0,$indent}{1}{2,-$proplength} :{3} " -f '' , $accentVTCode , $prop.Name, $resetColor))
$newIndent = $indent + 4
$newMinPropLen = $propLength -4
# only show nested objects that are Exceptions, ErrorRecords, or types defined in $expandTypes and types not in $ignoreTypes
if ($prop.Value -is [Exception] -or $prop.Value -is [System.Management.Automation.ErrorRecord] -or
$expandTypes -contains $prop.TypeNameOfValue -or ($null -ne $prop.TypeNames-and $expandTypes -contains $prop.TypeNames[0])) {
if ($depth -ge $maxDepth) {$null = $output.Append($ellipsis)}
else {
$null = $output.Append($newline)
$null = $output.Append((ShowErrorRecord -obj $prop.Value -indent $newIndent -depth ($depth + 1) -minPropLength $newMinPropLen))
}
}
# `TargetSite` has many members that are not useful visually, so we have a reduced view of the relevant members
elseif ($prop.Name -eq 'TargetSite' -and $prop.Value.GetType().Name -eq 'RuntimeMethodInfo') {
if ($depth -ge $maxDepth) { $null = $output.Append($ellipsis)}
else {
$targetSite = $prop.Value | Select-Object -Property Name, DeclaringType, MemberType, Module
$null = $output.Append($newline)
$null = $output.Append((ShowErrorRecord -obj $targetSite -indent $newIndent -depth ($depth + 1) -minPropLength $newMinPropLen))
}
}
# `StackTrace` is handled specifically because the lines are typically long but necessary so they are left justified without additional indentation
elseif ($prop.Name -eq 'StackTrace') {$null = $output.Append(($newline + $prop.Value))}
# Dictionary and Hashtable we want to show as Key/Value pairs, we don't do the extra whitespace alignment here
elseif ($prop.Value.GetType().Name.StartsWith('Dictionary') -or $prop.Value.GetType().Name -eq 'Hashtable') {
$null = $output.Append($newline)
foreach ($key in $prop.Value.Keys) {
$null = $output.Append( ("{0,$indent} {1}{2} :{3} " -f '' , $accentVTCode , $key, $resetColor))
if ($key -eq 'Authorization') {$null = $output.Append("${ellipsis}${newline}")}
else {$null = $output.Append("$($prop.Value[$key])${newline}")}
}
}
elseif ($prop.Value -isnot [System.String] -and $null -ne $prop.Value.GetType().GetInterface('IEnumerable') -and $prop.Name -ne 'Data') {
if ($depth -ge $maxDepth) {$null = $output.Append($ellipsis)}
else {
$null = $output.Append($newline)}
foreach ($value in $prop.Value) {
$null = $output.Append($newline)
$null = $output.Append((ShowErrorRecord -obj $value -indent $newIndent -depth ($depth + 1) -minPropLength $newMinPropLen ))
}
}
# Anything else, we convert to string. ToString() can throw so we use LanguagePrimitives.TryConvertTo() to hide a convert error
else {
$value = $null
if ([System.Management.Automation.LanguagePrimitives]::TryConvertTo($prop.Value, [string], [ref]$value) -and $value) {
if ($prop.Name -eq 'PositionMessage') {$value = $value.Insert($value.IndexOf('~'), $errorVTCode)}
elseif ($prop.Name -eq 'Message') {$value = $errorVTCode + $value}
elseif ($prop.Name -eq 'line') {
$value = $value -replace '^\s{3,}',"$ellipsis "
if ($value.length -ge ($host.UI.RawUI.WindowSize.Width - $indent - $propLength )) {
$value = $value.substring(0, ($host.UI.RawUI.WindowSize.Width - $indent - $propLength - 3)) + $ellipsis
}
}
if ( -not $value.Contains($newline)) {$null = $output.Append($value) }
else {
$isFirstLine = $true
# need to trim any extra whitespace already in the text
foreach ($line in $value.Split($newline)) {
if ($isFirstLine) {
$isFirstLine = $false
$null = $output.Append($line.Trim())
}
else {$null = $output.Append(("$newline{0,$indent}{0,-$proplength} {1}" -f '' , $line.Trim())) }
}
}
}
}
$null = $output.Append($newline)
}
# if we had added nested properties, we need to remove the last newline
if ($addedProperty) {$null = $output.Remove($output.Length - $newline.Length, $newline.Length)}
$output.ToString()
}
$errorRecords = @()
}
process {
if ($InputObject -and ($InputObject -is [Exception] -or
$InputObject -is [System.Management.Automation.ErrorRecord])) { $errorRecords += $InputObject}
elseif ($Newest -gt $error.count) {$errorRecords = $error}
else {$errorRecords = $error[0..($Newest -1)]}
}
end {
$index = 0;
foreach ($er in $errorRecords) {
if ($errorRecords.count -gt 1) {"ErrorIndex: $index" ; $index ++}
ShowErrorRecord -obj $er
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment