Advanced Sorting (Part 3)

by Nov 5, 2021

In the previous tip you have seen how Sort-Object accepts hash tables, providing you with advanced control over the sorting. For example, this line has sorted services by status, then display name, and used individual sort directions for each property:

Get-Service | 
Sort-Object -Property @{Expression='Status' Descending=$true}, @{Expression='DisplayName' Descending=$false } | 
Select-Object -Property DisplayName, Status

However, when you looked at the results, the “Status” property appeared to be sorted in the wrong direction. We already clarified that this happened because “Status” really is a numeric constant, and Sort-Object always sorts the underlying native data. It sorted “Status” by its internal numeric constants and not by the visible friendly name.

To make sure Sort-Object sorts such items the way you “see” them, use another feature of hash tables: the “Expression” key not only accepts the name of a property. Alternatively, you can submit a script block (code) and transform the data that needs sorting in any way you want. Inside the script block, $_ represents the full incoming object.

To make sure “Status” is sorted by its text equivalent and not by its underlying numeric constant, convert it to string like so:

Get-Service | 
Sort-Object -Property @{Expression={[string]$_.Status} Descending=$true}, @{Expression='DisplayName' Descending=$false } | 
Select-Object -Property DisplayName, Status

In fact, the ability to transform the data before sorting opens a whole new universe of sorting tricks.

Let’s assume you have a list of HTTPS connections with remote IP addresses your browser(s) are connected to, and you are trying to sort these IP addresses by adding Sort-Object:

 
PS C:\> Get-NetTCPConnection -RemotePort 443 | Select-Object -Property RemoteAddress, RemotePort, State, OwningProcess | Sort-Object -Property RemoteAddress

RemoteAddress  RemotePort       State OwningProcess
-------------  ----------       ----- -------------
142.250.185.74        443    TimeWait             0
142.250.186.46        443    TimeWait             0
20.199.120.182        443 Established          3552
20.199.120.182        443 Established          5564
20.42.65.89           443 Established          9836
20.42.65.89           443    TimeWait             0
20.42.73.24           443    TimeWait             0
35.186.224.25         443    TimeWait             0
45.60.13.212          443 Established          7644
51.104.30.131         443 Established         10220   
 

If you look at the results closely you’ll see that “RemoteAddress” was not sorted correctly: the IP address “20.199.120.182” was listed before “20.42.65.89”, for example.

That’s because these IPv4 addresses were treated as strings so Sort-Object used the default alphanumeric sorting algorithm.

You now know how to transform the data in a more appropriate data type. IPv4 addresses, for example, can be treated as software versions (data type [version]) to be sorted correctly:

 
PS> Get-NetTCPConnection -RemotePort 443 | Select-Object -Property RemoteAddress, RemotePort, State, OwningProcess | Sort-Object -Property @{Expression={ $_.RemoteAddress -as [version] }}

RemoteAddress  RemotePort       State OwningProcess
-------------  ----------       ----- -------------
20.42.65.89           443 Established          9836
20.42.65.89           443    TimeWait             0
20.199.120.182        443 Established          3552
20.199.120.182        443 Established          5564
35.186.224.25         443    TimeWait             0
45.60.13.212          443 Established          7644
51.104.30.131         443 Established         10220
142.250.185.74        443    TimeWait             0
142.250.186.46        443    TimeWait             0  
 

Now the IP addresses are sorted correctly.

Note: When transforming data to other data types (as shown here), always prefer the -as operator over direct type casts. If the data cannot be converted to the desired data type, the -as operator simply emits $null whereas a direct type cast would emit exceptions.

In the previous example, if “RemoteAddress” would show an IPv6 address (which can’t be converted to [version]), thanks to the -as operator, these items would simply not be sorted and put at the beginning of the sorted data.

To shorten your code, you can abbreviate the hash table keys (as long as they remain unique):

Get-NetTCPConnection -RemotePort 443 | 
Select-Object -Property RemoteAddress, RemotePort, State, OwningProcess | 
Sort-Object -Property @{E={ $_.RemoteAddress -as [version] }}

If all you need is a data transform, you can skip the hash table altogether and submit the script block directly to the -Property parameter:

 
PS> Get-NetTCPConnection -RemotePort 443 | Select-Object -Property RemoteAddress, RemotePort, State, OwningProcess | Sort-Object -Property { $_.RemoteAddress -as [version] }

RemoteAddress  RemotePort       State OwningProcess
-------------  ----------       ----- -------------
20.42.65.89           443 Established          9836
20.42.65.89           443    TimeWait             0
20.199.120.182        443 Established          3552
20.199.120.182        443 Established          5564
35.186.224.25         443    TimeWait             0
45.60.13.212          443 Established          7644
51.104.30.131         443 Established         10220
142.250.185.74        443    TimeWait             0
142.250.186.46        443    TimeWait             0  
 


Twitter This Tip! ReTweet this Tip!