Grouping PowerShell Objects with Group-Object

When working with results from a PowerShell expression, you may find it helpful to group objects by a common property. There are a few ways you can group objects depending on what you want to accomplish. Sometimes you need to work with each set of like objects, and sometimes you want to simply view the results. Let me share a few PowerShell examples and techniques to help you get the most out of group PowerShell results.

The most important thing to know about grouping is that you are working with objects and the grouping is almost always based on some common property. Thus, there is an assumption that you are grouping like objects. Sure, you could group different types of objects, but this is going to be an awkward process. The grouping property technically doesn’t have to have a value. You can use any property that you see with Get-Member. As I’ll demonstrate, you can even group on a dynamic or custom property of your own design. The cmdlet we’ll start with is Group-Object.

Using PowerShell’s Group-Object Cmdlet

Let’s start with an example of something that everyone can run, and something you might even find practical. All event log entries have a source property. How did I know? I asked PowerShell.

get-eventlog system -newest 1 | select *

Obtaining the source property with the get-eventlog cmdlet in PowerShell. (Image Credit: Jeff Hicks)
Obtaining the source property with the get-eventlog cmdlet in PowerShell. (Image Credit: Jeff Hicks)

Once I know the property name, I can group as many objects as I want on that property.

get-eventlog system | group-object -Property source

Grouping objects on the property in PowerShell. (Image Credit: Jeff Hicks)
Grouping objects on the property in PowerShell. (Image Credit: Jeff Hicks)

I hope it is apparent that when you use Group-Object, PowerShell writes a different object to the pipeline. I no longer end up with an event log entry object. Use the Get-Member cmdlet to see for yourself.

get-eventlog system -newest 10 | group-object -Property source | get-member

I only selected a few entries to speed things up.

Using Get-Member in Windows PowerShell. (Image Credit: Jeff Hicks)
Using Get-Member in Windows PowerShell. (Image Credit: Jeff Hicks)

Because this object has different properties, I can modify my original command.

get-eventlog system | group-object -Property source -NoElement | Sort Count -Descending | Select -first 10

I don’t care about the individual entries, so I’m including the –NoElement parameter with Group-Object, which leaves the Name and Count properties.

Using the NoElement property with Group-Object in Windows PowerShell. (Image Credit: Jeff Hicks)
Using the NoElement property with Group-Object in Windows PowerShell. (Image Credit: Jeff Hicks)

Some of those source names are bit long, so I might want to format the output.

get-eventlog system | group-object -Property source -NoElement | Sort Count -Descending | Select -first 10 | format-table -autosize

Formatting the output to be more readable. (Image Credit: Jeff Hicks)
Formatting the output to be more readable. (Image Credit: Jeff Hicks)

But what if I want to work with the grouped results? You have a few options. To demonstrate, I’m going to get all of the files in my scripts folder and group them on their file extension.

$g = dir c:\scripts -Recurse -File | group Extension

Sorting by extension in Windows PowerShell. (Image Credit: Jeff Hicks)
Sorting by extension in Windows PowerShell. (Image Credit: Jeff Hicks)

The Group property is the collection of files organized by file extension. I can process these files like this:

$g | foreach {[pscustomobject]@{Type=$_.Name;Count=$_.Count;Average = ($_.group | measure-object Length -Average).average}} | sort Average -descending | out-Gridview -title Averages

Using ForEach-Object, I’m creating a custom object with a property called Type, which will use the Name property of each group object, the Count property, and a custom property that takes the group of files and measures their average size.

Measuring a group of files' average size. (Image Credit: Jeff Hicks)
Measuring a group of files’ average size. (Image Credit: Jeff Hicks)


But what if you want to access a specific entry?
Suppose I want to analyze the PowerShell module files. I could use Where-Object or the new Where method.

$g.where({$_.name -match "psm1"})

I think a better approach is to create a hashtable.

$h = dir c:\scripts -Recurse -File | group Extension -AsHashTable

With a hashtable, each name becomes a key. You can treat the key like a property name and the value will be the collection of grouped objects. But there is a slight hiccup with what I’ve just created. Look at what I have to do to access one of my new properties.

It becomes more difficult to access my properties. (Image Credit: Jeff Hicks)
It becomes more difficult to access my properties. (Image Credit: Jeff Hicks)

Because the key name includes the period, I have to quote it. Or I could use syntax like this:
An alternative method of accessing properties. (Image Credit: Jeff Hicks)
An alternative method of accessing properties. (Image Credit: Jeff Hicks)

A better solution would be to get rid of the period altogether. With Group-Object you can also specify a custom property, so all I need to do is create a custom property that uses the file extension but without the period.

$j = dir c:\scripts -Recurse -File | where {$_.extension} | group {$_.Extension.substring(1)} –AsHashTable -AsString

I had to filter out files with no extension to avoid errors creating a hashtable with null keys. Or I could have used PowerShell to insert something for files with no extensions. I can insert as much code as I need into the scriptblock, using $_ to reference each object in the pipeline.
Here’s a big warning based on my experience: If you are using a custom property, then you should also include the AsString parameter, which will force PowerShell to turn each key into a string.
Here’s what I end up with:
112814 1858 PowerShellG11
Now it is much easier to access specific entries.

Now we have an easier way to access entries. (Image Credit: Jeff Hicks)
Now we have an easier way to access entries. (Image Credit: Jeff Hicks)

It is also easier to process all of the data in the hashtable.

$data = $j.GetEnumerator() | foreach {[pscustomobject]@{Type=$_.key;Count=$_.value.count;Size=($_.Value | measure length -sum).sum}}

Processing data in the hashtable. (Image Credit: Jeff Hicks)
Processing data in the hashtable. (Image Credit: Jeff Hicks)

I use Group-Object all the time to slice and dice information gathered from PowerShell. I think it is one of the most useful core cmdlet. Notice that the cmdlet name is Group-Object.

PowerShell doesn’t care what type of object it is, as long as you can specify or define a value to group on. Here’s an example grouping Active Directory user accounts on their department property, assuming one is defined.

Get-aduser –filter "department –like '*'" –properties Department,Title | Group Department

Grouping Active Directory objects on the department property. (Image Credit: Jeff Hicks)
Grouping Active Directory objects on the department property. (Image Credit: Jeff Hicks)

Each Group will include the related user account, including the Department and Title properties because I asked for them.
In this article, I showed you how to use Group-Object to gain insights and perspective based on information gathered with PowerShell commands. But perhaps your needs fall more in the category of reporting. I’ll be back in a future article to discuss that topic.