Prof. Powershell

Slice and Dice with Hash Tables in PowerShell

The Group-Object cmdlet can offer up lots of information if you extract that info through hash tables. Here's how.

One of the greatest benefits of Windows PowerShell is how it lets us slice and dice information with minimal effort. Often we like to look at data in buckets or groups. I think it has been awhile since we looked at the Group-Object cmdlet so I thought we'd discuss what I think is a little-used approach.

In case you haven't used it, Group-Object takes a collection of objects and organizes them into groups based on an object property:

PS C:\> get-service | group status

Count Name    Group
----- ----    -----
  101 Stopped {System.ServiceProcess.ServiceController, Sy...
   93 Running {System.ServiceProcess.ServiceController, Sy...

The cmdlet writes a new object to the pipeline, a GroupInfo object. The Group property is the collection of grouped objects. But there is another way to use Group-Object. We can create an associative array, or hash table:

PS C:\> $wmisvc=Get-WmiObject win32_service | group state -AsHashTable

The variable $wmisvc is a hash table:

PS C:\> $wmisvc

Name    Value
----    -----
Running {\\SERENITY\root\cimv2:Win32_Service.Name="Au...
Stopped {\\SERENITY\root\cimv2:Win32_Service.Name="Ae...

The values for each key are the Win32_Service objects. This can be handy because we can treat a hash table like an object. Each key becomes a property:

PS C:\> $wmisvc.running.count
94
PS C:\> $wmisvc.running[0]

ExitCode  : 0
Name      : AudioEndpointBuilder
ProcessId : 1140
StartMode : Auto
State     : Running
Status    : OK

As long as the property you are grouping on is a simple string this should work just fine. However, you may run into a few wrinkles. Look at this example:

PS C:\> $h=get-service | group status -AsHashTable
PS C:\> $h

Name    Value
----    -----
Running {System.ServiceProcess.ServiceController, Sys...
Stopped {System.ServiceProcess.ServiceController, Sys...
PS C:\> $h.running.count

But nothing will happen. That's because the key is really not a string:

PS C:\> $h.keys | get-member

TypeName: System.ServiceProcess.ServiceControllerStatus

Name        MemberType Definition
----        ---------- ----------
CompareTo   Method     int CompareTo(System.Object target)
Equals      Method     bool Equals(System.Object obj)
GetHashCode Method     int GetHashCode()
GetType     Method     type GetType()
GetTypeCode Method     System.TypeCode GetTypeCode()
ToString    Method     string ToString(), string ToString(string format, Sys...
value__     Property   System.Int32 value__ {get;set;}

The status property is actually an enumeration:

PS C:\> $h.keys | select value__

                                     value__
                                     -------
                                           4
                                           1

The solution is to use an additional parameter with Group-Object when creating a hash table.

PS C:\> $h=get-service | group status -AsHashTable -AsString
PS C:\> $h.running.count
94
PS C:\> $h.running[0]

Status  Name               DisplayName
------  ----               -----------
Running AudioEndpointBu... Windows Audio Endpoint Builder

Using the -AsString parameter handles all the under the hood transconfiguration voodoo to make this work.

Let's conclude with one more example:

PS C:\> $scripts=dir c:\scripts | group extension -AsHashTable
PS C:\> $scripts.keys
.ps1
.txt
.wsf
.png
...

The hash table keys all contain a period which makes it awkward to retrieve specific values:

PS C:\> $scripts.'.ps1' | measure length -sum

Count    : 966
Average  :
Sum      : 3437064
Maximum  :
Minimum  :
Property : Length

The solution is to create a key that omits the period. The Group-Object Property parameter will also accept a script block or hash table:

PS C:\> $scripts=dir c:\scripts | Where {$_.Extension} | group {$_.Extension.SubString(1)} -AsHashTable -AsString

This command will run the Substring() method on each extension and return everything starting from position 1, which should be everything after the period. You'll run into errors if the extension doesn't exist so I add a Where clause to filter out those situations, which are most likely directories anyway. But now I have something easier to work with:

PS C:\> $scripts.ps1 | measure length -sum

Count    : 966
Average  :
Sum      : 3437064
Maximum  :
Minimum  :
Property : Length

I can slice and dice my hash table anyway I want. So the next time you are looking for an appetizing alternative, consider a grouped hash table.

About the Author

Jeffery Hicks is an IT veteran with over 25 years of experience, much of it spent as an IT infrastructure consultant specializing in Microsoft server technologies with an emphasis in automation and efficiency. He is a multi-year recipient of the Microsoft MVP Award in Windows PowerShell. He works today as an independent author, trainer and consultant. Jeff has written for numerous online sites and print publications, is a contributing editor at Petri.com, and a frequent speaker at technology conferences and user groups.

comments powered by Disqus
Most   Popular