Fun with PowerShell: IP address control for Windows forms

I've gotten pretty good at manipulating .NET with PowerShell, but I still have some annoyances.  Every once in a while I bump into a .NET control that does one of those VROOOOM maneuvers right over my head.  One such instance was the IP address controls I found on MSDN.  To be fair though I think these are more C or C# controls, but still.

http://msdn.microsoft.com/en-us/library/bb761372(VS.85).aspx

Creating an IP Address Control

Before creating an IP address control, call InitCommonControlsEx with the ICC_INTERNET_CLASSES flag set in the dwICC member of the INITCOMMONCONTROLSEX structure.

Use the CreateWindow or the CreateWindowEx function to create an IP address control. The class name for the control is WC_IPADDRESS, which is defined in Commctrl.h. No IP address control-specific styles exist; however, because this is a child control, use the WS_CHILD style as a minimum.

 

Say what?  Those full blown developer types can be cryptic at times.  They may as well have written this article in ancient Sanskrit.  Which means once again I must do it myself.  Well, almost.  This time I worked with a fellow PowerShell scripter here at OrcsWeb by the name of Jimmy Martin.  I wrote the basic frame and part of the visual styling while he worked on the guts and checks.

The basic framework to the PowerShell IP address control is the MaskedTextBox form object.

http://msdn.microsoft.com/en-us/library/system.windows.forms.maskedtextbox.aspx

It's basically a text box that you create a "mask" for.  The mask limits the type and number of characters entered into the textbox and puts in nifty things like periods to make it appear like your traditional IP address input boxes.  Said mask is set, unsurprisingly, by the mask property.

http://msdn.microsoft.com/en-us/library/system.windows.forms.maskedtextbox.mask.aspx

For the IP address control I use a mask of "990.990.990.990".  "9" allows a numerical character or space.  "0" is a numerical character only.  This format allow for single and double-digit octets, as well as your full three-digit, without requiring annoying zeroes that no one has to type anymore.  For example, you only have to enter 1.23.123.54 instead of 001.023.123.054 like you would if you used "000.000.000.000" as the mask.

Next we added a key control when the period (or decimal when you use the num pad on a keyboard) is pressed.  This key press triggers code that makes the IP address control work more like the IPv4 properties in Windows.  Since the input is just text with spaces after the digits while you are typing, the press key event simply shifts the spaces to before the digits to make it more aesthetically pleasing.

As a side note, for those not familiar with .NET events in PowerShell, you use .Add_<event>({}).  If you try <event>() PowerShell will try to use the event as a method and you'll get an error about "no such method."

Speaking of aesthetics, by default the MaskedTextBox used an underscore ("_") as the prompt character.  You can use a space (" ") by changing the PromptChar property, but it messes with the spacing.  As you type in numbers the decimals shift around.  We simply chose to use the less pretty underscore and have neater spacing versus a prettier character with bad spacing.  If anyone figures out a way to have the best of both worlds I'm all ears.

The last step of the control is to do the IP address validation when you leave the control.  This is added to the Leave event.  While I won't go through all the details of this process but it's pretty easy. First we check to make sure all the octets are 255 or less then use the .NET IP address parser to do all the grunt work.  If you don't check for less than equal to 255 before hand you get a nasty parser error otherwise the check would be super easy instead of regular easy.

http://www.orcsweb.com/blog/james/powershell-stuff-validating-an-ip-address-revisited/

In the end you can convert the text to an IP address in one of two ways.  Just add one of the following two lines to your OK, Next, Continue, whatever button you use to move on.

	$ipObj =  [System.Net.IPAddress]::parse($($MTB_IpAddress.Text -replace " ",""))
$ipAddress = $MTB_IpAddress.Text -replace " ",""

The primary difference between the two is whether you want the IP as a string or an object.  The $ipObj method is more flexible, though, as you can still call the string output using $ipObj.IPAddressToString and use it as an object if your code so requires.  The choice is yours though.  Now for the big finale!

image

 

	#IPv4Control.ps1

<#

function Size($w, $h)
{
    New-Object System.Management.Automation.Host.Size($w, $h)
}
$a = (Get-Host).UI.RawUI
$b = $a.BufferSize
$w = $a.WindowSize
$w = 50
$h = 120
if ($b.Width -lt $w) {
    $w = size 
    $a.BufferSize = size 


#>

function dumbPrompt    {
    param ($PromptText = $(throw "You must pass a text string."))
    $a = new-object -comobject wscript.shell
    $b = $a.popup($PromptText,0,"Error",0)
}

[void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.forms")
$ScriptPath = Split-Path -Path $MyInvocation.MyCommand.Path -Parent

# creates a form for input via a window
$Global:objForm = New-Object System.Windows.Forms.Form 
$objForm.BackColor = [System.Drawing.Color]::WhiteSmoke
$objForm.Icon = new-object System.Drawing.Icon("$ScriptPathavicon.ico")
$objForm.Text = "A Form"
$objForm.AutoSize = $True
$objForm.AutoSizeMode = "GrowAndShrink"
$objForm.StartPosition = "CenterScreen"
$objForm.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::Fixed3D
$objForm.Font = New-Object System.Drawing.Font("Verdana",8,[System.Drawing.FontStyle]::Regular)

# Label for IP
$LbIP = New-Object System.Windows.Forms.Label
$LbIP.Location = New-Object System.Drawing.Size(10,22) 
$LbIP.Size = New-Object System.Drawing.Size(95,20) 
$LbIP.Text = 'IP Address:'
$LbIP.Font = New-Object System.Drawing.Font("Verdana",9,[System.Drawing.FontStyle]::Bold)
$objForm.Controls.Add($LbIP)

# MaskedTextBox: IP input
$MTB_IpAddress = New-Object System.Windows.Forms.MaskedTextBox
$MTB_IpAddress.Location = New-Object System.Drawing.Size(115,20)
$MTB_IpAddress.Size = New-Object System.Drawing.Size(120,20)
$MTB_IpAddress.Mask = "990.990.990.990"
$MTB_IpAddress.Add_KeyDown({
    $event = $_
    switch ($_.KeyCode) {
        "Space" {$event.SuppressKeyPress = $True}
        "Right" {$event.SuppressKeyPress = $True}
        "Left"  {$event.SuppressKeyPress = $True}
        "Up"    {$event.SuppressKeyPress = $True}
        "Down"  {$event.SuppressKeyPress = $True}
    }
})
$MTB_IpAddress.Add_KeyPress({
    if ($_.KeyChar -eq "."){
        $IP = $MTB_IpAddress.Text.split(".")
        $IP = $IP -Replace " ", ""
        $newIP = $null #reset the variable            
        foreach ($octet in $IP) {
            if ($octet -eq "" -or $octet -eq $null) {break}
            if ([int]$octet -le 255) {
                switch ($([string]$octet).length) {
                    1 {$octet = "  $octet"}
                    2 {$octet = " $octet"}
                }
                $newIP = "$newIP+$octet"
            } else {break} #If the value is 255 or less keep it, otherwise throw it out
        }
        $MTB_IpAddress.Text = $newIP
    }
})
$MTB_IpAddress.Add_Leave({
    if ($($MTB_IpAddress.Text -replace " ","") -ne "...") {
        $IP = $MTB_IpAddress.Text.split(".")
        $IP = $IP -Replace " ", ""
        #test 1st and 4th octet for Zero values
        if ($IP[0] -ne "" -and $IP[0] -ne ""){
            if ([int]$IP[0] -eq 0) {$IP[0] = ""} #first octet was bad, start over
        }
        if ($IP[3] -ne "" -and $IP[3] -ne ""){
            if ([int]$IP[3] -eq 0) {$IP[3] = ""} #last octet was bad, reset to that
        }
        
        $newIP = $null #reset the variable
        foreach ($octet in $IP) {
            if ($octet -eq "" -or $octet -eq $null) {break}
            if ([int]$octet -le 255) {
                switch ($([string]$octet).length) {
                    1 {$octet = "  $octet"}
                    2 {$octet = " $octet"}
                }
                $newIP = "$newIP+$octet"
            } else {break} #If the value is 255 or less keep it, otherwise throw it out
        }
        $MTB_IpAddress.Text = $newIP

        $IP = $MTB_IpAddress.Text -Replace " ", ""
        try {
            [system.net.ipaddress]::parse($($IP)) | Out-Null
        }
        catch {
            dumbPrompt "Invalid IP detected, please correct!"
            $MTB_IpAddress.focus()            
        }
    }
})
$objForm.Controls.Add($MTB_IpAddress)


# Button: close launcher
$Button = New-Object System.Windows.Forms.Button
$Button.Location = New-Object System.Drawing.Size(10,60)
$Button.Size = New-Object System.Drawing.Size(150,25)
$Button.Font = New-Object System.Drawing.Font("Verdana",8,[System.Drawing.FontStyle]::Bold)
$Button.BackColor = [System.Drawing.Color]::Salmon
$Button.Text = "A Button"
$Button.add_Click({
    $ipObj =  [System.Net.IPAddress]::parse($($MTB_IpAddress.Text -replace " ",""))
    $ipAddress = $MTB_IpAddress.Text -replace " ",""
    write-host "$($ipObj.IPAddressToString)"
    $objForm.Close()
    $objForm.Dispose()
})
$objForm.Controls.Add($Button)

#ToolTip: close launder
$ToolTip = New-Object System.Windows.Forms.ToolTip
$ToolTip.BackColor = [System.Drawing.Color]::LightGoldenrodYellow
#$ToolTip.IsBalloon = $true
$ToolTip.InitialDelay = 500
$ToolTip.ReshowDelay = 500
$ToolTip.SetToolTip($Button, "A ToolTip") 

# Activates/draws the form.
$objForm.Add_Shown({$objForm.Activate()})
[void] $objForm.ShowDialog()

 

 

#James Kehr
Get-Member $OW | ?{$_.title -eq "System Administrator"`
-and $_.certification -contains 'MCITP:SA 2008, MCSE 2000, MCDST, Network+, A+'}

New-Variable -name company -value 'ORCS Web, Inc.' -description 'www.orcsweb.com | 1.888.313.9421'

blog comments powered by Disqus