diff --git a/ErrorView.code-workspace b/ErrorView.code-workspace new file mode 100644 index 0000000..0f707bf --- /dev/null +++ b/ErrorView.code-workspace @@ -0,0 +1,14 @@ +{ + "folders": [ + { + "name": "ErrorView", + "path": "." + }, + { + "path": "../../Tasks" + } + ], + "settings": { + "powershell.cwd": "." + } +} \ No newline at end of file diff --git a/Reference/default.ps1 b/Reference/default.ps1 new file mode 100644 index 0000000..d4f6ac8 --- /dev/null +++ b/Reference/default.ps1 @@ -0,0 +1,335 @@ +<# +if (@('NativeCommandErrorMessage','NativeCommandError') -notcontains $_.FullyQualifiedErrorId -and @('CategoryView','ConciseView','DetailedView') -notcontains $ErrorView) +{ + $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 '^[&\.]?$') + { + if ($myinv.MyCommand.Name) + { + $myinv.MyCommand.Name + ' : ' + } + } + else + { + $myinv.InvocationName + ' : ' + } + + break + } + } + } + elseif ($myinv -and $myinv.InvocationName) + { + $myinv.InvocationName + ' : ' + } +} + + +Set-StrictMode -Off +$ErrorActionPreference = 'Stop' +trap { 'Error found in error view definition: ' + $_.Exception.Message } +$newline = [Environment]::Newline + +$resetColor = '' +$errorColor = '' +$accentColor = '' + +if ($Host.UI.SupportsVirtualTerminal -and ([string]::IsNullOrEmpty($env:__SuppressAnsiEscapeSequences))) { + $resetColor = $PSStyle.Reset + $errorColor = $PSStyle.Formatting.Error + $accentColor = $PSStyle.Formatting.ErrorAccent +} +function Get-ConciseViewPositionMessage { + + # returns a string cut to last whitespace + function Get-TruncatedString($string, [int]$length) { + + if ($string.Length -le $length) { + return $string + } + + return ($string.Substring(0,$length) -split '\s',-2)[0] + } + + $posmsg = '' + $headerWhitespace = '' + $offsetWhitespace = '' + $message = '' + $prefix = '' + + # The checks here determine if we show line detailed error information: + # - check if `ParserError` and comes from PowerShell which eventually results in a ParseException, but during this execution it's an ErrorRecord + # - check if invocation is a script or multiple lines in the console + # - check that it's not a script module as expectation is that users don't want to see the line of error within a module + if ((($err.CategoryInfo.Category -eq 'ParserError' -and $err.Exception -is 'System.Management.Automation.ParentContainsErrorRecordException') -or $myinv.ScriptName -or $myinv.ScriptLineNumber -gt 1) -and $myinv.ScriptName -notmatch '\.psm1$') { + $useTargetObject = $false + + # Handle case where there is a TargetObject and we can show the error at the target rather than the script source + if ($_.TargetObject.Line -and $_.TargetObject.LineText) { + $posmsg = "${resetcolor}$($_.TargetObject.File)${newline}" + $useTargetObject = $true + } + elseif ($myinv.ScriptName) { + if ($env:TERM_PROGRAM -eq 'vscode') { + # If we are running in vscode, we know the file:line:col links are clickable so we use this format + $posmsg = "${resetcolor}$($myinv.ScriptName):$($myinv.ScriptLineNumber):$($myinv.OffsetInLine)${newline}" + } + else { + $posmsg = "${resetcolor}$($myinv.ScriptName):$($myinv.ScriptLineNumber)${newline}" + } + } + else { + $posmsg = "${newline}" + } + + if ($useTargetObject) { + $scriptLineNumber = $_.TargetObject.Line + $scriptLineNumberLength = $_.TargetObject.Line.ToString().Length + } + else { + $scriptLineNumber = $myinv.ScriptLineNumber + $scriptLineNumberLength = $myinv.ScriptLineNumber.ToString().Length + } + + if ($scriptLineNumberLength -gt 4) { + $headerWhitespace = ' ' * ($scriptLineNumberLength - 4) + } + + $lineWhitespace = '' + if ($scriptLineNumberLength -lt 4) { + $lineWhitespace = ' ' * (4 - $scriptLineNumberLength) + } + + $verticalBar = '|' + $posmsg += "${accentColor}${headerWhitespace}Line ${verticalBar}${newline}" + + $highlightLine = '' + if ($useTargetObject) { + $line = $_.TargetObject.LineText.Trim() + $offsetLength = 0 + $offsetInLine = 0 + } + else { + $positionMessage = $myinv.PositionMessage.Split($newline) + $line = $positionMessage[1].Substring(1) # skip the '+' at the start + $highlightLine = $positionMessage[$positionMessage.Count - 1].Substring(1) + $offsetLength = $highlightLine.Trim().Length + $offsetInLine = $highlightLine.IndexOf('~') + } + + if (-not $line.EndsWith($newline)) { + $line += $newline + } + + # don't color the whole line + if ($offsetLength -lt $line.Length - 1) { + $line = $line.Insert($offsetInLine + $offsetLength, $resetColor).Insert($offsetInLine, $accentColor) + } + + $posmsg += "${accentColor}${lineWhitespace}${ScriptLineNumber} ${verticalBar} ${resetcolor}${line}" + $offsetWhitespace = ' ' * $offsetInLine + $prefix = "${accentColor}${headerWhitespace} ${verticalBar} ${errorColor}" + if ($highlightLine -ne '') { + $posMsg += "${prefix}${highlightLine}${newline}" + } + $message = "${prefix}" + } + + if (! $err.ErrorDetails -or ! $err.ErrorDetails.Message) { + if ($err.CategoryInfo.Category -eq 'ParserError' -and $err.Exception.Message.Contains("~$newline")) { + # need to parse out the relevant part of the pre-rendered positionmessage + $message += $err.Exception.Message.split("~$newline")[1].split("${newline}${newline}")[0] + } + elseif ($err.Exception) { + $message += $err.Exception.Message + } + elseif ($err.Message) { + $message += $err.Message + } + else { + $message += $err.ToString() + } + } + else { + $message += $err.ErrorDetails.Message + } + + # if rendering line information, break up the message if it's wider than the console + if ($myinv -and $myinv.ScriptName -or $err.CategoryInfo.Category -eq 'ParserError') { + $prefixLength = "$([char]27)]8;;{0}`a{1}$([char]27)]8;;`a" -f $pwd, $pwd::new($prefix).ContentLength + $prefixVtLength = $prefix.Length - $prefixLength + + # replace newlines in message so it lines up correct + $message = $message.Replace($newline, ' ').Replace("`n", ' ').Replace("`t", ' ') + + $windowWidth = 120 + if ($Host.UI.RawUI -ne $null) { + $windowWidth = $Host.UI.RawUI.WindowSize.Width + } + + if ($windowWidth -gt 0 -and ($message.Length - $prefixVTLength) -gt $windowWidth) { + $sb = [Text.StringBuilder]::new() + $substring = Get-TruncatedString -string $message -length ($windowWidth + $prefixVTLength) + $null = $sb.Append($substring) + $remainingMessage = $message.Substring($substring.Length).Trim() + $null = $sb.Append($newline) + while (($remainingMessage.Length + $prefixLength) -gt $windowWidth) { + $subMessage = $prefix + $remainingMessage + $substring = Get-TruncatedString -string $subMessage -length ($windowWidth + $prefixVtLength) + + if ($substring.Length - $prefix.Length -gt 0) + { + $null = $sb.Append($substring) + $null = $sb.Append($newline) + $remainingMessage = $remainingMessage.Substring($substring.Length - $prefix.Length).Trim() + } + else + { + break + } + } + $null = $sb.Append($prefix + $remainingMessage.Trim()) + $message = $sb.ToString() + } + + $message += $newline + } + + $posmsg += "${errorColor}" + $message + + $reason = 'Error' + if ($err.Exception -and $err.Exception.WasThrownFromThrowStatement) { + $reason = 'Exception' + } + # MyCommand can be the script block, so we don't want to show that so check if it's an actual command + elseif ($myinv.MyCommand -and $myinv.MyCommand.Name -and (Get-Command -Name $myinv.MyCommand -ErrorAction Ignore)) + { + $reason = $myinv.MyCommand + } + # If it's a scriptblock, better to show the command in the scriptblock that had the error + elseif ($_.CategoryInfo.Activity) { + $reason = $_.CategoryInfo.Activity + } + elseif ($myinv.MyCommand) { + $reason = $myinv.MyCommand + } + elseif ($myinv.InvocationName) { + $reason = $myinv.InvocationName + } + elseif ($err.CategoryInfo.Category) { + $reason = $err.CategoryInfo.Category + } + elseif ($err.CategoryInfo.Reason) { + $reason = $err.CategoryInfo.Reason + } + + $errorMsg = 'Error' + + "${errorColor}${reason}: ${posmsg}${resetcolor}" +} + +$myinv = $_.InvocationInfo +$err = $_ +if (!$myinv -and $_.ErrorRecord -and $_.ErrorRecord.InvocationInfo) { + $err = $_.ErrorRecord + $myinv = $err.InvocationInfo +} + +if ($err.FullyQualifiedErrorId -eq 'NativeCommandErrorMessage' -or $err.FullyQualifiedErrorId -eq 'NativeCommandError') { + return "${errorColor}$($err.Exception.Message)${resetcolor}" +} + +$myinv = $err.InvocationInfo +if ($ErrorView -eq 'DetailedView') { + $message = Get-Error + return "${errorColor}${message}${resetcolor}" +} + +if ($ErrorView -eq 'CategoryView') { + $message = $err.CategoryInfo.GetMessage() + return "${errorColor}${message}${resetcolor}" +} + +$posmsg = '' +if ($ErrorView -eq 'ConciseView') { + $posmsg = Get-ConciseViewPositionMessage +} +elseif ($myinv -and ($myinv.MyCommand -or ($err.CategoryInfo.Category -ne 'ParserError'))) { + $posmsg = $myinv.PositionMessage +} + +if ($posmsg -ne '') { + $posmsg = $newline + $posmsg +} + +if ($err.PSMessageDetails) { + $posmsg = ' : ' + $err.PSMessageDetails + $posmsg +} + +if ($ErrorView -eq 'ConciseView') { + if ($err.PSMessageDetails) { + $posmsg = "${errorColor}${posmsg}" + } + return $posmsg +} + +$indent = 4 + +$errorCategoryMsg = $err.ErrorCategory_Message + +if ($null -ne $errorCategoryMsg) +{ + $indentString = '+ CategoryInfo : ' + $err.ErrorCategory_Message +} +else +{ + $indentString = '+ CategoryInfo : ' + $err.CategoryInfo +} + +$posmsg += $newline + $indentString + +$indentString = "+ FullyQualifiedErrorId : " + $err.FullyQualifiedErrorId +$posmsg += $newline + $indentString + +$originInfo = $err.OriginInfo + +if (($null -ne $originInfo) -and ($null -ne $originInfo.PSComputerName)) +{ + $indentString = "+ PSComputerName : " + $originInfo.PSComputerName + $posmsg += $newline + $indentString +} + +$finalMsg = if ($err.ErrorDetails.Message) { + $err.ErrorDetails.Message + $posmsg +} else { + $err.Exception.Message + $posmsg +} + +"${errorColor}${finalMsg}${resetcolor}" + +#> \ No newline at end of file diff --git a/Reference/detail copy.ps1 b/Reference/detail copy.ps1 new file mode 100644 index 0000000..79e9c36 --- /dev/null +++ b/Reference/detail copy.ps1 @@ -0,0 +1,183 @@ +<# +[int]$maxDepth = 10 +Set-StrictMode -Off + +$ellipsis = "`u{2026}" +$resetColor = '' +$errorColor = '' +$accentColor = '' + +if ($Host.UI.SupportsVirtualTerminal -and ([string]::IsNullOrEmpty($env:__SuppressAnsiEscapeSequences))) { + $resetColor = $PSStyle.Reset + $errorColor = $psstyle.Formatting.Error + $accentColor = $PSStyle.Formatting.FormatAccent +} + +function Show-ErrorRecord($obj, [int]$indent = 0, [int]$depth = 1) { + $newline = [Environment]::Newline + $output = [System.Text.StringBuilder]::new() + $prefix = ' ' * $indent + + $expandTypes = @( + 'Microsoft.Rest.HttpRequestMessageWrapper' + 'Microsoft.Rest.HttpResponseMessageWrapper' + 'System.Management.Automation.InvocationInfo' + ) + + # if object is an Exception, add an ExceptionType property + if ($obj -is [Exception]) { + $obj | Add-Member -NotePropertyName Type -NotePropertyValue $obj.GetType().FullName -ErrorAction Ignore + } + + # first find the longest property so we can indent properly + $propLength = 0 + foreach ($prop in $obj.PSObject.Properties) { + if ($prop.Value -ne $null -and $prop.Value -ne [string]::Empty -and $prop.Name.Length -gt $propLength) { + $propLength = $prop.Name.Length + } + } + + $addedProperty = $false + foreach ($prop in $obj.PSObject.Properties) { + + # don't show empty properties or our added property for $error[index] + if ($prop.Value -ne $null -and $prop.Value -ne [string]::Empty -and $prop.Value.count -gt 0 -and $prop.Name -ne 'PSErrorIndex') { + $addedProperty = $true + $null = $output.Append($prefix) + $null = $output.Append($accentColor) + $null = $output.Append($prop.Name) + $propNameIndent = ' ' * ($propLength - $prop.Name.Length) + $null = $output.Append($propNameIndent) + $null = $output.Append(' : ') + $null = $output.Append($resetColor) + + $newIndent = $indent + 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 ($prop.TypeNames -ne $null -and $expandTypes -contains $prop.TypeNames[0])) { + + if ($depth -ge $maxDepth) { + $null = $output.Append($ellipsis) + } + else { + $null = $output.Append($newline) + $null = $output.Append((Show-ErrorRecord $prop.Value $newIndent ($depth + 1))) + } + } + # `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 = [PSCustomObject]@{ + Name = $prop.Value.Name + DeclaringType = $prop.Value.DeclaringType + MemberType = $prop.Value.MemberType + Module = $prop.Value.Module + } + + $null = $output.Append($newline) + $null = $output.Append((Show-ErrorRecord $targetSite $newIndent ($depth + 1))) + } + } + # `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') { + # for a stacktrace which is usually quite wide with info, we left justify it + $null = $output.Append($newline) + $null = $output.Append($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') { + $isFirstElement = $true + foreach ($key in $prop.Value.Keys) { + if ($isFirstElement) { + $null = $output.Append($newline) + } + + if ($key -eq 'Authorization') { + $null = $output.Append("${prefix} ${accentColor}${key} : ${resetColor}${ellipsis}${newline}") + } + else { + $null = $output.Append("${prefix} ${accentColor}${key} : ${resetColor}$($prop.Value[$key])${newline}") + } + + $isFirstElement = $false + } + } + # if the object implements IEnumerable and not a string, we try to show each object + # We ignore the `Data` property as it can contain lots of type information by the interpreter that isn't useful here + elseif (!($prop.Value -is [System.String]) -and $prop.Value.GetType().GetInterface('IEnumerable') -ne $null -and $prop.Name -ne 'Data') { + + if ($depth -ge $maxDepth) { + $null = $output.Append($ellipsis) + } + else { + $isFirstElement = $true + foreach ($value in $prop.Value) { + $null = $output.Append($newline) + if (!$isFirstElement) { + $null = $output.Append($newline) + } + $null = $output.Append((Show-ErrorRecord $value $newIndent ($depth + 1))) + $isFirstElement = $false + } + } + } + # 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 -ne $null) + { + if ($prop.Name -eq 'PositionMessage') { + $value = $value.Insert($value.IndexOf('~'), $errorColor) + } + elseif ($prop.Name -eq 'Message') { + $value = $errorColor + $value + } + + $isFirstLine = $true + if ($value.Contains($newline)) { + # the 3 is to account for ' : ' + $valueIndent = ' ' * ($propLength + 3) + # need to trim any extra whitespace already in the text + foreach ($line in $value.Split($newline)) { + if (!$isFirstLine) { + $null = $output.Append("${newline}${prefix}${valueIndent}") + } + $null = $output.Append($line.Trim()) + $isFirstLine = $false + } + } + else { + $null = $output.Append($value) + } + } + } + + $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() +} + +# Add back original typename and remove PSExtendedError +if ($_.PSObject.TypeNames.Contains('System.Management.Automation.ErrorRecord#PSExtendedError')) { + $_.PSObject.TypeNames.Add('System.Management.Automation.ErrorRecord') + $null = $_.PSObject.TypeNames.Remove('System.Management.Automation.ErrorRecord#PSExtendedError') +} +elseif ($_.PSObject.TypeNames.Contains('System.Exception#PSExtendedError')) { + $_.PSObject.TypeNames.Add('System.Exception') + $null = $_.PSObject.TypeNames.Remove('System.Exception#PSExtendedError') +} + +Show-ErrorRecord $_ +#> \ No newline at end of file diff --git a/Reference/detail.ps1 b/Reference/detail.ps1 new file mode 100644 index 0000000..79e9c36 --- /dev/null +++ b/Reference/detail.ps1 @@ -0,0 +1,183 @@ +<# +[int]$maxDepth = 10 +Set-StrictMode -Off + +$ellipsis = "`u{2026}" +$resetColor = '' +$errorColor = '' +$accentColor = '' + +if ($Host.UI.SupportsVirtualTerminal -and ([string]::IsNullOrEmpty($env:__SuppressAnsiEscapeSequences))) { + $resetColor = $PSStyle.Reset + $errorColor = $psstyle.Formatting.Error + $accentColor = $PSStyle.Formatting.FormatAccent +} + +function Show-ErrorRecord($obj, [int]$indent = 0, [int]$depth = 1) { + $newline = [Environment]::Newline + $output = [System.Text.StringBuilder]::new() + $prefix = ' ' * $indent + + $expandTypes = @( + 'Microsoft.Rest.HttpRequestMessageWrapper' + 'Microsoft.Rest.HttpResponseMessageWrapper' + 'System.Management.Automation.InvocationInfo' + ) + + # if object is an Exception, add an ExceptionType property + if ($obj -is [Exception]) { + $obj | Add-Member -NotePropertyName Type -NotePropertyValue $obj.GetType().FullName -ErrorAction Ignore + } + + # first find the longest property so we can indent properly + $propLength = 0 + foreach ($prop in $obj.PSObject.Properties) { + if ($prop.Value -ne $null -and $prop.Value -ne [string]::Empty -and $prop.Name.Length -gt $propLength) { + $propLength = $prop.Name.Length + } + } + + $addedProperty = $false + foreach ($prop in $obj.PSObject.Properties) { + + # don't show empty properties or our added property for $error[index] + if ($prop.Value -ne $null -and $prop.Value -ne [string]::Empty -and $prop.Value.count -gt 0 -and $prop.Name -ne 'PSErrorIndex') { + $addedProperty = $true + $null = $output.Append($prefix) + $null = $output.Append($accentColor) + $null = $output.Append($prop.Name) + $propNameIndent = ' ' * ($propLength - $prop.Name.Length) + $null = $output.Append($propNameIndent) + $null = $output.Append(' : ') + $null = $output.Append($resetColor) + + $newIndent = $indent + 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 ($prop.TypeNames -ne $null -and $expandTypes -contains $prop.TypeNames[0])) { + + if ($depth -ge $maxDepth) { + $null = $output.Append($ellipsis) + } + else { + $null = $output.Append($newline) + $null = $output.Append((Show-ErrorRecord $prop.Value $newIndent ($depth + 1))) + } + } + # `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 = [PSCustomObject]@{ + Name = $prop.Value.Name + DeclaringType = $prop.Value.DeclaringType + MemberType = $prop.Value.MemberType + Module = $prop.Value.Module + } + + $null = $output.Append($newline) + $null = $output.Append((Show-ErrorRecord $targetSite $newIndent ($depth + 1))) + } + } + # `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') { + # for a stacktrace which is usually quite wide with info, we left justify it + $null = $output.Append($newline) + $null = $output.Append($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') { + $isFirstElement = $true + foreach ($key in $prop.Value.Keys) { + if ($isFirstElement) { + $null = $output.Append($newline) + } + + if ($key -eq 'Authorization') { + $null = $output.Append("${prefix} ${accentColor}${key} : ${resetColor}${ellipsis}${newline}") + } + else { + $null = $output.Append("${prefix} ${accentColor}${key} : ${resetColor}$($prop.Value[$key])${newline}") + } + + $isFirstElement = $false + } + } + # if the object implements IEnumerable and not a string, we try to show each object + # We ignore the `Data` property as it can contain lots of type information by the interpreter that isn't useful here + elseif (!($prop.Value -is [System.String]) -and $prop.Value.GetType().GetInterface('IEnumerable') -ne $null -and $prop.Name -ne 'Data') { + + if ($depth -ge $maxDepth) { + $null = $output.Append($ellipsis) + } + else { + $isFirstElement = $true + foreach ($value in $prop.Value) { + $null = $output.Append($newline) + if (!$isFirstElement) { + $null = $output.Append($newline) + } + $null = $output.Append((Show-ErrorRecord $value $newIndent ($depth + 1))) + $isFirstElement = $false + } + } + } + # 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 -ne $null) + { + if ($prop.Name -eq 'PositionMessage') { + $value = $value.Insert($value.IndexOf('~'), $errorColor) + } + elseif ($prop.Name -eq 'Message') { + $value = $errorColor + $value + } + + $isFirstLine = $true + if ($value.Contains($newline)) { + # the 3 is to account for ' : ' + $valueIndent = ' ' * ($propLength + 3) + # need to trim any extra whitespace already in the text + foreach ($line in $value.Split($newline)) { + if (!$isFirstLine) { + $null = $output.Append("${newline}${prefix}${valueIndent}") + } + $null = $output.Append($line.Trim()) + $isFirstLine = $false + } + } + else { + $null = $output.Append($value) + } + } + } + + $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() +} + +# Add back original typename and remove PSExtendedError +if ($_.PSObject.TypeNames.Contains('System.Management.Automation.ErrorRecord#PSExtendedError')) { + $_.PSObject.TypeNames.Add('System.Management.Automation.ErrorRecord') + $null = $_.PSObject.TypeNames.Remove('System.Management.Automation.ErrorRecord#PSExtendedError') +} +elseif ($_.PSObject.TypeNames.Contains('System.Exception#PSExtendedError')) { + $_.PSObject.TypeNames.Add('System.Exception') + $null = $_.PSObject.TypeNames.Remove('System.Exception#PSExtendedError') +} + +Show-ErrorRecord $_ +#> \ No newline at end of file diff --git a/source/ErrorView.format.ps1xml b/source/ErrorView.format.ps1xml index 443e23a..028b7b5 100644 --- a/source/ErrorView.format.ps1xml +++ b/source/ErrorView.format.ps1xml @@ -68,5 +68,35 @@ + + InformationRecord + + + System.Management.Automation.InformationRecord + + + + + + + + $_.MessageData | Format-List * | Out-String + + + + + "Tags: " + @($_.Tags -join ", ") + + + + + $_ | Select-Object * -ExcludeProperty Tags, MessageData | Format-List * -Force | Out-String + + + + + + + diff --git a/source/prefix.ps1 b/source/prefix.ps1 index 9f73337..aef18c4 100644 --- a/source/prefix.ps1 +++ b/source/prefix.ps1 @@ -1,8 +1,53 @@ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '', Justification = 'ErrorView is all about the ErrorView global variable')] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Justification = 'Seriously. Stop complaining about ErrorView')] param( $global:ErrorView = "Simple" ) # We need to _overwrite_ the ErrorView # So -PrependPath, instead of FormatsToProcess -Update-FormatData -PrependPath $PSScriptRoot\ErrorView.format.ps1xml \ No newline at end of file +Update-FormatData -PrependPath $PSScriptRoot\ErrorView.format.ps1xml + +Set-StrictMode -Off + +# Borrowed this one from https://github.com/chalk/ansi-regex +$script:AnsiEscapes = [Regex]::new("([\u001b\u009b][[\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\d\/#&.:=?%@~_]+)*|[a-zA-Z\d]+(?:;[-a-zA-Z\d\/#&.:=?%@~_]*)*)?(?:\u001b\u005c|\u0007))|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~])))", "Compiled"); + +# starting with an escape character and then... +# ESC ] - where ST is either 1B 5C or 7 (BEL, aka `a) +# ESC [ non-letters letter (or ~, =, @, >) +# ESC ( +# ESC O P +# ESC O Q +# ESC O R +# ESC O S +# $script:AnsiEscapes = [Regex]::new("\x1b[\(\)%`"&\.\/*+.-][@-Z]|\x1b\].*?(?:\u001b\u005c|\u0007|^)|\x1b\[\P{L}*[@-_A-Za-z^`\{\|\}~]|\x1b#\d|\x1b[!-~]", [System.Text.RegularExpressions.RegexOptions]::Compiled); + + + + + +$script:ellipsis = [char]0x2026 +$script:newline = [Environment]::Newline +$script:resetColor = '' +$script:errorColor = '' +$script:accentColor = '' +$script:errorAccentColor = '' +$script:LineColors = @( + "`e[38;2;255;255;255m" + "`e[38;2;179;179;179m" +) + +if ($Host.UI.SupportsVirtualTerminal -and ([string]::IsNullOrEmpty($env:__SuppressAnsiEscapeSequences))) { + if ($PSStyle) { + $script:resetColor = $PSStyle.Reset + $script:errorColor = $PSStyle.Formatting.Error + $script:accentColor = $PSStyle.Formatting.FormatAccent + $script:errorAccentColor = $PSStyle.Formatting.ErrorAccent + } else { + $script:resetColor = "$([char]27)[0m" + $script:errorColor = "$([char]27)[31m" + $script:accentColor = "$([char]27)[32;1m" + $script:errorAccentColor = "$([char]27)[31;1m" + } +} \ No newline at end of file diff --git a/source/private/GetListRecursive.ps1 b/source/private/GetListRecursive.ps1 new file mode 100644 index 0000000..a6cdd05 --- /dev/null +++ b/source/private/GetListRecursive.ps1 @@ -0,0 +1,160 @@ +function GetListRecursive { + <# + .SYNOPSIS + Internal implementation of the Detailed error view to support recursion and indentation + #> + [CmdletBinding()] + param( + $InputObject, + [int]$indent = 0, + [int]$depth = 1 + ) + Write-Information "ENTER GetListRecursive END $($InputObject.GetType().FullName) $indent $depth" -Tags 'Trace', 'Enter', 'GetListRecursive' + Write-Information (Get-PSCallStack) -Tags 'Trace', 'StackTrace', 'GetListRecursive' + $output = [System.Text.StringBuilder]::new() + $prefix = ' ' * $indent + + $expandTypes = @( + 'Microsoft.Rest.HttpRequestMessageWrapper' + 'Microsoft.Rest.HttpResponseMessageWrapper' + 'System.Management.Automation.InvocationInfo' + ) + + # The built-in DetailedView aligns all the ":" characters, so we need to find the longest property name + $propLength = 0 + foreach ($prop in $InputObject.PSObject.Properties) { + if ($null -ne $prop.Value -and $prop.Value -ne [string]::Empty -and $prop.Name.Length -gt $propLength) { + $propLength = $prop.Name.Length + } + } + + $addedProperty = $false + foreach ($prop in $InputObject.PSObject.Properties) { + # don't show empty properties or our added property for $error[index] + if ($null -ne $prop.Value -and $prop.Value -ne [string]::Empty -and $prop.Value.count -gt 0 -and $prop.Name -ne 'PSErrorIndex') { + $addedProperty = $true + $null = $output.Append($prefix) + $null = $output.Append($accentColor) + $null = $output.Append($prop.Name) + $null = $output.Append(' ',($propLength - $prop.Name.Length)) + $null = $output.Append(' : ') + $null = $output.Append($resetColor) + + $newIndent = $indent + 2 + + # 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 { + if ($prop.Value -is [Exception]) { + $null = $output.Append($newline) + $null = $output.Append(( + GetListRecursive ([PSCustomObject]@{ + "Type" = $errorAccentColor + $prop.Value.GetType().FullName + $resetColor + }) $newIndent ($depth + 1) + )) + } + $null = $output.Append($newline) + $null = $output.Append((GetListRecursive $prop.Value $newIndent ($depth + 1))) + } + } elseif ($prop.Name -eq 'TargetSite' -and $prop.Value.GetType().Name -eq 'RuntimeMethodInfo') { + # `TargetSite` has many members that are not useful visually, so we have a reduced view of the relevant members + if ($depth -ge $maxDepth) { + $null = $output.Append($ellipsis) + } else { + $targetSite = [PSCustomObject]@{ + Name = $prop.Value.Name + DeclaringType = $prop.Value.DeclaringType + MemberType = $prop.Value.MemberType + Module = $prop.Value.Module + } + + $null = $output.Append($newline) + $null = $output.Append((GetListRecursive $targetSite $newIndent ($depth + 1))) + } + } elseif ($prop.Name -eq 'StackTrace') { + # `StackTrace` is handled specifically because the lines are typically long but necessary so they are left justified without additional indentation + # for a stacktrace which is usually quite wide with info, we left justify it + $null = $output.Append($newline) + $null = $output.Append($prop.Value) + } elseif ($prop.Value.GetType().Name.StartsWith('Dictionary') -or $prop.Value.GetType().Name -eq 'Hashtable') { + # Dictionary and Hashtable we want to show as Key/Value pairs, we don't do the extra whitespace alignment here + $isFirstElement = $true + foreach ($key in $prop.Value.Keys) { + if ($isFirstElement) { + $null = $output.Append($newline) + } + + if ($key -eq 'Authorization') { + $null = $output.Append("${prefix} ${accentColor}${key}: ${resetColor}${ellipsis}${newline}") + } else { + $null = $output.Append("${prefix} ${accentColor}${key}: ${resetColor}$($prop.Value[$key])${newline}") + } + + $isFirstElement = $false + } + } elseif (!($prop.Value -is [System.String]) -and $null -ne $prop.Value.GetType().GetInterface('IEnumerable') -and $prop.Name -ne 'Data') { + # if the object implements IEnumerable and not a string, we try to show each object + # We ignore the `Data` property as it can contain lots of type information by the interpreter that isn't useful here + + if ($depth -ge $maxDepth) { + $null = $output.Append($ellipsis) + } else { + $isFirstElement = $true + foreach ($value in $prop.Value) { + $null = $output.Append($newline) + if (!$isFirstElement) { + $null = $output.Append($newline) + } + $null = $output.Append((GetListRecursive $value $newIndent ($depth + 1))) + $isFirstElement = $false + } + } + } else { + # Anything else, we convert to string. + # ToString() can throw so we use LanguagePrimitives.TryConvertTo() to hide a convert error + $value = $null + if ([System.Management.Automation.LanguagePrimitives]::TryConvertTo($prop.Value, [string], [ref]$value) -and $null -ne $value) { + $value = $value.Trim() + if ($prop.Name -eq 'PositionMessage') { + $value = $value.Insert($value.IndexOf('~'), $errorColor) + } elseif ($prop.Name -eq 'Message') { + $value = $errorColor + $value + } + + $isFirstLine = $true + if ($value.Contains($newline)) { + # the 3 is to account for ' : ' + # $valueIndent = ' ' * ($prop.Name.Length + 2) + $valueIndent = ' ' * ($propLength + 3) + # need to trim any extra whitespace already in the text + foreach ($line in $value.Split($newline)) { + if (!$isFirstLine) { + $null = $output.Append("${newline}${prefix}${valueIndent}") + } + $null = $output.Append($line.Trim()) + $isFirstLine = $false + } + } else { + $null = $output.Append($value) + } + } + } + + $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() + Write-Information "EXIT GetListRecursive END $($InputObject.GetType().FullName) $indent $depth (of $maxDepth)" -Tags 'Trace', 'Enter', 'GetListRecursive' +} diff --git a/source/private/GetYamlRecursive.ps1 b/source/private/GetYamlRecursive.ps1 new file mode 100644 index 0000000..ac3880e --- /dev/null +++ b/source/private/GetYamlRecursive.ps1 @@ -0,0 +1,169 @@ +function GetYamlRecursive { + <# + .SYNOPSIS + Creates a description of an ErrorRecord that looks like valid Yaml + .DESCRIPTION + This produces valid Yaml output from ErrorRecord you pass to it, recursively. + #> + [CmdletBinding()] + param( + # The object that you want to convert to YAML + [Parameter(Mandatory, ValueFromPipeline)] + $InputObject, + + # Optionally, a limit on the depth to recurse properties (defaults to 16) + [parameter()] + [int]$depth = 16, + + # If set, include empty and null properties in the output + [switch]$IncludeEmpty, + + # Recursive use only. Handles indentation for formatting + [parameter(DontShow)] + [int]$NestingLevel = 0, + + # use OuterXml instead of treating XmlDocuments like objects + [parameter(DontShow)] + [switch]$XmlAsXml + ) + process { + $wrap = [Console]::BufferWidth - 1 - ($NestingLevel * 2) + @( + if ($Null -eq $InputObject) { return 'null' } # if it is null return null + if ($NestingLevel -eq 0 -and $local:__hasoutput) { '---' } # if we have output before, add a yaml separator + $__hasoutput = $true + $padding = "`n$(' ' * $NestingLevel)" # lets just create our left-padding for the block + $Recurse = @{ + 'Depth' = $depth - 1 + 'NestingLevel' = $NestingLevel + 1 + 'XmlAsXml' = $XmlAsXml + } + $Wrap = + + try { + switch ($InputObject) { + # prevent these values being expanded + <# if ($Type -in @( 'guid', + , 'datatable', 'List`1','SqlDataReader', 'datarow', 'type', + 'MemberTypes', 'RuntimeModule', 'RuntimeType', 'ErrorCategoryInfo', 'CommandInfo', 'CmdletInfo' )) { + #> + { $InputObject -is [scriptblock] } { + "{$($InputObject.ToString())}" + break + } + { $InputObject -is [type] } { + "'[$($InputObject.FullName)]'" + break + } + { $InputObject -is [System.Xml.XmlDocument] -or $InputObject -is [System.Xml.XmlElement] } { + "|" + $InputObject.OuterXml | WrapString $Wrap $padding -Colors:$LineColors + break + } + { $InputObject -is [datetime] -or $InputObject -is [datetimeoffset] } { + # s=SortableDateTimePattern (based on ISO 8601) using local time + $InputObject.ToString('s') + break + } + { $InputObject -is [timespan] -or $InputObject -is [version] -or $InputObject -is [uri] } { + # s=SortableDateTimePattern (based on ISO 8601) using local time + "'$InputObject'" + break + } + # yaml case for booleans + { $InputObject -is [bool] } { + if ($InputObject) { 'true' } else { 'false' } + break + } + # If we're going to go over our depth, just output like it's a value type + # ValueTypes are just output with no possibility of wrapping or recursion + { $InputObject -is [Enum] -or $InputObject.GetType().BaseType -eq [ValueType] -or $depth -eq 1 } { + "$InputObject" + break + } + # 'PSNoteProperty' { + # # Write-Verbose "$($padding)Show $($property.Name)" + # GetYamlRecursive -InputObject $InputObject.Value @Recurse } + { $InputObject -is [System.Collections.IDictionary] } { + foreach ($kvp in $InputObject.GetEnumerator()) { + # Write-Verbose "$($padding)Enumerate $($property.Name)" + "$padding$accentColor$($kvp.Name):$resetColor " + + (GetYamlRecursive -InputObject $kvp.Value @Recurse) + } + break + } + + { $InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [string] } { + foreach ($item in $InputObject) { + # Write-Verbose "$($padding)Enumerate $($property.Name)" + $Value = GetYamlRecursive -InputObject $item @Recurse + # if ($Value -ne 'null' -or $IncludeEmpty) { + "$accentColor$padding$resetColor- $Value" + # } + } + break + } + + # Limit recursive enumeration to specific types: + { $InputObject -is [Exception] -or $InputObject -is [System.Management.Automation.ErrorRecord] -or + $InputObject.PSTypeNames[0] -in @( + 'System.Exception' + 'System.Management.Automation.ErrorRecord' + 'Microsoft.Rest.HttpRequestMessageWrapper' + 'Microsoft.Rest.HttpResponseMessageWrapper' + 'System.Management.Automation.InvocationInfo' + ) } { + # For exceptions, output a fake property for the exception type + if ($InputObject -is [Exception]) { + "$padding${accentColor}#Type:$resetColor ${errorAccentColor}" + $InputObject.GetType().FullName + $resetColor + } + foreach ($property in $InputObject.PSObject.Properties) { + if ($property.Value) { + $Value = GetYamlRecursive -InputObject $property.Value @Recurse + # For special cases, add some color: + if ($property.Name -eq "PositionMessage") { + $Value = $Value -replace "(\+\s+)(~+)", "`$1$errorColor`$2$resetColor" + } + if ($InputObject -is [Exception] -and $property.Name -eq "Message") { + $Value = "$errorColor$Value$resetColor" + } + if ((-not [string]::IsNullOrEmpty($Value) -and $Value -ne 'null' -and $Value.Count -gt 0) -or $IncludeEmpty) { + "$padding$accentColor$($property.Name):$resetColor " + $Value + } + } + } + break + } + # 'generic' { + # foreach($key in $InputObject.Keys) { + # # Write-Verbose "$($padding)Enumerate $($key)" + # $Value = GetYamlRecursive -InputObject $InputObject.$key @Recurse + # if ((-not [string]::IsNullOrEmpty($Value) -and $Value -ne 'null') -or $IncludeEmpty) { + # "$padding$accentColor$($key):$resetColor " + $Value + # } + # } + # } + default { + # Treat anything else as a string + $StringValue = $null + if ([System.Management.Automation.LanguagePrimitives]::TryConvertTo($InputObject, [string], [ref]$StringValue) -and $null -ne $StringValue) { + $StringValue = $StringValue.Trim() + if ($StringValue -match '[\r\n]' -or $StringValue.Length -gt $wrap) { + ">" # signal that we are going to use the readable 'newlines-folded' format + $StringValue | WrapString $Wrap $padding -Colors:$LineColors + } elseif ($StringValue.Contains(":")) { + "'$($StringValue -replace '''', '''''')'" # single quote it + } else { + "$($StringValue -replace '''', '''''')" + } + } else { + Write-Warning "Unable to convert $($InputObject.GetType().FullName) to string" + } + } + } + } catch { + Write-Error "Error'$($_)' in script $($_.InvocationInfo.ScriptName) $($_.InvocationInfo.Line.Trim()) (line $($_.InvocationInfo.ScriptLineNumber)) char $($_.InvocationInfo.OffsetInLine) executing $($_.InvocationInfo.MyCommand) on $type object '$($InputObject)' Class: $($InputObject.GetType().Name) BaseClass: $($InputObject.GetType().BaseType.Name) " + } + ) -join "" + } +} \ No newline at end of file diff --git a/source/private/WrapString.ps1 b/source/private/WrapString.ps1 new file mode 100644 index 0000000..4d3fbfa --- /dev/null +++ b/source/private/WrapString.ps1 @@ -0,0 +1,119 @@ +$script:AnsiPattern = "[\u001b\u009b][[\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\d\/#&.:=?%@~_]+)*|[a-zA-Z\d]+(?:;[-a-zA-Z\d\/#&.:=?%@~_]*)*)?(?:\u001b\u005c|\u0007))|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))" +$script:AnsiRegex = [Regex]::new($AnsiPattern, "Compiled"); +function MeasureString { + [CmdletBinding()] + param( + [string]$InputObject + ) + $AnsiRegex.Replace($InputObject, '').Length +} + + +filter WrapString { + [CmdletBinding()] + param( + # The input string will be wrapped to a certain length, with optional padding on the front + [Parameter(ValueFromPipeline)] + [string]$InputObject, + + # The maximum length of a line. Defaults to [Console]::BufferWidth - 1 + [Parameter(Position=0)] + [int]$Width = ($Host.UI.RawUI.BufferSize.Width), + + # The padding to add to the front of each line to cause indenting. Defaults to empty string. + [Parameter(Position=1)] + [string]$IndentPadding = ([string]::Empty), + + # If set, colors to use for alternating lines + [string[]]$Colors = @(''), + + # If set, will output empty lines for each original new line + [switch]$EmphasizeOriginalNewlines + ) + begin { + $color = 0; + Write-Debug "Colors: $($Colors -replace "`e(.+)", "`e`$1``e`$1")" + # $wrappableChars = [char[]]" ,.?!:;-`n`r`t" + # $maxLength = $width - $IndentPadding.Length -1 + $wrapper = [Regex]::new("((?:$AnsiPattern)*[^-=,.?!:;\s\r\n\t\\\/\|]+(?:$AnsiPattern)*)", "Compiled") + $output = [System.Text.StringBuilder]::new($Colors[$color] + $IndentPadding) + } + process { + foreach($line in $InputObject -split "(\r?\n)") { + # Don't bother trying to split empty lines + if ([String]::IsNullOrWhiteSpace($AnsiRegex.Replace($line, ''))) { + Write-Debug "Empty String ($($line.Length))" + if ($EmphasizeOriginalNewlines) { [string]::Empty } + continue + } + + $slices = $line -split $wrapper | ForEach-Object { @{ Text = $_; Length = MeasureString $_ } } + Write-Debug "$($line.Length) words in line. $($AnsiRegex.Replace($line, ''))" + $lineLength = $IndentPadding.Length + foreach($slice in $slices) { + $lineLength += $slice.Length + if ($lineLength -le $Width) { + Write-Verbose "+ $($slice.Length) = $lineLength < $Width" + $null = $output.Append($slice.Text) + } else { + Write-Verbose "Output $($lineLength - $slice.Length)" + Write-Verbose "+ $($slice.Length) = $($slice.Length)" + $color = ($color + 1) % $Colors.Length + $output.ToString().Trim() + $null = $output.Clear().Append($Colors[$color]).Append($IndentPadding).Append($slice.Text) + $lineLength = $IndentPadding.Length + $slice.Length + } + } + $output.ToString().Trim() + $null = $output.Clear().Append($Colors[$color]).Append($IndentPadding) + } + + # $currentIndex = 0; + # $lastWrap = 0; + # do { + # $lastWrap = $currentIndex; + # #Write-Verbose "m: $Width, l: $($line.Length), c: $color $($Colors[$color] -replace "`e","``e")" + + + + # if ($AnsiEscapes.Replace($first, '').Length -gt $maxLength + 1) { + # # If we couldn't find a good place to wrap, just wrap at the end of the line + # $first = $line.Substring(0, $maxLength+1) + # $line = $line.Substring($maxLength+1) + # } + # $Colors[$color] + $IndentPadding + $first.TrimEnd() + +<# $currentIndex = $( + if ($lastWrap + $Width -gt $line.Length) { + $line.Length + } else { + $line.LastIndexOfAny($wrappableChars, ([Math]::Min(($line.Length - 1), ($lastWrap + $Width)))) + 1 + } + ) + + $slice = $line.Substring($lastWrap, ($currentIndex - $lastWrap)) + if (($slice.Length - $script:AnsiEscapes.Replace($slice,'').Length) -gt 0) { + $currentIndex = $( + if ($lastWrap + $Width -gt $line.Length) { + $line.Length + } else { + $line.LastIndexOfAny($wrappableChars, ([Math]::Min(($line.Length - 1), ($lastWrap + $Width)))) + 1 + } + ) + } + + # If we couldn't find a good place to wrap, just wrap at the end of the line + if ($currentIndex -le $lastWrap) { + $currentIndex = [Math]::Min(($lastWrap + $Width), $line.Length ) + } + # Output the line, with the appropriate color and padding + $Colors[$color] + $IndentPadding + $line.Substring($lastWrap, ($currentIndex - $lastWrap)).TrimEnd() +#> +<# } while($line); + + if ($line -ne "`n") { + $color = ($color + 1) % $Colors.Length + } + } #> + } +} \ No newline at end of file diff --git a/source/public/ConvertTo-ConciseErrorView.ps1 b/source/public/ConvertTo-ConciseErrorView.ps1 new file mode 100644 index 0000000..391014f --- /dev/null +++ b/source/public/ConvertTo-ConciseErrorView.ps1 @@ -0,0 +1,62 @@ +function ConvertTo-ConciseErrorView { + [CmdletBinding()] + param( + [System.Management.Automation.ErrorRecord] + $InputObject + ) + if ("$accentColor".Length) { + $local:accentColor = $script:errorAccentColor + $local:resetColor = $script:resetColor + } else { + $local:accentColor = ">>>" + $local:resetColor = "<<<" + } + + + if ($InputObject.FullyQualifiedErrorId -in 'NativeCommandErrorMessage','NativeCommandError') { + $errorColor + $InputObject.Exception.Message + $resetColor + } else { + $myinv = $InputObject.InvocationInfo + if ($myinv -and ($myinv.MyCommand -or ($InputObject.CategoryInfo.Category -ne 'ParserError'))) { + # rip off lines that say "At line:1 char:1" (hopefully, in a language agnostic way) + $posmsg = $myinv.PositionMessage -replace "^At line:1 char:1[\r\n]+" + + # rip off the underline and use the accentcolor instead + $pattern = $posmsg -split "[\r\n]+" -match "\+( +~+)\s*" -replace '(~+)', '($1)' -replace '( +)','($1)' -replace '~| ','.' + $posmsg = $posmsg -replace '[\r\n]+\+ +~+' + if ($pattern) { + $posmsg = $posmsg -replace "\+$pattern", "+`$1$accentColor`$2$resetColor" + } + } else { + $posmsg = "" + } + + if ($posmsg -ne "") { + $posmsg = "`n" + $posmsg + } + + if ( & { Set-StrictMode -Version 1; $InputObject.PSMessageDetails } ) { + $posmsg = " : " + $InputObject.PSMessageDetails + $posmsg + } + + $indent = 4 + $width = $host.UI.RawUI.BufferSize.Width - $indent - 2 + + $originInfo = & { Set-StrictMode -Version 1; $InputObject.OriginInfo } + if (($null -ne $originInfo) -and ($null -ne $originInfo.PSComputerName)) { + $indentString = "+ PSComputerName : " + $originInfo.PSComputerName + $posmsg += "`n" + foreach ($line in @($indentString -split "(.{$width})")) { + if ($line) { + $posmsg += (" " * $indent + $line) + } + } + } + + if (!$InputObject.ErrorDetails -or !$InputObject.ErrorDetails.Message) { + $errorColor + $InputObject.Exception.Message + $resetColor + $posmsg + "`n " + } else { + $errorColor + $InputObject.ErrorDetails.Message + $resetColor + $posmsg + } + } +} \ No newline at end of file diff --git a/source/public/ConvertTo-DetailedErrorView.ps1 b/source/public/ConvertTo-DetailedErrorView.ps1 index fbc1c58..210520c 100644 --- a/source/public/ConvertTo-DetailedErrorView.ps1 +++ b/source/public/ConvertTo-DetailedErrorView.ps1 @@ -21,186 +21,13 @@ function ConvertTo-DetailedErrorView { ) begin { + Write-Information "ENTER ConvertTo-DetailedErrorView BEGIN " -Tags 'Trace', 'Enter', 'ConvertTo-DetailedErrorView' - Set-StrictMode -Off - - $ellipsis = "`u{2026}" - $resetColor = '' - $errorColor = '' - $accentColor = '' - $newline = [Environment]::Newline - $OutputRoot = [System.Text.StringBuilder]::new() - - if ($Host.UI.SupportsVirtualTerminal -and ([string]::IsNullOrEmpty($env:__SuppressAnsiEscapeSequences))) { - $resetColor = "$([char]0x1b)[0m" - $errorColor = if ($PSStyle.Formatting.Error) { $PSStyle.Formatting.Error } else { "`e[1;31m" } - $accentColor = if ($PSStyle.Formatting.ErrorAccent) { $PSStyle.Formatting.ErrorAccent } else { "`e[1;36m" } - } - - function DetailedErrorView { - <# - .SYNOPSIS - Internal implementation of the Detailed error view to support recursion and indentation - #> - [CmdletBinding()] - param( - $InputObject, - [int]$indent = 0, - [int]$depth = 1 - ) - $prefix = ' ' * $indent - - $expandTypes = @( - 'Microsoft.Rest.HttpRequestMessageWrapper' - 'Microsoft.Rest.HttpResponseMessageWrapper' - 'System.Management.Automation.InvocationInfo' - ) - - # if object is an Exception, add an ExceptionType property - if ($InputObject -is [Exception]) { - $InputObject | Add-Member -NotePropertyName Type -NotePropertyValue $InputObject.GetType().FullName -ErrorAction Ignore - } - - # first find the longest property so we can indent properly - $propLength = 0 - foreach ($prop in $InputObject.PSObject.Properties) { - if ($null -ne $prop.Value -and $prop.Value -ne [string]::Empty -and $prop.Name.Length -gt $propLength) { - $propLength = $prop.Name.Length - } - } - - $addedProperty = $false - foreach ($prop in $InputObject.PSObject.Properties) { - - # don't show empty properties or our added property for $error[index] - if ($null -ne $prop.Value -and $prop.Value -ne [string]::Empty -and $prop.Value.count -gt 0 -and $prop.Name -ne 'PSErrorIndex') { - $addedProperty = $true - $null = $OutputRoot.Append($prefix) - $null = $OutputRoot.Append($accentColor) - $null = $OutputRoot.Append($prop.Name) - $null = $OutputRoot.Append(' ',($propLength - $prop.Name.Length)) - $null = $OutputRoot.Append(' : ') - $null = $OutputRoot.Append($resetColor) - - $newIndent = $indent + 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 = $OutputRoot.Append($ellipsis) - } - else { - $null = $OutputRoot.Append($newline) - $null = $OutputRoot.Append((DetailedErrorView $prop.Value $newIndent ($depth + 1))) - } - } - # `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 = $OutputRoot.Append($ellipsis) - } - else { - $targetSite = [PSCustomObject]@{ - Name = $prop.Value.Name - DeclaringType = $prop.Value.DeclaringType - MemberType = $prop.Value.MemberType - Module = $prop.Value.Module - } - - $null = $OutputRoot.Append($newline) - $null = $OutputRoot.Append((DetailedErrorView $targetSite $newIndent ($depth + 1))) - } - } - # `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') { - # for a stacktrace which is usually quite wide with info, we left justify it - $null = $OutputRoot.Append($newline) - $null = $OutputRoot.Append($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') { - $isFirstElement = $true - foreach ($key in $prop.Value.Keys) { - if ($isFirstElement) { - $null = $OutputRoot.Append($newline) - } - - if ($key -eq 'Authorization') { - $null = $OutputRoot.Append("${prefix} ${accentColor}${key} : ${resetColor}${ellipsis}${newline}") - } - else { - $null = $OutputRoot.Append("${prefix} ${accentColor}${key} : ${resetColor}$($prop.Value[$key])${newline}") - } - - $isFirstElement = $false - } - } - # if the object implements IEnumerable and not a string, we try to show each object - # We ignore the `Data` property as it can contain lots of type information by the interpreter that isn't useful here - elseif (!($prop.Value -is [System.String]) -and $null -ne $prop.Value.GetType().GetInterface('IEnumerable') -and $prop.Name -ne 'Data') { - - if ($depth -ge $maxDepth) { - $null = $OutputRoot.Append($ellipsis) - } - else { - $isFirstElement = $true - foreach ($value in $prop.Value) { - $null = $OutputRoot.Append($newline) - if (!$isFirstElement) { - $null = $OutputRoot.Append($newline) - } - $null = $OutputRoot.Append((DetailedErrorView $value $newIndent ($depth + 1))) - $isFirstElement = $false - } - } - } - # 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 $null -ne $value) - { - if ($prop.Name -eq 'PositionMessage') { - $value = $value.Insert($value.IndexOf('~'), $errorColor) - } - elseif ($prop.Name -eq 'Message') { - $value = $errorColor + $value - } - - $isFirstLine = $true - if ($value.Contains($newline)) { - # the 3 is to account for ' : ' - $valueIndent = ' ' * ($propLength + 3) - # need to trim any extra whitespace already in the text - foreach ($line in $value.Split($newline)) { - if (!$isFirstLine) { - $null = $OutputRoot.Append("${newline}${prefix}${valueIndent}") - } - $null = $OutputRoot.Append($line.Trim()) - $isFirstLine = $false - } - } - else { - $null = $OutputRoot.Append($value) - } - } - } - - $null = $OutputRoot.Append($newline) - } - } - - # if we had added nested properties, we need to remove the last newline - if ($addedProperty) { - $null = $OutputRoot.Remove($OutputRoot.Length - $newline.Length, $newline.Length) - } - - $OutputRoot.ToString() - } + Write-Information "EXIT ConvertTo-DetailedErrorView BEGIN" -Tags 'Trace', 'Enter', 'ConvertTo-DetailedErrorView' } process { - DetailedErrorView $InputObject + Write-Information "ENTER ConvertTo-DetailedErrorView PROCESS $($InputObject.GetType().FullName)" -Tags 'Trace', 'Enter', 'ConvertTo-DetailedErrorView' + GetListRecursive $InputObject + Write-Information "EXIT ConvertTo-DetailedErrorView PROCESS $($InputObject.GetType().FullName)" -Tags 'Trace', 'Enter', 'ConvertTo-DetailedErrorView' } } diff --git a/source/public/ConvertTo-FullErrorView.ps1 b/source/public/ConvertTo-FullErrorView.ps1 index 5377ef8..c13705e 100644 --- a/source/public/ConvertTo-FullErrorView.ps1 +++ b/source/public/ConvertTo-FullErrorView.ps1 @@ -22,13 +22,10 @@ filter ConvertTo-FullErrorView { $resetColor = "$([char]0x1b)[0m" $errorColor = if ($PSStyle.Formatting.Error) { $PSStyle.Formatting.Error } else { "`e[1;31m" } - #$accentColor = if ($PSStyle.Formatting.ErrorAccent) { $PSStyle.Formatting.ErrorAccent } else { "`e[1;36m" } - $Detail = $InputObject | Format-List * -Force | Out-String -Width 120 - $Detail = $Detail -replace "((?:Exception|FullyQualifiedErrorId).*`e\[0m)(.*)", "$($PSStyle.Formatting.ErrorAccent)`$1$($PSStyle.Formatting.Error)`$2$($PSStyle.Reset)" - } else { - $Detail = $InputObject | Format-List * -Force | Out-String -Width 120 } + $Detail = $InputObject | Format-List * -Force | Out-String -Width 120 + # NOTE: ErrorViewRecurse is normally false, and only set temporarily by Format-Error -Recurse if ($ErrorViewRecurse) { $Count = 1 diff --git a/source/public/ConvertTo-NormalErrorView.ps1 b/source/public/ConvertTo-NormalErrorView.ps1 index cdcd820..a33dc72 100644 --- a/source/public/ConvertTo-NormalErrorView.ps1 +++ b/source/public/ConvertTo-NormalErrorView.ps1 @@ -11,17 +11,8 @@ filter ConvertTo-NormalErrorView { [System.Management.Automation.ErrorRecord] $InputObject ) - $resetColor = '' - $errorColor = '' - #$accentColor = '' - if ($Host.UI.SupportsVirtualTerminal -and ([string]::IsNullOrEmpty($env:__SuppressAnsiEscapeSequences))) { - $resetColor = "$([char]0x1b)[0m" - $errorColor = if ($PSStyle.Formatting.Error) { $PSStyle.Formatting.Error } else { "`e[1;31m" } - #$accentColor = if ($PSStyle.Formatting.ErrorAccent) { $PSStyle.Formatting.ErrorAccent } else { "`e[1;36m" } - } - - if ($InputObject.FullyQualifiedErrorId -eq "NativeCommandErrorMessage") { + if ($InputObject.FullyQualifiedErrorId -in 'NativeCommandErrorMessage','NativeCommandError') { $errorColor + $InputObject.Exception.Message + $resetColor } else { $myinv = $InputObject.InvocationInfo @@ -44,9 +35,9 @@ filter ConvertTo-NormalErrorView { $errorCategoryMsg = &{ Set-StrictMode -Version 1; $InputObject.ErrorCategory_Message } if ($null -ne $errorCategoryMsg) { - $indentString = "+ CategoryInfo : " + $InputObject.ErrorCategory_Message + $indentString = $accentColor + "+ CategoryInfo : " + $resetColor + $InputObject.ErrorCategory_Message } else { - $indentString = "+ CategoryInfo : " + $InputObject.CategoryInfo + $indentString = $accentColor + "+ CategoryInfo : " + $resetColor + $InputObject.CategoryInfo } $posmsg += "`n" foreach ($line in @($indentString -split "(.{$width})")) { @@ -55,7 +46,7 @@ filter ConvertTo-NormalErrorView { } } - $indentString = "+ FullyQualifiedErrorId : " + $InputObject.FullyQualifiedErrorId + $indentString = $accentColor + "+ FullyQualifiedErrorId: " + $resetColor + $InputObject.FullyQualifiedErrorId $posmsg += "`n" foreach ($line in @($indentString -split "(.{$width})")) { if ($line) { @@ -65,7 +56,7 @@ filter ConvertTo-NormalErrorView { $originInfo = &{ Set-StrictMode -Version 1; $InputObject.OriginInfo } if (($null -ne $originInfo) -and ($null -ne $originInfo.PSComputerName)) { - $indentString = "+ PSComputerName : " + $originInfo.PSComputerName + $indentString = "+ PSComputerName : " + $originInfo.PSComputerName $posmsg += "`n" foreach ($line in @($indentString -split "(.{$width})")) { if ($line) { @@ -75,9 +66,9 @@ filter ConvertTo-NormalErrorView { } if (!$InputObject.ErrorDetails -or !$InputObject.ErrorDetails.Message) { - $errorColor + $InputObject.Exception.Message + $posmsg + $resetColor + "`n " + $errorColor + $InputObject.Exception.Message + $resetColor + $posmsg + "`n " } else { - $errorColor + $InputObject.ErrorDetails.Message + $posmsg + $resetColor + $errorColor + $InputObject.ErrorDetails.Message + $resetColor + $posmsg } } } \ No newline at end of file diff --git a/source/public/ConvertTo-NormalExceptionView.ps1 b/source/public/ConvertTo-NormalExceptionView.ps1 index fea1db0..929b040 100644 --- a/source/public/ConvertTo-NormalExceptionView.ps1 +++ b/source/public/ConvertTo-NormalExceptionView.ps1 @@ -21,6 +21,6 @@ filter ConvertTo-NormalExceptionView { #$accentColor = if ($PSStyle.Formatting.ErrorAccent) { $PSStyle.Formatting.ErrorAccent } else { "`e[1;36m" } } - $errorColor + $InputObject.Exception.Message + $resetColor + $errorColor + $InputObject.Message + $resetColor } \ No newline at end of file diff --git a/source/public/ConvertTo-SimpleErrorView.ps1 b/source/public/ConvertTo-SimpleErrorView.ps1 index 97197ff..c8b836b 100644 --- a/source/public/ConvertTo-SimpleErrorView.ps1 +++ b/source/public/ConvertTo-SimpleErrorView.ps1 @@ -14,28 +14,17 @@ function ConvertTo-SimpleErrorView { #$accentColor = if ($PSStyle.Formatting.ErrorAccent) { $PSStyle.Formatting.ErrorAccent } else { "`e[1;36m" } } - if ($InputObject.FullyQualifiedErrorId -eq "NativeCommandErrorMessage") { + if ($InputObject.FullyQualifiedErrorId -in 'NativeCommandErrorMessage','NativeCommandError') { $InputObject.Exception.Message } else { $myinv = $InputObject.InvocationInfo if ($myinv -and ($myinv.MyCommand -or ($InputObject.CategoryInfo.Category -ne 'ParserError'))) { # rip off lines that say "At line:1 char:1" (hopefully, in a language agnostic way) - $posmsg = $myinv.PositionMessage -replace "^At line:1 .*[\r\n]+" - # rip off the underline and instead, put >>>markers<<< around the important bit - # we could, instead, set the background to a highlight color? - $pattern = $posmsg -split "[\r\n]+" -match "\+( +~+)\s*" -replace '(~+)', '($1)' -replace '( +)','($1)' -replace '~| ','.' - $posmsg = $posmsg -replace '[\r\n]+\+ +~+' - if ($pattern) { - $posmsg = $posmsg -replace "\+$pattern", '+ $1>>>$2<<<' - } + $posmsg = "`n" + $myinv.PositionMessage -replace "^At line:1 .*[\r\n]+" } else { $posmsg = "" } - if ($posmsg -ne "") { - $posmsg = "`n" + $posmsg - } - if ( & { Set-StrictMode -Version 1; $InputObject.PSMessageDetails } ) { $posmsg = " : " + $InputObject.PSMessageDetails + $posmsg } diff --git a/source/public/ConvertTo-YamlErrorView.ps1 b/source/public/ConvertTo-YamlErrorView.ps1 new file mode 100644 index 0000000..704bc2d --- /dev/null +++ b/source/public/ConvertTo-YamlErrorView.ps1 @@ -0,0 +1,33 @@ +function ConvertTo-YamlErrorView { + <# + .SYNOPSIS + Creates a description of an ErrorRecord that looks like valid Yaml + .DESCRIPTION + This produces valid Yaml output from ErrorRecord you pass to it, recursively. + #> + [CmdletBinding()] + param( + # The object that you want to convert to YAML + [Parameter(Mandatory, ValueFromPipeline)] + [System.Management.Automation.ErrorRecord] + $InputObject, + + # Optionally, a limit on the depth to recurse properties (defaults to 16) + [parameter()] + [int]$depth = 16, + + # If set, include empty and null properties in the output + [switch]$IncludeEmpty, + + # Recursive use only. Handles indentation for formatting + [parameter(DontShow)] + [int]$NestingLevel = 0, + + # use OuterXml instead of treating XmlDocuments like objects + [parameter(DontShow)] + [switch]$XmlAsXml + ) + process { + GetYamlRecursive $InputObject + } +} \ No newline at end of file diff --git a/source/public/Format-Error.ps1 b/source/public/Format-Error.ps1 index 2e7edf2..6a28a37 100644 --- a/source/public/Format-Error.ps1 +++ b/source/public/Format-Error.ps1 @@ -1,13 +1,13 @@ function Format-Error { <# .SYNOPSIS - Formats an error for the screen using a specified error view + Formats an error (or exception) for the screen using a specified error view .DESCRIPTION Temporarily switches the error view and outputs the errors .EXAMPLE Format-Error - Shows the Normal error view for the most recent error + Shows the Detailed error view for the most recent error (changed to be compatible with Get-Error) .EXAMPLE $error[0..4] | Format-Error Full @@ -17,8 +17,8 @@ function Format-Error { Shows the full error view of the specific error, recursing into the inner exceptions (if that's supported by the view) #> - [CmdletBinding(DefaultParameterSetName="Count")] - [Alias("fe", "Get-Error")] + [CmdletBinding(DefaultParameterSetName = "InputObject")] + [Alias("fe"<#, "Get-Error"#>)] [OutputType([System.Management.Automation.ErrorRecord])] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Justification = 'The ArgumentCompleter parameters are the required method signature')] @@ -28,7 +28,7 @@ function Format-Error { [ArgumentCompleter({ param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) [System.Management.Automation.CompletionResult[]](( - Get-Command ConvertTo-*ErrorView -ListImported -ParameterName InputObject -ParameterType [System.Management.Automation.ErrorRecord] + Get-Command ConvertTo-*ErrorView -ListImported -ParameterName InputObject -ParameterType [System.Management.Automation.ErrorRecord], [System.Exception] ).Name -replace "ConvertTo-(.*)ErrorView",'$1' -like "*$($wordToComplete)*") })] $View = "Detailed", @@ -47,19 +47,22 @@ function Format-Error { } ), - # Allows ErrorView functions to recurse to InnerException + # Encourages ErrorView functions to recurse InnerException properties [switch]$Recurse ) begin { $ErrorActionPreference = "Continue" - $ErrorView, $View = $View, $ErrorView - [bool]$Recurse, [bool]$ErrorViewRecurse = [bool]$ErrorViewRecurse, $Recurse + + $local:_ErrorView, $global:ErrorView = $global:ErrorView, $View + $local:_ErrorViewRecurse, [bool]$global:ErrorViewRecurse = [bool]$global:ErrorViewRecurse, $Recurse } process { $InputObject } end { - [bool]$ErrorViewRecurse = $Recurse - $ErrorView = $View + $global:ErrorView = $local:_ErrorView + if ($null -ne $local:_ErrorViewRecurse) { + [bool]$global:ErrorViewRecurse = $local:_ErrorViewRecurse + } } } diff --git a/source/public/Set-ErrorView.ps1 b/source/public/Set-ErrorView.ps1 index 8041415..340394a 100644 --- a/source/public/Set-ErrorView.ps1 +++ b/source/public/Set-ErrorView.ps1 @@ -27,6 +27,7 @@ filter Set-ErrorView { ).Name -replace "ConvertTo-(\w+)ErrorView", '$1View' | Select-Object -Unique $ofs = ';' - .([ScriptBlock]::Create("enum ErrorView { $Names }")) + . ([ScriptBlock]::Create("enum ErrorView { $Names }")) + [ErrorView]$global:ErrorView = $View } diff --git a/source/public/Write-NativeCommandError.ps1 b/source/public/Write-NativeCommandError.ps1 index 85d98d1..5ab8889 100644 --- a/source/public/Write-NativeCommandError.ps1 +++ b/source/public/Write-NativeCommandError.ps1 @@ -12,36 +12,40 @@ function Write-NativeCommandError { $errorColor = if ($PSStyle.Formatting.Error) { $PSStyle.Formatting.Error } else { "`e[1;31m" } $accentColor = $PSStyle.Formatting.ErrorAccent } + if ($InputObject -is [System.Exception]) { + $errorColor + $InputObject.GetType().FullName + " : " + $resetColor + } - if ($InputObject.FullyQualifiedErrorId -eq "NativeCommandErrorMessage") { return } - - $invoc = $InputObject.InvocationInfo - if ($invoc -and $invoc.MyCommand) { - switch -regex ( $invoc.MyCommand.CommandType ) { - ([System.Management.Automation.CommandTypes]::ExternalScript) { - if ($invoc.MyCommand.Path) { - $accentColor + $invoc.MyCommand.Path + " : " + $resetColor + # @('NativeCommandErrorMessage', 'NativeCommandError') -notcontains $_.FullyQualifiedErrorId -and @('CategoryView', 'ConciseView', 'DetailedView') -notcontains $ErrorView + if (@('NativeCommandErrorMessage', 'NativeCommandError') -notcontains $_.FullyQualifiedErrorId -and @('CategoryView', 'ConciseView', 'DetailedView') -notcontains $ErrorView) { + $invoc = $InputObject.InvocationInfo + if ($invoc -and $invoc.MyCommand) { + switch -regex ( $invoc.MyCommand.CommandType ) { + ([System.Management.Automation.CommandTypes]::ExternalScript) { + if ($invoc.MyCommand.Path) { + $errorColor + $invoc.MyCommand.Path + " : " + $resetColor + } + break } - break - } - ([System.Management.Automation.CommandTypes]::Script) { - if ($invoc.MyCommand.ScriptBlock) { - $accentColor + $invoc.MyCommand.ScriptBlock.ToString() + " : " + $resetColor + ([System.Management.Automation.CommandTypes]::Script) { + if ($invoc.MyCommand.ScriptBlock) { + $errorColor + $invoc.MyCommand.ScriptBlock.ToString() + " : " + $resetColor + } + break } - break - } - default { - if ($invoc.InvocationName -match '^[&\.]?$') { - if ($invoc.MyCommand.Name) { - $accentColor + $invoc.MyCommand.Name + " : " + $resetColor + default { + if ($invoc.InvocationName -match '^[&\.]?$') { + if ($invoc.MyCommand.Name) { + $errorColor + $invoc.MyCommand.Name + " : " + $resetColor + } + } else { + $errorColor + $invoc.InvocationName + " : " + $resetColor } - } else { - $accentColor + $invoc.InvocationName + " : " + $resetColor + break } - break } + } elseif ($invoc -and $invoc.InvocationName) { + $errorColor + $invoc.InvocationName + " : " + $resetColor } - } elseif ($invoc -and $invoc.InvocationName) { - $accentColor + $invoc.InvocationName + " : " + $resetColor } } \ No newline at end of file