Skip to content

Instantly share code, notes, and snippets.

@trackd
Last active February 5, 2026 20:31
Show Gist options
  • Select an option

  • Save trackd/ae282d75def6c17924ba08eed3668f09 to your computer and use it in GitHub Desktop.

Select an option

Save trackd/ae282d75def6c17924ba08eed3668f09 to your computer and use it in GitHub Desktop.
Show-Object

this is just me playing around with [ClassExplorer.Internal._Format] class to make things pretty.
obviously there is a hard requirement for the module ClassExplorer

also note the Internal in the name, this could probably break at any time.

Show-Object

experimental thing.. :)

function Show-Object {
<#
.SYNOPSIS
Show all properties and methods of an object.
Sort of like a Get-Member but with nice formatting and will show the object.
Uses the module ClassExplorer to make things pretty.
.PARAMETER Name
The name of the properties or methods to show.
.PARAMETER Type
The type of members to show.
Default is 'Properties'.
.PARAMETER Force
Show all members, including hidden ones.
.PARAMETER Static
Show static members.
.PARAMETER ShowAllObjects
Show all objects, default is to show one per type.
.PARAMETER ConvertHashtable
Convert hashtable to PSCustomObject if specified.
makes it easier to work with to visualize the object.
.PARAMETER Count
The number of objects to show.
.PARAMETER InputObject
The object to show properties and methods for.
.EXAMPLE
Get-Process -id $PID | Show-Object
.EXAMPLE
Get-Item . | Show-Object -Type All
.LINK
https://github.com/SeeminglyScience/ClassExplorer
#>
[Alias('so')]
[OutputType('Show.Object')]
[CmdletBinding(DefaultParameterSetName = 'Default')]
param(
[Parameter(Position = 0)]
[SupportsWildcards()]
[Alias('n')]
[String[]] $Name,
[ValidateSet('Properties', 'Methods', 'All')]
[Alias('t')]
[String] $Type = 'Properties',
[Alias('f')]
[Switch] $Force,
[Alias('s')]
[Switch] $Static,
[Parameter(ParameterSetName = 'ShowAllObjects')]
[Switch] $ShowAllObjects,
[Switch] $ConvertHashtable,
[Parameter(ParameterSetName = 'count')]
[Alias('c')]
[ValidateRange(0, 1000)]
[int] $Count,
[Parameter(ValueFromPipeline)]
[PSObject] $InputObject
)
begin {
if (-Not (Get-Module ClassExplorer)) {
Import-Module ClassExplorer -Global -ErrorAction Stop
}
$splat = @{
Force = $true
}
if ($Type -eq 'All') {
$splat.MemberType = 'All' #'Properties', 'Methods', 'ScriptMethod'
}
else {
$splat.MemberType = $Type
}
if ($Name) {
$splat.Name = $Name
}
if ($Static) {
$splat.Static = $true
}
$typesSeen = @{}
}
process {
if (-Not $InputObject) {
# this will just return instead of throwing an error.
return
}
if ($PSCmdlet.ParameterSetName -eq 'count' -and $Count -le 0) {
# we are in the count parameter set, but we have no more objects to show.
return
}
$ParentType = $InputObject.GetType().FullName.split('`', 2)[0]
if ($PSCmdlet.ParameterSetName -eq 'Default') {
if ($typesSeen.Contains($ParentType)) {
# we are in the default parameter set, and we have already seen this type.
return
}
$typesSeen[$ParentType] = $true
}
$Copy = $InputObject.PSObject?.Copy()
$ParentProperty = [PSNoteProperty]::new('ParentType', $ParentType)
if ($ConvertHashtable -and
$ParentType -in 'System.Collections.Hashtable', 'System.Management.Automation.OrderedHashtable') {
# ConvertHashtable will Convert a hashtable to a PSCustomObject.
$Copy = [PSCustomObject]$Copy
}
if (-not $Copy) {
# something went wrong, we have no object to show.
return
}
if ($MyInvocation.ExpectingInput) {
$Members = $Copy | Get-Member @splat
}
else {
$Members = Get-Member -InputObject $Copy @splat
}
foreach ($Object in $Members) {
$item = $null
if ($Object.MemberType -match 'Method') {
if (-Not $Force -And $Object.Name -cmatch '^(get|set)_') {
# i don't care about these.. force will override
continue
}
$item = $Copy.PSObject.Methods[$Object.Name]
}
elseif ($Object.MemberType -match 'Property') {
$item = $Copy.PSObject.Properties[$Object.Name]
}
if (-Not $item) {
if (-Not $Static) {
continue
}
# use the Get-Member output if we cant find a suitable match.
$item = $Object
}
$item.PSObject.TypeNames.Insert(0, 'Show.Object')
$item.PSObject.Properties.Add($ParentProperty)
$item
}
if ($Count) {
$Count--
}
}
}
<Configuration>
<ViewDefinitions>
<View>
<Name>Show-Object</Name>
<ViewSelectedBy>
<TypeName>Show.Object</TypeName>
<TypeName>System.Management.Automation.PSNoteProperty</TypeName>
<TypeName>System.Management.Automation.PSCodeProperty</TypeName>
<TypeName>System.Management.Automation.PSScriptProperty</TypeName>
<TypeName>System.Management.Automation.PSAliasProperty</TypeName>
<TypeName>System.Management.Automation.PSProperty</TypeName>
<TypeName>System.Management.Automation.PSScriptMethod</TypeName>
<TypeName>System.Management.Automation.PSCodeMethod</TypeName>
</ViewSelectedBy>
<GroupBy>
<PropertyName>ParentType</PropertyName>
<CustomControl>
<CustomEntries>
<CustomEntry>
<CustomItem>
<Frame>
<FirstLineIndent>4</FirstLineIndent>
<CustomItem>
<ExpressionBinding>
<ScriptBlock>
return '{0}ParentType{2}: {1}' -f (
$PSStyle.Formatting.TableHeader,
[ClassExplorer.Internal._Format]::Type($_.ParentType),
$PSStyle.Reset
)
</ScriptBlock>
</ExpressionBinding>
</CustomItem>
</Frame>
</CustomItem>
</CustomEntry>
</CustomEntries>
</CustomControl>
</GroupBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Label>MT</Label>
<Width>4</Width>
</TableColumnHeader>
<TableColumnHeader>
<Label>TypeNameOfValue</Label>
<Width>22</Width>
</TableColumnHeader>
<TableColumnHeader>
<Label>Name</Label>
<Width>25</Width>
</TableColumnHeader>
<TableColumnHeader>
<Label>Value</Label>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<ScriptBlock>
[ClassExplorer.Internal._Format]::Operator(($_.MemberType -creplace '[a-z]'), 4)
</ScriptBlock>
</TableColumnItem>
<TableColumnItem>
<ScriptBlock>
if ($_.MemberType -in 'ScriptMethod','CodeMethod') {
return [ClassExplorer.Internal._Format]::Type($_.MemberType.ToString(),22)
}
if (($TypeName = $_.TypeNameOfValue -as [Type])) {
return [ClassExplorer.Internal._Format]::Type($TypeName, 22)
}
if (-Not [String]::IsNullOrEmpty($_.TypeNameOfValue)) {
return [ClassExplorer.Internal._Format]::Type($_.TypeNameOfValue.TrimStart('System.').split('`',2)[0], 22)
}
return [ClassExplorer.Internal._Format]::Type($_.TypeName, 22)
</ScriptBlock>
</TableColumnItem>
<TableColumnItem>
<ScriptBlock>
[ClassExplorer.Internal._Format]::Variable($_.Name, 25)
</ScriptBlock>
</TableColumnItem>
<TableColumnItem>
<ScriptBlock>
&amp; {
if ($_.MemberType -eq 'Method') {
# maybe this should just be moved to a separate PSTypeName, eh doesnt seem worth it.
$flags = [System.Reflection.BindingFlags]::NonPublic -bor [System.Reflection.BindingFlags]::Instance
$adapterDataField = $_.GetType().GetField('adapterData', $flags)
if ($null -eq $adapterDataField) {
return $_.OverloadDefinitions -join [System.Environment]::NewLine
}
$adapterData = $adapterDataField.GetValue($_)
if ($null -eq $adapterData) {
return $_.OverloadDefinitions -join [System.Environment]::NewLine
}
$methodInformationStructuresField = $adapterData.
GetType().
GetField('methodInformationStructures', $flags)
if ($null -eq $methodInformationStructuresField) {
return $_.OverloadDefinitions -join [System.Environment]::NewLine
}
$methodInformationStructures = $methodInformationStructuresField.GetValue($adapterData)
if ($null -eq $methodInformationStructures) {
return $_.OverloadDefinitions -join [System.Environment]::NewLine
}
$instanceField = $_.GetType().GetField('instance', $flags)
if ($null -eq $instanceField) {
return $_.OverloadDefinitions -join [System.Environment]::NewLine
}
$instance = $instanceField.GetValue($_)
$instanceType = $null
if ($null -ne $instance) {
$instanceType = $instance.GetType()
}
$methodField = $methodInformationStructures[0].GetType().GetField('method', $flags)
if ($null -eq $methodField) {
return $_.OverloadDefinitions -join [System.Environment]::NewLine
}
$reflectionInfo = foreach ($structure in $methodInformationStructures) {
$methodField.GetValue($structure)
}
$result = foreach ($info in $reflectionInfo) {
[ClassExplorer.Internal._Format]::Member($info)
}
return $result -join [System.Environment]::NewLine
}
if ($_.MemberType -in 'ScriptMethod','CodeMethod') {
$c = Get-PSReadLineOption
$r = $PSStyle.Reset
# Generate a method signature for ScriptMethod and CodeMethod
if ($_.MemberType -eq 'ScriptMethod' -and $null -ne $_.Script) {
$scriptBlock = $_.Script
# Try to extract parameter info from the ParamBlock
$paramNames = @()
if ($null -ne $scriptBlock.Ast.ParamBlock -and $scriptBlock.Ast.ParamBlock.Parameters.Count -gt 0) {
$paramNames = $scriptBlock.Ast.ParamBlock.Parameters | ForEach-Object { '{0}{1}{2}' -f $c.VariableColor, $_.Name.VariablePath.UserPath, $r }
}
# Build a signature with a generic return type (object) and show params or variadic args
if ($paramNames.Count -gt 0) {
$paramString = '({0})' -f ($paramNames -join ', ')
return '{0}{1};' -f $_.Name, $paramString
}
$usesArgs = $scriptBlock.Ast.EndBlock -and ($scriptBlock.Ast.EndBlock.Extent.Text -match '\$args')
if ($usesArgs) {
return '{0}({1}object {2}args{3})' -f $_.Name, $c.KeywordColor, $c.VariableColor, $r
}
return '{0}();' -f $_.Name
}
# Fallback to OverloadDefinitions
if ($null -ne $_.OverloadDefinitions -and -not [String]::IsNullOrEmpty($_.OverloadDefinitions)) {
return $_.OverloadDefinitions
}
# Last resort: show the method signature with unknown params
return '{0}()' -f $_.Name
}
if ($_.TypeNameOfValue -in 'System.Boolean','bool') {
return [ClassExplorer.Internal._Format]::FancyBool($_.Value)
}
if ($_.TypeNameOfValue -eq 'System.DateTime') {
# explicitly call ToString() because it formats datetime according to CurrentCulture settings.
return [ClassExplorer.Internal._Format]::Number($_.Value.ToString('yyyy-MM-dd HH:mm:ss'))
}
if ($_.TypeNameOfValue -in 'System.String','string','string[]') {
if ($_.MemberType -eq 'CodeProperty' -And $_.Name.EndsWith('String')) {
# this is probably PSStyle format property.
return $_.Value
}
if ($_.Value -eq [String]::Empty) {
return '{0}{1}IsEmpty{2}{3}' -f (
[char]0x3c, $PSStyle.Foreground.Red, $PSStyle.Reset, [char]0x3e
)
}
if ([String]::IsNullOrWhiteSpace($_.Value)) {
return '{0}{1}IsBlank{2}{3}' -f (
[char]0x3c, $PSStyle.Foreground.Red, $PSStyle.Reset, [char]0x3e
)
}
return [ClassExplorer.Internal._Format]::String($_.Value)
}
if ($_.Name -eq 'pstypenames') {
return [ClassExplorer.Internal._Format]::Type($_.Value.split('`', 2)[0], [Console]::BufferWidth - 4 - 22 - 25)
}
if ($null -ne $_.TypeName) {
return [ClassExplorer.Internal._Format]::String($_.Definition)
}
if ($null -eq $_.Value) {
return '{0}{1}IsNull{2}{3}' -f (
[char]0x3c, $PSStyle.Foreground.Red, $PSStyle.Reset, [char]0x3e
)
}
if (-Not $_.Value) {
return '{0}{1}NoValue{2}{3}' -f (
[char]0x3c, $PSStyle.Foreground.Red, $PSStyle.Reset, [char]0x3e
)
}
if ($_.Value -is [System.ValueType]) {
return [ClassExplorer.Internal._Format]::Number($_.Value)
}
if (($_.TypeNameOfValue -in 'System.Management.Automation.PSCustomObject', 'System.Management.Automation.PSObject') -or
($_.Name -in 'VersionInfo','FileVersionInfo')) {
$AvailableWidth = [Console]::BufferWidth - 4 - 22 - 25 - 2
$sb = [System.Text.StringBuilder]::new($AvailableWidth)
$null = $sb.Append('{')
$_.Value.psobject.properties | ForEach-Object {
if ($sb.Length -ge $AvailableWidth) {
return
}
# dont render null values, we're low on width here so we need to save space.
if ($_.Value) {
$null = $sb.AppendFormat('{0}={1}, ', $_.Name, $_.Value)
}
}
return $sb.Remove(($sb.Length - 2), 2).Append('}').ToString()
}
if ($_.Value -is [System.Array]) {
return $_.Value | Join-String -OutputPrefix '@(' -OutputSuffix ')' -Separator ', '
}
if ($_.Value -is [Hashtable]) {
$AvailableWidth = [Console]::BufferWidth - 4 - 22 - 25 - 2
$sb = [System.Text.StringBuilder]::new($AvailableWidth)
$null = $sb.Append('@{')
$_.Value.GetEnumerator() | ForEach-Object {
if ($sb.Length -ge $AvailableWidth) {
return
}
if ($_.Value) {
$null = $sb.AppendFormat('{0}={1}, ', $_.Key, $_.Value)
}
}
return $sb.Remove(($sb.Length - 2),2).Append('}').ToString()
}
# catch-all for the rest
return [ClassExplorer.Internal._Format]::String($_.Value, [Console]::BufferWidth - 4 - 22 - 25)
}
</ScriptBlock>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
</ViewDefinitions>
</Configuration>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment