Last Update: Sep 04, 2024 | Published: Feb 02, 2015
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.
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 *
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
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.
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.
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
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
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.
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.
Because the key name includes the period, I have to quote it. Or I could use syntax like this:
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:
Now it is much easier to access specific entries.
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}}
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
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.