How do you list all the objects and values within a PowerShell object, investigate an object or explore its structure? There must be a simple way. I used to use ConvertTo-JSON. This is fine up to a point but what if you just wish to search for strings or look for objects with a cartain name or value? What if you need their path so you can reference them? It isn’t plain-sailing. In the ISE, the intellisense will help you a lot but I want more and I want to do it in script rather than the IDE
Let’s start with a silly example just to get in the mood. Imagine we have a hashtable that tells us our servers and users.
$ServersAndUsers = @{'Shem' = @{ 'version' = '2017'; 'Location' = 'Server room'; 'Users'=@('Fred','Jane','Mo') }; 'Ham' = @{ 'version' = '2019'; 'Location' = 'Floor two rack'; 'Downtime'=$null 'Users'=@('Fred','Jane',@{'TheDevopsTeam'=@('Joe','Tracy','Arthur')},'Phil','Tony') }; 'Japeth' = @{ 'version' = '2008'; 'Location' = 'basement rack'; 'Users'=@('Karen','Wyonna','Lilibet') } }
If we just pipe it to Format-Table we get this, which is a good start, but those values are more tantalising than helpful
Name Value ---- ----- Ham {Users, Downtime, version, Location} Japeth {Users, version, Location} Shem {Users, version, Location}
Getting the names of the base members isn’t that helpful. After all, we can also get the names of the servers through Get-Member
[PSCustomObject]$ServersAndUsers | Get-Member -MemberType NoteProperty|Select Name
PS C:\Users\Phil> [PSCustomObject]$ServersAndUsers | Get-Member -MemberType NoteProperty|Select Name Name ---- Ham Japeth Shem
… Or, knowing it is a hashtable, use …
$ServersAndUsers.keys
PS C:\Users\Phil> $ServersAndUsers.keys Ham Japeth Shem
If we can assume that it is a hashtable, we can get name and value as we did with Format-Table
$ServersAndUsers | Foreach-Object -ov obj{ $_ } | Foreach{ $_.Keys } | Foreach{ [pscustomobject]@{ 'Path' = $_; 'Value' = $obj.($_) } }
Path Value ---- ----- Ham {Users, Downtime, version, Location} Japeth {Users, version, Location} Shem {Users, version, Location}
Well. That’s progress of a sort in that we have something we can work on, but of course, not every object will be a hashtable. but we can elaborate it to list more types of object by turning a hashtable into a pscustomobject (Beware: the order of properties isn’t preserved). We’ll output a PSCustomObject as well so that we can output the results or do filters on them.
[pscustomObject]$ServersAndUsers | Foreach-Object -ov obj { $_ } | gm -MemberType NoteProperty | Foreach{ [pscustomobject]@{ 'Path' = "$.$($_.Name)"; 'Value' = $obj.($_.Name) } }
We changed that hashtable into a PSCustomPbject so we could iterate through the names using the Noteproperties. We get those noteproperties via the Get-Member (gm) cmdlet. We added a $ Reference to represent whatever name you used for the variable that referred to your object.
the problem here is that this trick only works for a pscustomObject or something we can change into it, such as a hashtable or ordered dictionary. For other objects, we use the property members rather than the NoteProperty members.
You can see that we need recursion because all the values are themselves objects of some description. Most data is more than a simple hashtable or pscustomObject.
function Display-Object ($TheObject, $Parent = '$') { $MemberType = 'Property' #assume this for the time being $ObjType = $TheObject.GetType().Name; if ($ObjType -in 'Hashtable', 'OrderedDictionary') { $TheObject = [pscustomObject]$TheObject; $ObjType = 'PSCustomObject'; } if ($ObjType -eq 'PSCustomObject') { $MemberType = 'NoteProperty' } $TheObject | gm -MemberType $MemberType | Foreach{ Try { $child = $TheObject.($_.Name); } Catch { $Child = $null } # avoid crashing on write-only objects if ($child -eq $null -or #is the current child a value or a null? $child.GetType().BaseType.Name -eq 'ValueType' -or $child.GetType().Name -in @('String', 'Object[]')) {#output the value of this as a ps object [pscustomobject]@{ 'Path' = "$Parent.$($_.Name)"; 'Value' = $Child; } } else #not a value but an object of some sort { Display-Object -TheObject $child -Parent "$Parent.$($_.Name)" } } }
Display-Object $ServersAndUsers
Path Value ---- ----- $.Ham.Downtime $.Ham.Location Floor two rack $.Ham.Users {Fred, Jane, System.Collections.Hashtable, Phil...} $.Ham.version 2019 $.Japeth.Location basement rack $.Japeth.Users {Karen, Wyonna, Lilibet} $.Japeth.version 2008 $.Shem.Location Server room $.Shem.Users {Fred, Jane, Mo} $.Shem.version 2017
The problem here is that it doesn’t yet handle arrays that well. We also need to allow the function to avoid a list of one or more names and allow it to work with a range of objects such as XML objects. It is also wise to copy ConvertTo-JSON in the way that it specifies the allowable depth. As a final touch, we allow it to keep its cool when presented with write-only values.
<# .SYNOPSIS Displays an object's values and the 'dot' paths to them .DESCRIPTION A detailed description of the Display-Object function. .PARAMETER TheObject The object that you wish to display .PARAMETER depth the depth of recursion (keep it low!) .PARAMETER Avoid an array of names of pbjects or arrays you wish to avoid. .PARAMETER Parent For internal use, but you can specify the name of the variable .PARAMETER CurrentDepth For internal use .NOTES Additional information about the function. #> function Display-Object { [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] $TheObject, [int]$depth = 5, [Object[]]$Avoid = @('#comment'), [string]$Parent = '$', [int]$CurrentDepth = 0 ) if ($CurrentDepth -ge $Depth) { return; } #prevent runaway recursion $ObjectTypeName = $TheObject.GetType().Name #find out what type it is if ($ObjectTypeName -in 'HashTable', 'OrderedDictionary') { #If you can, force it to be a PSCustomObject $TheObject = [pscustomObject]$TheObject; $ObjectTypeName = 'PSCustomObject' } if ($ObjectTypeName -ne 'Object[]') #not a system array. { # figure out where you get the names from if ($ObjectTypeName -in @('PSCustomObject')) # Name-Value pair properties created by Powershell { $MemberType = 'NoteProperty' } else { $MemberType = 'Property' } #now go through the names $TheObject | gm -MemberType $MemberType | where { $_.Name -notin $Avoid } | Foreach{ Try { $child = $TheObject.($_.Name); } Catch { $Child = $null } # avoid crashing on write-only objects if ($child -eq $null -or #is the current child a value or a null? $child.GetType().BaseType.Name -eq 'ValueType' -or $child.GetType().Name -eq 'String') { [pscustomobject]@{ 'Path' = "$Parent.$($_.Name)"; 'Value' = $Child; } } else #not a value but an object of some sort { Display-Object -TheObject $child -depth $Depth -Avoid $Avoid -Parent "$Parent.$($_.Name)" ` -CurrentDepth ($currentDepth + 1) } } } else #it is an array { 0..($TheObject.Count - 1) | Foreach{ $child = $TheObject[$_]; if (($child -eq $null) -or #is the current child a value or a null? ($child.GetType().BaseType.Name -eq 'ValueType') -or ($child.GetType().Name -eq 'String')) #if so display it { [pscustomobject]@{ 'Path' = "$Parent[$_]"; 'Value' = "$($child)"; } } else #not a value but an object of some sort so do a recursive call { Display-Object -TheObject $child -depth $Depth -Avoid $Avoid -parent "$Parent[$_]" ` -CurrentDepth ($currentDepth + 1) } } } }
Path Value ---- ----- $.Ham.Downtime $.Ham.Location Floor two rack $.Ham.Users[0] Fred $.Ham.Users[1] Jane $.Ham.Users[2].TheDevopsTeam[0] Joe $.Ham.Users[2].TheDevopsTeam[1] Tracy $.Ham.Users[2].TheDevopsTeam[2] Arthur $.Ham.Users[3] Phil $.Ham.Users[4] Tony $.Ham.version 2019 $.Japeth.Location basement rack $.Japeth.Users[0] Karen $.Japeth.Users[1] Wyonna $.Japeth.Users[2] Lilibet $.Japeth.version 2008 $.Shem.Location Server room $.Shem.Users[0] Fred $.Shem.Users[1] Jane $.Shem.Users[2] Mo $.Shem.version 2017
Naturally, one can tinker with the values you return. Some people will want a ‘name/index’ column. I’ve kept it simple. I like to know the path because that tells me the structure and it provides the correct dot syntax to get the value from the object.
$ServersAndUsers.Shem.Location
Path Value ---- ----- $.Ham.Users[4] Tony
You just substitute the name of the variable representing the objects for the $ symbol in the path.
You can filter what comes back. What server does Tony use?
Display-Object $ServersAndUsers | where {$_.Value -like '*Tony*' }
Path Value ---- ----- $.Ham.Users[4] Tony
What are the server locations?
Display-Object $ServersAndUsers | where {$_.Path -like '*location*' }
Path Value ---- ----- $.Ham.Location Floor two rack $.Japeth.Location basement rack $.Shem.Location Server room
Let’s try a text file..
@' Here lies John Bunn who was killed by a gun his name wasn't bun but Wood 'Wood' wouldn't rhyme with gun but 'Bunn' would Anon '@>"$env:Temp\Secondpoem.txt" Display-Object (Get-Content "$env:Temp\Secondpoem.txt")
Path Value ---- ----- $[0] Here lies John Bunn $[1] who was killed by a gun $[2] his name wasn't bun but Wood $[3] 'Wood' wouldn't rhyme with gun but 'Bunn' would $[4] $[5] Anon
We can display other, meatier, objects such as the PowerShell process
Display-Object (get-process pwsh) -depth 2
(too much to display in a Blog!)
Once you can ‘walk’ through an object and search them some possibilities open up. You can slice and dice them; You can convert objects to markup or find out if they’ve changed and how. If you are in a DevOps Windows environment, you’re likely to have all sorts of objects that are delivered to you by cmdlets that monitor the servers. It is useful to be able to investigate them and quickly learn how to pull out just the data you need. Hopefully, I’ll be able to show some of these in this series of Blog posts.
This function saved me a great deal of time when trying to get the result of a regex match into a rational format, so I have a certain affection for it.
I’ve added the source to a collection of PowerShell utilities on my github repositories.
The post Display-Object: a PowerShell utility Cmdlet appeared first on Simple Talk.
from Simple Talk https://ift.tt/2SSxPbL
via
No comments:
Post a Comment