PowerShell Group-Object: Group and Count Pipeline Data

PowerShell Group-Object: Group and Count Pipeline Data

PowerShell Tips Editor 4 min read
PowerShell Group-Object: Group and Count Pipeline Data

Which error message appears most frequently in the application log? How many processes are running per user? How many files exist in each folder? All these questions follow the same pattern — group items by a property and count each group — and PowerShell Group-Object answers them in a single pipeline line. This post covers every useful grouping pattern from basic counts to accessing group members to building top-N frequency reports.

Basic Grouping by Property

Group-Object groups pipeline objects by one or more property values. The result is a collection of groups, each with a Name (the group key), a Count, and a Group (the collection of matching objects).

# Group running services by status
Get-Service | Group-Object Status
Count  Name     Group
-----  ----     -----
58     Running  {AdobeARMservice, AJRouter, ALG, AppIDSvc...}
142    Stopped  {AeLookupSvc, AppMgmt, AppReadiness...}
# Group processes by parent process ID concept — group by first letter of name
Get-Process | Group-Object { $_.Name.Substring(0, 1).ToUpper() } |
    Sort-Object Name | Format-Table Name, Count

Count Group Size

The Count property on each group object is what you typically want first. Combine with Sort-Object to rank groups by frequency.

# Frequency analysis of event log sources
Get-WinEvent -FilterHashtable @{ LogName = "Application"; StartTime = (Get-Date).AddDays(-1) } |
    Group-Object ProviderName |
    Sort-Object Count -Descending |
    Select-Object -First 10 |
    Format-Table Name, Count
Name                          Count
----                          -----
MsiInstaller                  127
MSSQLSERVER                   84
VSS                           31
Microsoft-Windows-WMI         18

Use -NoElement for Summary Only

By default, Group-Object stores all the original objects in the Group property — which consumes memory for large datasets. Use -NoElement to return only the name and count, skipping the object storage when you only need frequency data.

# Efficient frequency count without storing objects
Get-Content -Path "C:\Logs\app.log" |
    Where-Object { $_ -match "ERROR" } |
    Group-Object -NoElement |
    Sort-Object Count -Descending |
    Select-Object -First 20

Multiple Property Grouping

Group by multiple properties simultaneously by passing an array to -Property. The group name becomes a comma-separated combination of the property values.

# Group processes by company AND status concept
Get-Process | Group-Object { $_.Company ?? "Unknown" } |
    Sort-Object Count -Descending |
    Select-Object -First 5 |
    Format-Table Name, Count

# Group files by extension and creation year
Get-ChildItem -Path "C:\Logs" -File |
    Group-Object Extension,
        @{ E={ $_.CreationTime.Year }; L="Year" } |
    Sort-Object Name |
    Format-Table Name, Count

Access Group Members

Each group’s Group property contains the original objects. Iterate through groups and access members to perform per-group calculations or extract specific items.

# Find the largest file in each extension group
Get-ChildItem -Path "C:\Logs" -File |
    Group-Object Extension |
    ForEach-Object {
        $largest = $_.Group | Sort-Object Length -Descending | Select-Object -First 1
        [PSCustomObject]@{
            Extension = $_.Name
            Count     = $_.Count
            LargestFile = $largest.Name
            LargestMB   = [math]::Round($largest.Length / 1MB, 1)
        }
    } |
    Sort-Object LargestMB -Descending |
    Format-Table -AutoSize

Top N Groups with Sort-Object

Building a top-N report — most common errors, most active users, most consumed memory — combines Group-Object, Sort-Object, and Select-Object in a clean pipeline.

# Top 5 processes by number of instances
Get-Process |
    Group-Object Name |
    Sort-Object Count -Descending |
    Select-Object -First 5 |
    Select-Object Name, Count,
        @{ N="TotalMemMB"; E={ [math]::Round(($_.Group | Measure-Object WorkingSet -Sum).Sum / 1MB, 1) } } |
    Format-Table -AutoSize
Name       Count  TotalMemMB
----       -----  ----------
svchost    42     412.3
chrome     18     2841.7
conhost    12     45.2
RuntimeBroker 8   128.9
SearchHost 4      89.1

Common Errors and Fixes

  • Group members accessed via .Group not direct array: The groups returned by Group-Object have a Group property that contains the matching objects. Trying to index into the group directly (like $grp[0]) references the group object, not its members. Access members with $grp.Group[0] or pipe $grp.Group to further cmdlets.
  • Case sensitivity in string grouping: By default, Group-Object is case-insensitive when grouping string values — "ERROR" and "error" are treated as the same group. Use -CaseSensitive to distinguish them when case matters, such as when analyzing log levels where the case reflects different severity conventions.

Related Cmdlets / See Also

Wrapping Up

Group-Object transforms any collection into a frequency analysis in a single pipeline step — combine it with Sort-Object and Select-Object -First N to get your top-N report instantly. As a next step, run the event log frequency analysis from the first section against your production Application log to see which providers are most active — that pattern alone is worth having in your diagnostic toolkit.

Send-Item -To