Sorting IPAddresses the PowerShell Way

A question came up on the PowerShell newsgroup about how to sort IP addresses in PowerShell.  The poster had down this in Linux like so:

#> cat ips | awk -F. ‘{printf("%03d.%03d.%03d.%03d\n", $1,$2,$3,$4)};’ |
sort -n -t "." | awk -F. ‘{printf("%d.%d.%d.%d\n", $1,$2,$3,$4)};’

Where the file "ips" contains the following IP addresses:

124.5.6.8
2.4.53.233
12.24.3.78
234.2.5.7
1.5.5.5

You could do the equivalent lexicographical sort in PowerShell like this:  Update: Jacques Barathon and MoW have shortened this approach quite a bit by doing:

> gc ips | sort {"{0:d3}.{1:d3}.{2:d3}.{3:d3}" -f @([int[]]$_.split(‘.’)) 

1.5.5.5
2.4.53.233
12.24.3.78
124.5.6.8
234.2.5.7

Which produces the correct output.  This solution does a string sort after converting each quad so that there are leading zeros which enables string sorting to give the correct answer e.g.:

124.005.006.008
002.004.053.233
012.024.003.078
234.002.005.007
001.005.005.005

And after the sort it round trips the string quads to ints and then reformats them as text without the leading zeros.  However this solution doesn’t take advantage of PowerShell’s unique ability to manipulate domain objects like say an IPAddress object which just happens to exists in the .NET Framework.  So my first take at solving this was to use the System.Net.IPAddress type:

> gc ips | %{[Net.IPAddress]$_} | sort Address | ft IPAddressToString

IPAddressToString
—————–
1.5.5.5
234.2.5.7
124.5.6.8
12.24.3.78
2.4.53.233

Note: It is not necessary to use the ‘System." prefix since PowerShell will do that for you if it can’t find the type.  Now the command above would have been a nice solution that takes advantage of PowerShell’s ability to manipulate objects – if it only worked.  Doh!  It turns out that the Address field value is in little endian byte order which won’t work for sorting.  We need the bytes to be interpreted in big endian order.  We can do this but it admittedly is a bit uglier:

> gc ips | %{[Net.IPAddress]::Parse($_)} | 
  sort {$b=$_.GetAddressBytes();[array]::Reverse($b);[BitConverter]::ToUInt32($b,0)} |
  ft IPAddressToString

IPAddressToString
—————–
1.5.5.5
2.4.53.233
12.24.3.78
124.5.6.8
234.2.5.7

What we really need is a BigEndianAddress property on the System.Net.IPAddress object.  Good luck trying to get the BCL team at Microsoft to add that anytime in the next several years.  🙂  Fortunately we don’t have to wait for them to do this.  PowerShell provides us with an extensible type system where we can add our own convenience properties to a pre-existing .NET object.  It is actually pretty easy.  First you need to construct an XML that tells PowerShell how to modify a particular .NET type:

<?xml version="1.0" encoding="utf-8" ?>
<Types> 
  <Type> 
    <Name>System.Net.IPAddress</Name> 
    <Members> 
      <ScriptProperty> 
        <Name>BigEndianAddress</Name> 
        <GetScriptBlock> 
          $bytes=$this.GetAddressBytes() 
          [array]::Reverse($bytes) 
          [BitConverter]::ToUInt32($bytes,0)        
        </GetScriptBlock> 
      </ScriptProperty> 
    </Members> 
  </Type>
</Types>

Save this to a file called IPAddress.ps1xml.  Now all you have to do is tell PowerShell to update its type data with the information in this file like so:

> Update-TypeData IPAddress.ps1xml

You might want to put this command in your profile so that the System.Net.IPAddress.BigEndianAddress is always available whenever you start up a new PowerShell session.  Now let’s use our new property to sort IP addresses:

> gc ips | %{[Net.IPAddress]$_} | sort BigEndianAddress | ft IPAddressToString

IPAddressToString
—————–
1.5.5.5
2.4.53.233
12.24.3.78
124.5.6.8
234.2.5.7

That is how to do sorting "the PowerShell way"!  Just remember – use the force, err I mean objects!

This entry was posted in PowerShell. Bookmark the permalink.

4 Responses to Sorting IPAddresses the PowerShell Way

  1. Bruce says:

    One other PowerShell feature that can help here is "covariant casts" where a collection of one type (string) can be cast into collection of a different type (IPAddress)  as long as each element in the collection can be converted. So if your file is:
     
    PS (1) > cat ips.txt124.5.6.82.4.53.23312.24.3.78234.2.5.71.5.5.5
     
    Then you can sort by simply doing:
    PS (2) > [net.ipaddress[]] (cat ips.txt) | sort address | ft address, ipaddresstostring -auto
       Address IPAddressToString   ——- —————–  84215041 1.5.5.5 117768938 234.2.5.7 134612348 124.5.6.81308825612 12.24.3.783912565762 2.4.53.233
    You can use the same approach for other data types like dates:
     
    PS (3) > cat dates.txt07/04/177610/10/200701/01/1984PS (4) > [datetime[]] (cat dates.txt) | sort
    Thursday, July 04, 1776 12:00:00 AMSunday, January 01, 1984 12:00:00 AMWednesday, October 10, 2007 12:00:00 AM
     
    -bruce
     
    Bruce Payette [MSFT]

  2. Keith says:

    Ooh, covariant casts.  That\’s a nice tool to add to the toolbox. Thanks! 

  3. Sceptico says:

    Ok, it’s an old post, but if anyone else looks I found a shorter way to do this:

    Sort-Object { [system.version[]]($_.IPAddress) }

Leave a comment