Repository navigation
Description
Summary of the new feature / enhancement
Provide a standardised way to render nested attributes expanded (with Format-Table and Format-List). This would affect any PSObject with nested attributes. However, it would also make a tree.com replacement using Get-ChildItem -Recurse significantly easier to implement.
As an example, consider the undermentioned question that I posed earlier today: 1
#!/usr/bin/env pwsh Get-ErrorDetails
Exception : Type : System.Management.Automation.CommandNotFoundException ErrorRecord : Exception : Type : System.Management.Automation.ParentContainsErrorRecordException Message : The 'Show-Tree' command was found in the module 'Pscx', but the module could not be loaded due to the following error: [Could not load file or assembly 'System.Messaging, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The system cannot find the file specified. ] For more information, run 'Import-Module Pscx'. HResult : -2146233087 TargetObject : Show-Tree CategoryInfo : ObjectNotFound: (Show-Tree:String) [], ParentContainsErrorRecordException FullyQualifiedErrorId : CouldNotAutoloadMatchingModule InvocationInfo : ScriptLineNumber : 1 OffsetInLine : 1 HistoryId : 22 Line : Show-Tree Statement : Show-Tree PositionMessage : At line:1 char:1 + Show-Tree + ~~~~~~~~~ InvocationName : Show-Tree CommandOrigin : Internal ScriptStackTrace : at <ScriptBlock>, <No file>: line 1 CommandName : Show-Tree TargetSite : Name : TryModuleAutoDiscovery DeclaringType : [System.Management.Automation.CommandDiscovery] MemberType : Method Module : System.Management.Automation.dll Message : The 'Show-Tree' command was found in the module 'Pscx', but the module could not be loaded due to the following error: [Could not load file or assembly 'System.Messaging, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The system cannot find the file specified. ] For more information, run 'Import-Module Pscx'. Data : System.Collections.ListDictionaryInternal InnerException : Type : System.Management.Automation.CmdletInvocationException ErrorRecord : Exception : Type : System.IO.FileNotFoundException Message : Could not load file or assembly 'System.Messaging, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The system cannot find the file specified. FileName : System.Messaging, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a TargetSite : Name : GetExportedTypes DeclaringType : [System.Reflection.RuntimeAssembly] MemberType : Method Module : System.Private.CoreLib.dll Source : System.Private.CoreLib HResult : -2147024894 StackTrace : at System.Reflection.RuntimeAssembly.GetExportedTypes(QCallAssembly assembly, ObjectHandleOnStack retTypes) at System.Reflection.RuntimeAssembly.GetExportedTypes() at System.Management.Automation.Runspaces.PSSnapInHelpers.GetAssemblyTypes(Assembly assembly, String name) at System.Management.Automation.Runspaces.PSSnapInHelpers.AnalyzeModuleAssemblyWithReflection(Assembly assembly, String name, PSSnapInInfo psSnapInInfo, PSModuleInfo moduleInfo, String helpFile, Dictionary`2& cmdlets, Dictionary`2& aliases, Dictionary`2& providers) at System.Management.Automation.Runspaces.PSSnapInHelpers.AnalyzePSSnapInAssembly(Assembly assembly, String name, PSSnapInInfo psSnapInInfo, PSModuleInfo moduleInfo, Dictionary`2& cmdlets, Dictionary`2& aliases, Dictionary`2& providers, String& helpFile) at System.Management.Automation.Runspaces.InitialSessionState.ImportCmdletsFromAssembly(Assembly assembly, PSModuleInfo module) at Microsoft.PowerShell.Commands.ModuleCmdletBase.LoadBinaryModule(PSModuleInfo parentModule, String moduleName, String fileName, Assembly assemblyToLoad, String moduleBase, SessionState ss, ImportModuleOptions options, ManifestProcessingFlags manifestProcessingFlags, String prefix, Boolean& found, String shortModuleName, Boolean disableFormatUpdates) at Microsoft.PowerShell.Commands.ModuleCmdletBase.LoadModule(PSModuleInfo parentModule, String fileName, String moduleBase, String prefix, SessionState ss, Object privateData, ImportModuleOptions& options, ManifestProcessingFlags manifestProcessingFlags, Boolean& found, Boolean& moduleFileFound) at Microsoft.PowerShell.Commands.ModuleCmdletBase.LoadModuleNamedInManifest(PSModuleInfo parentModule, ModuleSpecification moduleSpecification, String moduleBase, Boolean searchModulePath, String prefix, SessionState ss, ImportModuleOptions options, ManifestProcessingFlags manifestProcessingFlags, Object privateData, Boolean& found, String shortModuleName, Nullable`1 manifestLanguageMode) at Microsoft.PowerShell.Commands.ModuleCmdletBase.LoadModuleManifest(String moduleManifestPath, ExternalScriptInfo manifestScriptInfo, Hashtable data, Hashtable localizedData, ManifestProcessingFlags manifestProcessingFlags, Version minimumVersion, Version maximumVersion, Version requiredVersion, Nullable`1 requiredModuleGuid, ImportModuleOptions& options, Boolean& containedErrors) at Microsoft.PowerShell.Commands.ModuleCmdletBase.LoadModule(PSModuleInfo parentModule, String fileName, String moduleBase, String prefix, SessionState ss, Object privateData, ImportModuleOptions& options, ManifestProcessingFlags manifestProcessingFlags, Boolean& found, Boolean& moduleFileFound) at Microsoft.PowerShell.Commands.ImportModuleCommand.ImportModule_LocallyViaName(ImportModuleOptions importModuleOptions, String name) at Microsoft.PowerShell.Commands.ImportModuleCommand.ImportModule_LocallyViaName_WithTelemetry(ImportModuleOptions importModuleOptions, String name) at Microsoft.PowerShell.Commands.ImportModuleCommand.ProcessRecord() at System.Management.Automation.CommandProcessor.ProcessRecord() CategoryInfo : NotSpecified: (:) [Import-Module], FileNotFoundException FullyQualifiedErrorId : System.IO.FileNotFoundException,Microsoft.PowerShell.Commands.ImportModuleCommand InvocationInfo : MyCommand : Import-Module HistoryId : 22 InvocationName : Import-Module CommandOrigin : Internal ScriptStackTrace : at <ScriptBlock>, <No file>: line 1 PipelineIterationInfo : 0 1 TargetSite : Name : Invoke DeclaringType : [System.Management.Automation.Runspaces.PipelineBase] MemberType : Method Module : System.Management.Automation.dll Message : Could not load file or assembly 'System.Messaging, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The system cannot find the file specified. InnerException : Type : System.IO.FileNotFoundException Message : Could not load file or assembly 'System.Messaging, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The system cannot find the file specified. FileName : System.Messaging, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a TargetSite : Name : GetExportedTypes DeclaringType : [System.Reflection.RuntimeAssembly] MemberType : Method Module : System.Private.CoreLib.dll Source : System.Private.CoreLib HResult : -2147024894 StackTrace : at System.Reflection.RuntimeAssembly.GetExportedTypes(QCallAssembly assembly, ObjectHandleOnStack retTypes) at System.Reflection.RuntimeAssembly.GetExportedTypes() at System.Management.Automation.Runspaces.PSSnapInHelpers.GetAssemblyTypes(Assembly assembly, String name) at System.Management.Automation.Runspaces.PSSnapInHelpers.AnalyzeModuleAssemblyWithReflection(Assembly assembly, String name, PSSnapInInfo psSnapInInfo, PSModuleInfo moduleInfo, String helpFile, Dictionary`2& cmdlets, Dictionary`2& aliases, Dictionary`2& providers) at System.Management.Automation.Runspaces.PSSnapInHelpers.AnalyzePSSnapInAssembly(Assembly assembly, String name, PSSnapInInfo psSnapInInfo, PSModuleInfo moduleInfo, Dictionary`2& cmdlets, Dictionary`2& aliases, Dictionary`2& providers, String& helpFile) at System.Management.Automation.Runspaces.InitialSessionState.ImportCmdletsFromAssembly(Assembly assembly, PSModuleInfo module) at Microsoft.PowerShell.Commands.ModuleCmdletBase.LoadBinaryModule(PSModuleInfo parentModule, String moduleName, String fileName, Assembly assemblyToLoad, String moduleBase, SessionState ss, ImportModuleOptions options, ManifestProcessingFlags manifestProcessingFlags, String prefix, Boolean& found, String shortModuleName, Boolean disableFormatUpdates) at Microsoft.PowerShell.Commands.ModuleCmdletBase.LoadModule(PSModuleInfo parentModule, String fileName, String moduleBase, String prefix, SessionState ss, Object privateData, ImportModuleOptions& options, ManifestProcessingFlags manifestProcessingFlags, Boolean& found, Boolean& moduleFileFound) at Microsoft.PowerShell.Commands.ModuleCmdletBase.LoadModuleNamedInManifest(PSModuleInfo parentModule, ModuleSpecification moduleSpecification, String moduleBase, Boolean searchModulePath, String prefix, SessionState ss, ImportModuleOptions options, ManifestProcessingFlags manifestProcessingFlags, Object privateData, Boolean& found, String shortModuleName, Nullable`1 manifestLanguageMode) at Microsoft.PowerShell.Commands.ModuleCmdletBase.LoadModuleManifest(String moduleManifestPath, ExternalScriptInfo manifestScriptInfo, Hashtable data, Hashtable localizedData, ManifestProcessingFlags manifestProcessingFlags, Version minimumVersion, Version maximumVersion, Version requiredVersion, Nullable`1 requiredModuleGuid, ImportModuleOptions& options, Boolean& containedErrors) at Microsoft.PowerShell.Commands.ModuleCmdletBase.LoadModule(PSModuleInfo parentModule, String fileName, String moduleBase, String prefix, SessionState ss, Object privateData, ImportModuleOptions& options, ManifestProcessingFlags manifestProcessingFlags, Boolean& found, Boolean& moduleFileFound) at Microsoft.PowerShell.Commands.ImportModuleCommand.ImportModule_LocallyViaName(ImportModuleOptions importModuleOptions, String name) at Microsoft.PowerShell.Commands.ImportModuleCommand.ImportModule_LocallyViaName_WithTelemetry(ImportModuleOptions importModuleOptions, String name) at Microsoft.PowerShell.Commands.ImportModuleCommand.ProcessRecord() at System.Management.Automation.CommandProcessor.ProcessRecord() Source : System.Management.Automation HResult : -2146233087 StackTrace : at System.Management.Automation.Runspaces.PipelineBase.Invoke(IEnumerable input) at System.Management.Automation.PowerShell.Worker.ConstructPipelineAndDoWork(Runspace rs, Boolean performSyncInvoke) at System.Management.Automation.PowerShell.CoreInvokeHelper[TInput,TOutput](PSDataCollection`1 input, PSDataCollection`1 output, PSInvocationSettings settings) at System.Management.Automation.PowerShell.CoreInvoke[TInput,TOutput](PSDataCollection`1 input, PSDataCollection`1 output, PSInvocationSettings settings) at System.Management.Automation.PowerShell.Invoke[T]() at System.Management.Automation.CommandDiscovery.AutoloadSpecifiedModule(String moduleName, ExecutionContext context, SessionStateEntryVisibility visibility, Exception& exception) Source : System.Management.Automation HResult : -2146233087 StackTrace : at System.Management.Automation.CommandDiscovery.TryModuleAutoDiscovery(String commandName, ExecutionContext context, String originalCommandName, CommandOrigin commandOrigin, SearchResolutionOptions searchResolutionOptions, CommandTypes commandTypes, Exception& lastError) at System.Management.Automation.CommandDiscovery.LookupCommandInfo(String commandName, CommandTypes commandTypes, SearchResolutionOptions searchResolutionOptions, CommandOrigin commandOrigin, ExecutionContext context) at System.Management.Automation.PipelineOps.AddCommand(PipelineProcessor pipe, CommandParameterInternal[] commandElements, CommandBaseAst commandBaseAst, CommandRedirection[] redirections, ExecutionContext context) at System.Management.Automation.PipelineOps.InvokePipeline(Object input, Boolean ignoreInput, CommandParameterInternal[][] pipeElements, CommandBaseAst[] pipeElementAsts, CommandRedirection[][] commandRedirections, FunctionContext funcContext) at System.Management.Automation.Interpreter.ActionCallInstruction`6.Run(InterpretedFrame frame) at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame) TargetObject : Show-Tree CategoryInfo : ObjectNotFound: (Show-Tree:String) [], CommandNotFoundException FullyQualifiedErrorId : CouldNotAutoloadMatchingModule InvocationInfo : ScriptLineNumber : 1 OffsetInLine : 1 HistoryId : 22 Line : Show-Tree Statement : Show-Tree PositionMessage : At line:1 char:1 + Show-Tree + ~~~~~~~~~ InvocationName : Show-Tree CommandOrigin : Internal ScriptStackTrace : at <ScriptBlock>, <No file>: line 1
Specifically, the undermentioned section:
FileName : System.Messaging, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a TargetSite : Name : GetExportedTypes DeclaringType : [System.Reflection.RuntimeAssembly] MemberType : Method Module : System.Private.CoreLib.dll
Summarily, how can I replicate TargetSite printing its properties with their values, with indentation to visualise the depedency? I ask because I've been utterly unable to locate online and alone how to force a PSObject with nested properties to render them in the CLI as a tree like Get-Error is evidently able to.
Meta
Originally asked at Reddit, but it was immediately redacted by AutoMod.
I request this because I utilize hash-tables extensively, and visualising one's data in its entirety without performing $HashTable | ConvertTo-YAML isn't possible.
Proposed technical implementation details (optional)
As its (currently sole) response states:
There's no single/automatic way of doing this unfortunately. The
Get-Errorcmdlet works by inserting theSystem.Management.Automation.ErrorRecord#PSExtendedErrortype name to the error object and PowerShell has a custom formatter set for that type name to do the manual string building to create this format [in the undermentioned]:Lines 824 to 1030 in 623c451
private static IEnumerable<FormatViewDefinition> ViewsOf_System_Management_Automation_GetError() { yield return new FormatViewDefinition("GetErrorInstance", CustomControl.Create() .GroupByProperty("PSErrorIndex", label: "ErrorIndex") .StartEntry() .AddScriptBlockExpressionBinding(@" Set-StrictMode -Off $maxDepth = 10 $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 -is [System.Collections.IDictionary]) { $isFirstElement = $true foreach ($key in ($prop.Value.Keys | Sort-Object)) { 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) $valueIndent = ' ' * ($newIndent + 2) if ($value -is [Type]) { # Just show the typename instead of it as an object $null = $output.Append(""${prefix}${valueIndent}[$($value.ToString())]"") } elseif ($value -is [string] -or $value.GetType().IsPrimitive) { $null = $output.Append(""${prefix}${valueIndent}${value}"") } else { if (!$isFirstElement) { $null = $output.Append($newline) } $null = $output.Append((Show-ErrorRecord $value $newIndent ($depth + 1))) } $isFirstElement = $false } } } elseif ($prop.Value -is [Type]) { # Just show the typename instead of it as an object $null = $output.Append(""[$($prop.Value.ToString())]"") } # 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 $_ ") .EndEntry() .EndControl()); } You can certainly copy/modify the code for your own needs but it's not a simple function you can call here and something very specific to the
Get-Erroroutput. You could look at using a YAML library to try and achieve a similar setup but it won't strictly be the same as theGet-Errorformat which is YAML like but not actually YAML.
Activity
[-]Provide a standardised mechanism to render nested attributes expanded (in a dependency-preserving manner):[/-][+]Provide a standardised mechanism to render nested attributes expanded with dependency visualisation:[/+][-]Provide a standardised mechanism to render nested attributes expanded with dependency visualisation:[/-][+]Provide a standardised mechanism to render nested attributes expanded with dependency visualisation.[/+][-]Provide a standardised mechanism to render nested attributes expanded with dependency visualisation.[/-][+]Provide a standardised mechanism to render nested attributes in a PSObject expanded with dependency visualisation.[/+]237dmitry commentedon Oct 21, 2024
In my opinion you can examine ErrorRecord object by its properties:
beedell-roke commentedon Oct 22, 2024
237dmitry, I am referring to a situation in which you want to see the contents of
(Get-Error).Exception:Details
…and the children of its properties (in this case,
TargetSite):…inline, like the undermentioned (custom) example:
Details
beedell-roke commentedon Mar 17, 2025
This also appears to be exposed via
Get-Command -Name "${.exe}":