none
IP array to IP range RRS feed

  • Question

  • Hi guys

    Learning Poweshell and stuck at which seems to be an easy task, but wrecking my brain on it for some time...

    I have a list of IP addresses in an array $ipArray. I need to go through this list and return IP addresses grouped in IP range (if they can be grouped) or just plain IP.

    Something like this:

    10.18.130.136
    10.18.130.137
    10.2.110.167

    10.18.130.138
    10.2.110.170
    10.18.130.139

    OUTPUT:

    10.18.130.136 - 10.18.130.139
    10.2.110.167
    10.2.110.170

    Tuesday, September 2, 2014 7:52 AM

Answers

  • I find that anytime I want to compare IPv4 addresses, it's easier to convert them to their 32-bit numeric representations first. For some silly reason, the IPAddress.Address property gives this to you in Little-Endian byte order, which is worthless for doing any sort of IP math, so I use a pair of utility functions to perform conversions to and from UInt32 values in the proper byte order:

    function IPv4ToUInt32([ipaddress]$IPAddress)
    {
        if ($IPAddress.AddressFamily -ne [System.Net.Sockets.AddressFamily]::InterNetwork) { throw "$($IPAddress.IPAddressToString) is not an IPv4 address." }
    
        $bytes = $IPAddress.GetAddressBytes()
        if ([BitConverter]::IsLittleEndian) { [Array]::Reverse($bytes) }
        return [BitConverter]::ToUInt32($bytes, 0)
    }
    
    function UInt32ToIPv4([UInt32] $Value)
    {
        $bytes = [BitConverter]::GetBytes($Value)
        if ([BitConverter]::IsLittleEndian) { [Array]::Reverse($bytes) }
        return New-Object ipaddress(,$bytes)
    }
    

    With those out of the way, your problem can be solved with these general steps:

    1. Convert the list of IPs to their 32-bit numeric values.
    2. Sort the array.
    3. Enumerate the array, looking for consecutive values.
    4. Convert the numbers back into their IPAddress / string representations for output.

    Something along these lines should work.  (I won't paste in the two functions again, but they do need to be present at the beginning of the script.)

    # This function takes a sorted list of UInt32 values and converts them either to
    # a single IP address (if the list contains one value) or a Range string of
    # First - Last.  All UInt32 values are converted back into dotted-decimal IPv4
    # strings during this process.
    
    function Write-IPRange([UInt32[]] $UInt32)
    {
        if ($null -eq $UInt32 -or $UInt32.Count -eq 0) { return }
    
        $string = (UInt32ToIPv4 -Value $UInt32[0]).IPAddressToString
        if ($UInt32.Count -gt 1)
        {
            $string += ' - ' + (UInt32ToIPv4 -Value $UInt32[-1]).IPAddressToString
        }
    
        return $string
    }
    
    # Sample data.
    
    $string = @'
    10.18.130.136 
    10.18.130.137 
    10.2.110.167 
    
      10.18.130.138
    10.2.110.170
    10.18.130.139
    '@
    
    # This line converts the text list into an array.  Each line is treated as a single IP, and any
    # unwanted whitespace or blank lines is removed.  In the actual script, you may not need this step,
    # if the function accepts an array of strings or [ipaddress] objects already.
    $IPList = $string -split '\r?\n' -replace '^\s+|\s+$' -match '\S'
    
    # Now we convert the IPList string array into an array of UInt32 values, and sort it.
    # Note:  IPv4ToUInt32 could be modified to accept pipeline input, removing the need
    # for ForEach-Object here.
    $ipNumbers = @($IPList | ForEach-Object { IPv4ToUInt32 -IPAddress $_ } | Sort-Object)
    
    # Now we enumerate the array, looking for consecutive values.
    $range = @()
    
    for ($i = 0; $i -lt $ipNumbers.Count; $i++)
    {
        if ($range.Count -gt 0 -and $ipNumbers[$i] -ne $range[-1] + 1)
        {
            Write-IPRange -UInt32 $range
            $range = @()
        }
    
        $range += $ipNumbers[$i]
    }
    
    # We need to make one final call to Write-IPRange to deal with the final value(s) in the array:
    Write-IPRange -UInt32 $range
    

    Note:  $array[-1] is PowerShell shorthand for accessing the last element in an array.  It means the same thing as $array[$array.Count - 1]

    • Marked as answer by SergeyAU Wednesday, September 3, 2014 3:18 AM
    Tuesday, September 2, 2014 11:55 AM

All replies

  • I find that anytime I want to compare IPv4 addresses, it's easier to convert them to their 32-bit numeric representations first. For some silly reason, the IPAddress.Address property gives this to you in Little-Endian byte order, which is worthless for doing any sort of IP math, so I use a pair of utility functions to perform conversions to and from UInt32 values in the proper byte order:

    function IPv4ToUInt32([ipaddress]$IPAddress)
    {
        if ($IPAddress.AddressFamily -ne [System.Net.Sockets.AddressFamily]::InterNetwork) { throw "$($IPAddress.IPAddressToString) is not an IPv4 address." }
    
        $bytes = $IPAddress.GetAddressBytes()
        if ([BitConverter]::IsLittleEndian) { [Array]::Reverse($bytes) }
        return [BitConverter]::ToUInt32($bytes, 0)
    }
    
    function UInt32ToIPv4([UInt32] $Value)
    {
        $bytes = [BitConverter]::GetBytes($Value)
        if ([BitConverter]::IsLittleEndian) { [Array]::Reverse($bytes) }
        return New-Object ipaddress(,$bytes)
    }
    

    With those out of the way, your problem can be solved with these general steps:

    1. Convert the list of IPs to their 32-bit numeric values.
    2. Sort the array.
    3. Enumerate the array, looking for consecutive values.
    4. Convert the numbers back into their IPAddress / string representations for output.

    Something along these lines should work.  (I won't paste in the two functions again, but they do need to be present at the beginning of the script.)

    # This function takes a sorted list of UInt32 values and converts them either to
    # a single IP address (if the list contains one value) or a Range string of
    # First - Last.  All UInt32 values are converted back into dotted-decimal IPv4
    # strings during this process.
    
    function Write-IPRange([UInt32[]] $UInt32)
    {
        if ($null -eq $UInt32 -or $UInt32.Count -eq 0) { return }
    
        $string = (UInt32ToIPv4 -Value $UInt32[0]).IPAddressToString
        if ($UInt32.Count -gt 1)
        {
            $string += ' - ' + (UInt32ToIPv4 -Value $UInt32[-1]).IPAddressToString
        }
    
        return $string
    }
    
    # Sample data.
    
    $string = @'
    10.18.130.136 
    10.18.130.137 
    10.2.110.167 
    
      10.18.130.138
    10.2.110.170
    10.18.130.139
    '@
    
    # This line converts the text list into an array.  Each line is treated as a single IP, and any
    # unwanted whitespace or blank lines is removed.  In the actual script, you may not need this step,
    # if the function accepts an array of strings or [ipaddress] objects already.
    $IPList = $string -split '\r?\n' -replace '^\s+|\s+$' -match '\S'
    
    # Now we convert the IPList string array into an array of UInt32 values, and sort it.
    # Note:  IPv4ToUInt32 could be modified to accept pipeline input, removing the need
    # for ForEach-Object here.
    $ipNumbers = @($IPList | ForEach-Object { IPv4ToUInt32 -IPAddress $_ } | Sort-Object)
    
    # Now we enumerate the array, looking for consecutive values.
    $range = @()
    
    for ($i = 0; $i -lt $ipNumbers.Count; $i++)
    {
        if ($range.Count -gt 0 -and $ipNumbers[$i] -ne $range[-1] + 1)
        {
            Write-IPRange -UInt32 $range
            $range = @()
        }
    
        $range += $ipNumbers[$i]
    }
    
    # We need to make one final call to Write-IPRange to deal with the final value(s) in the array:
    Write-IPRange -UInt32 $range
    

    Note:  $array[-1] is PowerShell shorthand for accessing the last element in an array.  It means the same thing as $array[$array.Count - 1]

    • Marked as answer by SergeyAU Wednesday, September 3, 2014 3:18 AM
    Tuesday, September 2, 2014 11:55 AM
  • Man... Great! 

    Reading through the code just realize how much more to learn

    Wednesday, September 3, 2014 3:20 AM