PowerShell Priority Sorting: A Multi-Key Approach

PowerShell Priority Sorting: A Multi-Key Approach

When working with data in PowerShell, you’ll often need sorting that goes beyond simple alphabetical or numerical ordering. This post explores how to use multiple sort keys with calculated properties to implement sophisticated priority-based sorting.

The Problem

Imagine you have a mixed list of items and you need to prioritize them in a specific order:

  • Certain items should appear first
  • Other items should have medium priority
  • Everything else comes last

Within each priority group, you still want consistent ordering (such as alphabetical).

The Solution

Here’s the technique in action with a simple example.

 1# Unsorted list of fruits (🥑 are fruit - who knew?)
 2$items = @(
 3    'Cherry'
 4    'Blueberry'
 5    'Apple'
 6    'Apricot'
 7    'Orange'
 8    'Avocado'
 9    'Banana'
10)
11
12$items | Sort-Object {
13    if ($_ -like 'B*') {
14        # B-items get highest priority
15        0 
16    } elseif ($_ -like 'O*') {
17        # O-items get second priority  
18        1
19    } else {
20        # Everything else gets lowest priority
21        2
22    }
23},{
24    # Then sort alphabetically within each group
25    $_
26}
Banana
Blueberry
Orange
Apple
Apricot
Avocado
Cherry

We get the above sorted list; where Banana and Blueberry come first, followed by Orange, then everything else sorted alphabetically.

Here’s what’s happening: we start with an unsorted list of fruit and pipe them to Sort-Object. We provide two script blocks that each define a sorting property.

The magic ✨ in those script blocks is that they create custom sorting rules on-the-fly. Instead of being limited to existing object properties, you can define your own. This technique is called Calculated Properties.

Calculated Properties

Calculated Properties are a neat feature that let you create custom properties on-the-fly without touching the original object. While they’re super handy for sorting, you’ll find this same pattern in other places too - you’ve probably used it in Select-Object already.

Our fruit example used the shorthand syntax (just script blocks), but there’s a more complete hashtable approach that gives you finer-grained control. With hashtables, you use Expression and Descending keys to define exactly how each sort should work - including the ability to mix sort directions.

Real-World Application

Consider a practical IT scenario: you need to perform operations across Active Directory domain controllers in different regions. You want to prioritize by geographic proximity - London first, then Manchester, then Edinburgh, with other regional offices last:

 1# Get all domain controllers from Active Directory
 2$Domain = Get-ADDomain -Identity 'contoso.co.uk'
 3$DomainControllers = $Domain.ReplicaDirectoryServers
 4
 5# Sample domain controllers that might be returned:
 6@(
 7    'BRI-DC01.contoso.co.uk'  # Bristol
 8    'LON-DC01.contoso.co.uk'  # London
 9    'MAN-DC01.contoso.co.uk'  # Manchester
10    'LEE-DC01.contoso.co.uk'  # Leeds
11    'BRI-DC02.contoso.co.uk'  # Bristol
12    'LON-DC02.contoso.co.uk'  # London
13    'BIR-DC01.contoso.co.uk'  # Birmingham
14    'BIR-DC02.contoso.co.uk'  # Birmingham
15    'BIR-DC03.contoso.co.uk'  # Birmingham
16    'EDI-DC01.contoso.co.uk'  # Edinburgh
17)

Geographic Priority Sort

Here’s how to automatically prioritize them by region:

1$DomainControllers | Sort-Object {
2    switch($_) {
3        {$_ -like 'LON*'} { 0; break }  # London gets priority
4        {$_ -like 'MAN*'} { 1; break }  # Manchester second
5        {$_ -like 'EDI*'} { 2; break }  # Edinburgh third
6        default { 3 }                   # Everything else last
7    }
8}, { $_ }  # Then alphabetically within each region
LON-DC01.contoso.co.uk
LON-DC02.contoso.co.uk
MAN-DC01.contoso.co.uk
EDI-DC01.contoso.co.uk
BIR-DC01.contoso.co.uk
BIR-DC02.contoso.co.uk
BIR-DC03.contoso.co.uk
BRI-DC01.contoso.co.uk
BRI-DC02.contoso.co.uk
LEE-DC01.contoso.co.uk

Notice how London DCs (LON-*) appear first, followed by Manchester (MAN-*), then Edinburgh (EDI-*), with all others last - exactly matching our priority numbers 0, 1, 2, 3.

Mixed Sort Directions

The hashtable syntax lets you control the sorting direction for each criterion. For example, you might want regions sorted by priority (ascending), but server names within each region sorted in reverse alphabetical order (descending):

 1$DomainControllers | Sort-Object @{
 2    Expression = {
 3        switch($_) {
 4            {$_ -like 'LON*'} { 0; break }
 5            {$_ -like 'MAN*'} { 1; break }
 6            {$_ -like 'EDI*'} { 2; break }
 7            default { 3 }
 8        }
 9    }
10    Descending = $false  # Regions in priority order (ascending)
11}, @{
12    Expression = { $_ }
13    Descending = $true   # Server names in reverse alphabetical order
14}
LON-DC02.contoso.co.uk
LON-DC01.contoso.co.uk
MAN-DC01.contoso.co.uk
EDI-DC01.contoso.co.uk
LEE-DC01.contoso.co.uk
BRI-DC02.contoso.co.uk
BRI-DC01.contoso.co.uk
BIR-DC03.contoso.co.uk
BIR-DC02.contoso.co.uk
BIR-DC01.contoso.co.uk

🥡 Key Takeaways

  • Calculated Properties appear in key cmdlets - The same pattern works in Select-Object, Format-Table, Group-Object, and Measure-Object too (though some use different keys like Label and Expression)
  • Script blocks create dynamic properties - You can create sorting criteria on the fly rather than being limited to existing object properties
  • Multiple sort keys give you control - Combine priority logic with secondary sorting for sophisticated, predictable results
  • Numeric priorities beat complex logic - Using 0, 1, 2… is cleaner and more maintainable than nested conditions
  • Hashtable syntax unlocks mixed sorting - Control ascending/descending direction independently for each sort criterion

📖 Reading List

Share Post