Creating IP Ranges (and other type magic)

Recently I needed to create a range of IP addresses. No big deal, you think? Well, if it is just a small segment, then it *is* easy stuff:

1..255 | Foreach-Object { "192.168.2.$_" }

But what if you need larger segments?

Type Conversion Magic

Ok, you could use nested loops. However, there's also a type that represents IP addresses. It is called System.Net.IPAddress, and it can turn a string into an IP address. It even calculates the decimal value (check out property Address), and you can also convert a decimal value back into an IP address:

PS > [System.Net.IPAddress]'192.168.2.1'


Address : 16951488
AddressFamily : InterNetwork
ScopeId :
IsIPv6Multicast : False
IsIPv6LinkLocal : False
IsIPv6SiteLocal : False
IPAddressToString : 192.168.2.1



PS > [System.Net.IPAddress]16951488


Address : 16951488
AddressFamily : InterNetwork
ScopeId :
IsIPv6Multicast : False
IsIPv6LinkLocal : False
IsIPv6SiteLocal : False
IPAddressToString : 192.168.2.1

Now, since IP addresses really are just 32bit numeric values, this conversion is doing an amazing thing when you start thinking about it: it breaks up the decimal into its byte parts. Breaking up a number in its High/Low-part something needed very often, and most of the time people are coding confusing amounts of math into their scripts to do just that.

Breaking Up Decimals in Low-/High-Bytes

So the first thing I want to do is use the System.Net.IPAddress type to break up any decimal into its low/high bytes. I created a pretty small function called ConvertTo-HighLow which you can download here.

function ConvertTo-HighLow($number) {
$v = [System.Version][String]([System.Net.IPAddress]$number)
$o = 1 | Select-Object Low, High, Low32, High32
$o.Low, $o.High, $o.Low32, $o.High32 = $v.Major, $v.Minor, $v.Build, $v.Revision
$o
}

It accepts any unsigned 64bit number and returns its four bytes like this:

PS > ConvertTo-HighLow 255

Low High Low32 High32
--- ---- ----- ------
255 0 0 0


PS > ConvertTo-HighLow 256

Low High Low32 High32
--- ---- ----- ------
0 1 0 0


PS > ConvertTo-HighLow 52176537

Low High Low32 High32
--- ---- ----- ------
153 38 28 3

Here is what I did: I converted the number to an IP address. This did all the complex math by converting the number into four octets. Next, I converted the string IP address into a System.Version type. A version has four numbers, just like an IP address, so now I was able to create a new object (using the Select-Object trick) with the properties I needed and add the byte values to the appropriate properties of my return object - done.

If you just need the low or high byte of a decimal, you could also boil it down to this:

PS > $low = ([System.Net.IPAddress]15672).GetAddressBytes()[0]
PS > $high = ([System.Net.IPAddress]15672).GetAddressBytes()[1]
PS > $low
56
PS > $High
61
PS > $high*256 + $low
15672

Creating IP Address Ranges

Now that this worked perfectly, let's create IP address ranges. Here is the scoop: if every ip address is just a (large) decimal number, then why not convert start and end ip address into such a number and loop through that range? Each number could then be converted back into a valid IP address!

Sounds cool, BUT. IP addresses organize bytes in an unusual way. Turns out that the lowest byte represents the first octet. So if you counted up, you would not get consecutive ip address ranges.

The solution is simple, though. Just reverse the octets before you convert them, create the numeric range, and reverse them back. Here is the function New-IPRange (which you can also download here):

function New-IPRange ($start, $end) {
# created by Dr. Tobias Weltner, MVP PowerShell
$ip1 = ([System.Net.IPAddress]$start).GetAddressBytes()
[Array]::Reverse($ip1)
$ip1 = ([System.Net.IPAddress]($ip1 -join '.')).Address

$ip2 = ([System.Net.IPAddress]$end).GetAddressBytes()
[Array]::Reverse($ip2)
$ip2 = ([System.Net.IPAddress]($ip2 -join '.')).Address

for ($x=$ip1; $x -le $ip2; $x++) {
$ip = ([System.Net.IPAddress]$x).GetAddressBytes()
[Array]::Reverse($ip)
$ip -join '.'
}
}

Now it is really easy to create IP address ranges. Try this:

PS > New-IPRange 192.168.1.12 192.168.3.44
192.168.1.12
192.168.1.13
192.168.1.14
...
192.168.3.43
192.168.3.44

When you look at the code, it really is pretty straight-forward: both start and end address are converted into an ip address, then the four octets are read into an array (GetAddressBytes()). The static Reverse() method can reverse the array content.

Now we have two numbers, the start and the end point. A for loop can loop through that range (you cannot use the ".." trick because the numbers are 64bit values and ".." only deals with Int32). Finally, the numbers are again converted into an ip address and the bytes reversed.  Now, -join can put the bytes together and create an ip address.

Wait, there is more!

Using types can be fun, and you can achive a lot of cool results. Now that you can easily create IP address segments, what is the purpose? Of course there is always a purpose. Originally, I wanted not just a tool to create addresses but also check which computers are online in that segment and what their names are.

If you did that with Test-Connection or ping, it could take forever because it would work sequentially. Next time around, I show you how to ping 500 computers at the same time, making analyzing an entire network segment only a matter of seconds.

Hope you had fun, see you next week around...

Tobias

Microsoft MVP PowerShell Germany

P.S.
If you live in Germany or other parts of Europe and your company would like to set up a truly great PowerShell training, just contact me! I regularly train mid- to large-size companies. Trainings are always a blast with tons of real-world-examples and solutions. Here's how to get in touch with me: tobias.weltner@scriptinternals.de  


Posted Feb 20 2011, 11:20 PM by Tobias
Concentrated Tech NSoftware Dell Compellent Sponsored by Idera and Concentrated Tech and NSoftware and Dell Compellent
Copyright 2011 PowerShell.com. All rights reserved.