# IP array to IP range

• ### 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 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 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