Monday, March 28, 2016

Sorting IP addresses in PowerShell, part 1, IPv4

Sorting IP v4 addresses is a pain. If they are strings, they sort typographically rather than numerically.

$IPAddresses = @(
'10.11.12.13'
'10.11.102.3'
'10.11.10.26'
'10.11.10.252'
)

$IPAddresses | Sort

10.11.10.252
10.11.10.26
10.11.102.3
10.11.12.13

If they are [ipaddress] objects, they do the same thing. The [ipaddress] does not have the built in methods that PowerShell would use to sort it, so PowerShell converts it to a string, and then sorts it typographically.

$IPAddresses = @(
[System.Net.IPAddress]'10.11.12.13'
[System.Net.IPAddress]'10.11.102.3'
[System.Net.IPAddress]'10.11.10.26'
[System.Net.IPAddress]'10.11.10.252'
)

$IPAddresses | Sort | Select IPAddressToString

IPAddressToString
-----------------
10.11.10.252
10.11.10.26
10.11.102.3
10.11.12.13

I have in the past come up with a variety of complex ways to do it. Here’s one.

# Function to convert dotted decimal notation to integer64 (e.g. - "255.255.255.0" to 4294967040)
function ConvertDottedDecimalToInt64 ( $DottedDecimal )
    {
    $DecimalParts = $DottedDecimal.Split( "." )
    $Result = [int64]0
    0..3 | ForEach { $Result += [int64]$DecimalParts[$_] * [math]::pow( 256, 3 - $_ ) }
    return $Result
    }

$IPAddresses = @(
'10.11.12.13'
'10.11.102.3'
'10.11.10.26'
'10.11.10.252'
)

$IPAddresses | Sort { ConvertDottedDecimalToInt64 $_ }

10.11.10.26
10.11.10.252
10.11.12.13
10.11.102.3

There is an easier way

There is an object which can look like an IP address, which needs to be sorted like an IP address, and whose creators had the foresight to endow it with a .CompareTo() method, which is what PowerShell uses when it sorts things. The [System.Version] object is used to represent file and application versions, and we can leverage it to sort IP addresses simply. We sort on a custom calculation, converting the IP addresses to version objects. The conversion is just for sorting purposes. The output is the original input objects.

$IPAddresses = @(
'10.11.12.13'
'10.11.102.3'
'10.11.10.26'
'10.11.10.252'
)

$IPAddresses | Sort { [System.Version]$_ }

10.11.10.26
10.11.10.252
10.11.12.13
10.11.102.3

The System namespace is the default namespace PowerShell searches for things, so we can leave that out and write it more simply.

$IPAddresses | Sort { [version]$_ }

Or if we want to go the other way, and avoid aliases and positional parameters, we can say it this way.

$IPAddresses | Sort-Object -Property { [System.Version]$_ }

13 comments:

  1. Damn, this is a great solution! Awesome!

    ReplyDelete
  2. I searched for a way to do this and this site came up. Very nice, thank you.

    ReplyDelete
  3. Awesome!
    Now I wonder if this is part 1, where are the other parts ? ;-)

    ReplyDelete
  4. Just came to this page, brilliant that System.Version, but.. it's failing for me (multidimensional array, from uncontrolled source) when I happen to get devices without an IP Address, get this error, understandably so, but thought you might appreciate the feedback:
    Sort-Object : Cannot convert value "" to type "System.Version". Error: "Version string portion was too short or too long."
    So I resorted to using your function, that did the trick, thanks!!

    ReplyDelete