Want to iterate IPv4 addresses in Windows PowerShell? Let’s talk PowerShell classes, how to use them and what they can do for you! Buckle up, this is a long one…
PowerShell has always appealed to me as a programmer who spends most of his time doing more IT support-related tasks. It has a lot of programming-like conventions and the hooks into Windows server administration are, well, very powerful (if you haven’t explored the ActiveDirectory module, you’re missing out on something big).
One thing it has definitely lacked is the ability to build and flesh out custom objects – sure, you can harness the PS-Object type, but any functions that want to utilize your custom data structure need to either be laden with checks and balances to ensure the right parts are in the right places or blindly trust that their consumer always feeds well-formed data. I don’t find either of these options particularly appealing.
Enter PowerShell 5.0, released with the Windows Management Framework (WMF) 5.0 in February, 2016 (link to the download: https://www.microsoft.com/en-us/download/details.aspx?id=50395) and PowerShell classes. While lacking a few features I’d love to see, classes open a new world of possibilities in the scripting world: object oriented scripts.
Walk with me now as we build our first simple PowerShell class to help us work with IPv4 addresses and eventually iterate through IP blocks – something I have always wished I could do more easily in PowerShell. (Want to skip the how to and get yourself a copy of the full IPHelper class? Click here to download the file now!)
Part One: Make me a class!
Let’s start with a super basic class called “IPHelper” that’s going to have one member variable called “IPAddress” that will be a 32-bit unsigned integer (for the math challenged, 4 x 8-bit octets = 32 bits). Copy the following code into a file called “bcIPHelper.ps1” and save it. Note that unlike most object-oriented languages, PowerShell classes cannot have private or protected member variables. The keyword “hidden” will make the field unavailable when tab-completing or running Get-Member
against your class, but it can still be manually referenced and will show up in the more verbose Get-Member -Force
cmdlet.
class IPHelper { # declare member variable hidden [uint32]$IPAddress = 0 }
Now, in a PowerShell window, load up your class by first running . .\bcIPHelper.ps1
. If you have errors here, make sure you’re running PowerShell 5.0 (check $PSVersionTable.PSVersion
) and make sure you have no typos. Next, instantiate your new class by running $IP = New-Object -TypeName 'IPHelper'
. Verify now that you can set and get the value of $IP.IPAddress
, and notice that you cannot tab-complete the variable name. Also notice that though 32-bit unsigned decimal integers can represent IPv4 addresses, they sure are ugly.
Part Two: Make it less ugly!
There are, for me, only two ways I ever want to look at IP addresses: hexadecimal and quad-dotted notation of each decimal octet. So we can override the ToString()
function to output the dot notation string and add a ToHexString()
function to output the IP address as a hexadecimal string.
class IPHelper { # declare member variable hidden [uint32]$IPAddress = 0 # export quad-dotted string [string]ToString() { # get the hex string $strValue = $this.ToHexString() # slice into 2-character octets, convert back to decimal, and combine with '.' return ([Convert]::ToUint16($strValue.Substring(0, 2), 16)).ToString() + '.' + ([Convert]::ToUint16($strValue.Substring(2, 2), 16)).ToString() + '.' + ([Convert]::ToUint16($strValue.Substring(4, 2), 16)).ToString() + '.' + ([Convert]::ToUint16($strValue.Substring(6, 2), 16)).ToString() } # export hex string [string]ToHexString() { return ('{0:x8}' -f $this.IPAddress) } }
Creating a hex string is fairly straightforward. '{0:x8}'
tells PowerShell to format the number in hexadecimal with lower-case letters and pad it out with zeros to 8 characters. The quad-dotted notation is a bit more complex. Using the hex string, it slices it into two-character substrings, converts those back from hex to decimal and recombines them with the “.”s in between. Follow the steps in part one to instantiate a new version of your class and try these new functions out.
Part Three: Make it easy to create!
Alright, I got it. We need prettier input as well as output. To accomplish this, we’ll add two static functions called FromString()
and FromInt()
. Static functions work just like any programming language. They are executed by referencing the class instead of an instance of the class (for example, [IPHelper]::FromString()
).
Looking at the code, importing from an integer couldn’t be easier. It just wraps the $IP = New-Object -TypeName 'IPHelper'
and the $IP.IPAddress=4294967295
lines into one $IP = [IPHelper]::FromInt(4294967295)
line. Maybe not the most helpful thing in the world, but it has its applications.
Importing a quad-dotted string is another story. There are some checks for the input string’s validity: it must have four “.”-separated values that are each between 0 and 255. A little bit of tricky math utilizes hex notation to add the octet values into one 32-bit unsigned integer.
class IPHelper { # declare member variable hidden [uint32]$IPAddress = 0 # import from string static [IPHelper]FromString([string]$InputString) { # split string on dots and verify length $arrSplit = $InputString.Split('.') if ($arrSplit.Count -ne 4) { throw 'Invalid IP address in input string.' } # get octets (will throw an unhandled exception if input is out of range or non-numeric) $iOctet1 = [byte]$arrSplit[0] $iOctet2 = [byte]$arrSplit[1] $iOctet3 = [byte]$arrSplit[2] $iOctet4 = [byte]$arrSplit[3] # create return object $objReturn = New-Object -TypeName 'IPHelper' # calculate IP address $objReturn.IPAddress = ($iOctet1 * 0x01000000) + ($iOctet2 * 0x00010000) + ($iOctet3 * 0x00000100) + $iOctet4 return $objReturn } # import from integer static [IPHelper]FromInt([uint32]$InputInteger) { # create return object $objReturn = New-Object -TypeName 'IPHelper' # set IP address $objReturn.IPAddress = $InputInteger return $objReturn } # export quad-dotted string [string]ToString() { # get the hex string $strValue = $this.ToHexString() # slice into 2-character octets, convert back to decimal, and combine with '.' return ([Convert]::ToUint16($strValue.Substring(0, 2), 16)).ToString() + '.' + ([Convert]::ToUint16($strValue.Substring(2, 2), 16)).ToString() + '.' + ([Convert]::ToUint16($strValue.Substring(4, 2), 16)).ToString() + '.' + ([Convert]::ToUint16($strValue.Substring(6, 2), 16)).ToString() } # export hex string [string]ToHexString() { return ('{0:x8}' -f $this.IPAddress) } }
Try it again, then dance for joy at the pretty little IPHelper you’ve created!
Part 4: Make it iterable!
Finally, let’s figure out how to sweep IP ranges with ease. To make IPHelper iterate, we need two things: the ability to compare two IPHelper objects and the ability to increment from one IPHelper object to the next. So we’re going to add a slew of new functions, all of which are easy to understand: EqualTo()
, GreaterThan()
, GreaterThanOrEqual()
, LessThan()
, LessThanOrEqual()
, Next()
, and Previous()
.
class IPHelper { # declare member variable hidden [uint32]$IPAddress = 0 # import from string static [IPHelper]FromString([string]$InputString) { # split string on dots and verify length $arrSplit = $InputString.Split('.') if ($arrSplit.Count -ne 4) { throw 'Invalid IP address in input string.' } # get octets (will throw an unhandled exception if input is out of range or non-numeric) $iOctet1 = [byte]$arrSplit[0] $iOctet2 = [byte]$arrSplit[1] $iOctet3 = [byte]$arrSplit[2] $iOctet4 = [byte]$arrSplit[3] # create return object $objReturn = New-Object -TypeName 'IPHelper' # calculate IP address $objReturn.IPAddress = ($iOctet1 * 0x01000000) + ($iOctet2 * 0x00010000) + ($iOctet3 * 0x00000100) + $iOctet4 return $objReturn } # import from integer static [IPHelper]FromInt([uint32]$InputInteger) { # create return object $objReturn = New-Object -TypeName 'IPHelper' # set IP address $objReturn.IPAddress = $InputInteger return $objReturn } # export quad-dotted string [string]ToString() { # get the hex string $strValue = $this.ToHexString() # slice into 2-character octets, convert back to decimal, and combine with '.' return ([Convert]::ToUint16($strValue.Substring(0, 2), 16)).ToString() + '.' + ([Convert]::ToUint16($strValue.Substring(2, 2), 16)).ToString() + '.' + ([Convert]::ToUint16($strValue.Substring(4, 2), 16)).ToString() + '.' + ([Convert]::ToUint16($strValue.Substring(6, 2), 16)).ToString() } # export hex string [string]ToHexString() { return ('{0:x8}' -f $this.IPAddress) } # calculate equal to [bool]EqualTo([IPHelper]$IPToCompare) { return $this.IPAddress -eq $IPToCompare.IPAddress } # calculate greater than [bool]GreaterThan([IPHelper]$IPToCompare) { return $this.IPAddress -gt $IPToCompare.IPAddress } # calculate greater than or equal to [bool]GreaterThanOrEqual([IPHelper]$IPToCompare) { return $this.IPAddress -ge $IPToCompare.IPAddress } # calculate less than [bool]LessThan([IPHelper]$IPToCompare) { return $this.IPAddress -lt $IPToCompare.IPAddress } # calculate less than or equal to [bool]LessThanOrEqual([IPHelper]$IPToCompare) { return $this.IPAddress -le $IPToCompare.IPAddress } # get next IP [IPHelper]Next() { # check for max value if ($this.IPAddress -eq [uint32]::MaxValue) { # roll around to all zeroes return [IPHelper]::FromInt(0) } else { # add one to current IP return [IPHelper]::FromInt($this.IPAddress + 1) } } # get previous IP [IPHelper]Previous() { # check for zeroes if ($this.IPAddress -eq 0) { # roll around to max value return [IPHelper]::FromInt([uint32]::MaxValue) } else { # subtract one from current IP return [IPHelper]::FromInt($this.IPAddress - 1) } } }
Now for the pièce de résistance, we will iterate. Load up your newest version of the IPHelper class and try this on for size:
for ($IP = [IPHelper]::FromString('192.168.1.1'); $IP.LessThan('192.168.1.255'); $IP = $IP.Next()) { # do some stuff here... like maybe ping all these IPs? }
If you made it this far, thanks for hanging in there! What else can we make this class do? What other classes can you think up? I’d love to see your comments below!