PowerShell Classes: IPHelper

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.

1class IPHelper
2{
3    # declare member variable
4    hidden [uint32]$IPAddress = 0
5}

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.

1class IPHelper
2{
3    # declare member variable
4    hidden [uint32]$IPAddress = 0
5 
6    # export quad-dotted string
7    [string]ToString()
8    {
9        # get the hex string
10        $strValue = $this.ToHexString()
11 
12        # slice into 2-character octets, convert back to decimal, and combine with '.'
13        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()
14    }
15 
16    # export hex string
17    [string]ToHexString()
18    {
19        return ('{0:x8}' -f $this.IPAddress)
20    }
21}

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.

1class IPHelper
2{
3    # declare member variable
4    hidden [uint32]$IPAddress = 0
5 
6    # import from string
7    static [IPHelper]FromString([string]$InputString)
8    {
9        # split string on dots and verify length
10        $arrSplit = $InputString.Split('.')
11        if ($arrSplit.Count -ne 4)
12        {
13            throw 'Invalid IP address in input string.'
14        }
15 
16        # get octets (will throw an unhandled exception if input is out of range or non-numeric)
17        $iOctet1 = [byte]$arrSplit[0]
18        $iOctet2 = [byte]$arrSplit[1]
19        $iOctet3 = [byte]$arrSplit[2]
20        $iOctet4 = [byte]$arrSplit[3]
21 
22        # create return object
23        $objReturn = New-Object -TypeName 'IPHelper'
24 
25        # calculate IP address
26        $objReturn.IPAddress = ($iOctet1 * 0x01000000) + ($iOctet2 * 0x00010000) + ($iOctet3 * 0x00000100) + $iOctet4
27         
28        return $objReturn
29    }
30 
31    # import from integer
32    static [IPHelper]FromInt([uint32]$InputInteger)
33    {
34        # create return object
35        $objReturn = New-Object -TypeName 'IPHelper'
36 
37        # set IP address
38        $objReturn.IPAddress = $InputInteger
39 
40        return $objReturn
41    }
42 
43    # export quad-dotted string
44    [string]ToString()
45    {
46        # get the hex string
47        $strValue = $this.ToHexString()
48 
49        # slice into 2-character octets, convert back to decimal, and combine with '.'
50        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()
51    }
52 
53    # export hex string
54    [string]ToHexString()
55    {
56        return ('{0:x8}' -f $this.IPAddress)
57    }
58}

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().

1class IPHelper
2{
3    # declare member variable
4    hidden [uint32]$IPAddress = 0
5 
6    # import from string
7    static [IPHelper]FromString([string]$InputString)
8    {
9        # split string on dots and verify length
10        $arrSplit = $InputString.Split('.')
11        if ($arrSplit.Count -ne 4)
12        {
13            throw 'Invalid IP address in input string.'
14        }
15 
16        # get octets (will throw an unhandled exception if input is out of range or non-numeric)
17        $iOctet1 = [byte]$arrSplit[0]
18        $iOctet2 = [byte]$arrSplit[1]
19        $iOctet3 = [byte]$arrSplit[2]
20        $iOctet4 = [byte]$arrSplit[3]
21 
22        # create return object
23        $objReturn = New-Object -TypeName 'IPHelper'
24 
25        # calculate IP address
26        $objReturn.IPAddress = ($iOctet1 * 0x01000000) + ($iOctet2 * 0x00010000) + ($iOctet3 * 0x00000100) + $iOctet4
27         
28        return $objReturn
29    }
30 
31    # import from integer
32    static [IPHelper]FromInt([uint32]$InputInteger)
33    {
34        # create return object
35        $objReturn = New-Object -TypeName 'IPHelper'
36 
37        # set IP address
38        $objReturn.IPAddress = $InputInteger
39 
40        return $objReturn
41    }
42 
43    # export quad-dotted string
44    [string]ToString()
45    {
46        # get the hex string
47        $strValue = $this.ToHexString()
48 
49        # slice into 2-character octets, convert back to decimal, and combine with '.'
50        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()
51    }
52 
53    # export hex string
54    [string]ToHexString()
55    {
56        return ('{0:x8}' -f $this.IPAddress)
57    }
58 
59    # calculate equal to
60    [bool]EqualTo([IPHelper]$IPToCompare)
61    {
62        return $this.IPAddress -eq $IPToCompare.IPAddress
63    }
64 
65    # calculate greater than
66    [bool]GreaterThan([IPHelper]$IPToCompare)
67    {
68        return $this.IPAddress -gt $IPToCompare.IPAddress
69    }
70 
71    # calculate greater than or equal to
72    [bool]GreaterThanOrEqual([IPHelper]$IPToCompare)
73    {
74        return $this.IPAddress -ge $IPToCompare.IPAddress
75    }
76 
77    # calculate less than
78    [bool]LessThan([IPHelper]$IPToCompare)
79    {
80        return $this.IPAddress -lt $IPToCompare.IPAddress
81    }
82 
83    # calculate less than or equal to
84    [bool]LessThanOrEqual([IPHelper]$IPToCompare)
85    {
86        return $this.IPAddress -le $IPToCompare.IPAddress
87    }
88 
89    # get next IP
90    [IPHelper]Next()
91    {
92        # check for max value
93        if ($this.IPAddress -eq [uint32]::MaxValue)
94        {
95            # roll around to all zeroes
96            return [IPHelper]::FromInt(0)
97        }
98        else
99        {
100            # add one to current IP
101            return [IPHelper]::FromInt($this.IPAddress + 1)
102        }
103    }
104 
105    # get previous IP
106    [IPHelper]Previous()
107    {
108        # check for zeroes
109        if ($this.IPAddress -eq 0)
110        {
111            # roll around to max value
112            return [IPHelper]::FromInt([uint32]::MaxValue)
113        }
114        else
115        {
116            # subtract one from current IP
117            return [IPHelper]::FromInt($this.IPAddress - 1)
118        }
119    }
120}

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:

1for ($IP = [IPHelper]::FromString('192.168.1.1'); $IP.LessThan('192.168.1.255'); $IP = $IP.Next())
2{
3    # do some stuff here... like maybe ping all these IPs?
4}

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!

Leave a Reply

Your email address will not be published. Required fields are marked *