Skip to content

Instantly share code, notes, and snippets.

@trackd
Last active October 28, 2024 22:56
Show Gist options
  • Save trackd/ae282d75def6c17924ba08eed3668f09 to your computer and use it in GitHub Desktop.
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 InputObject
The object to show properties and methods for.
.PARAMETER Type
The type of members to show.
Default is 'Properties'.
.PARAMETER Force
Show all members, including hidden ones.
.PARAMETER Static
Show static members.
.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()]
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(ValueFromPipeline)]
[PSObject] $InputObject
)
begin {
if (-Not (Get-Module ClassExplorer)) {
Import-Module ClassExplorer -Global -ErrorAction Stop
}
$splat = @{
Force = $true
}
if ($Type -eq 'All') {
$splat.MemberType = 'Properties', 'Methods'
}
else {
$splat.MemberType = $Type
}
if ($Name) {
$splat.Name = $Name
}
if ($Static) {
$splat.Static = $true
}
}
process {
if (-Not $InputObject) {
# this will just return instead of throwing an error.
return
}
# not sure if we really need to copy, but better to be safe.
$Copy = $InputObject.PSObject.Copy()
$ParentType = $Copy.GetType().FullName.split('`', 2)[0]
$ParentProperty = [PSNoteProperty]::new('ParentType', $ParentType)
if ($Force -And $ParentType -eq 'System.Collections.Hashtable') {
# Force will also Convert a hashtable to a PSCustomObject.
$Copy = [PSCustomObject]$Copy
}
if ($MyInvocation.ExpectingInput) {
$Members = $Copy | Get-Member @splat
}
else {
$Members = Get-Member -InputObject $Copy @splat
}
foreach ($Object in $Members) {
if ($Object.MemberType -eq '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]
}
else {
$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
}
}
}
<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>
</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 (($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 ($_.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())
}
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