Creating a GUI Natively for your PowerShell tools using .NET methods

Posted on Updated on

I know, scary title, Right!?  Trust me, it isn’t so bad!

My standard methodology around the office is to find a common problem that I can solve using a repeatable method,  I then turn this into a tool I give to my help desk or clients and move on to the next interesting problem.

While you or I might be comfortable in a console or digging deep into systems settings and can turn a PC with an issue in practically no time, others on our team or that we support might want the process to be more approachable, and have more automation.  This also means, to an extent, that it should be made into a tool.

So, lets say in your environment you’ve located an issue (for the sake of this example, computers not responding to ping indicating some problem) and need a mechanism to test whether a machine is connected to the network using a Ping operation, we could do this with just an easy command line entry, but lets build a full graphical tool around this that anybody could use, in order to teach the procedure.

Alright, here we go: a simple ping testing tool.

$ComputerName = read-host "Enter Computer Name to test:"</code>
if (Test-Connection $ComputerName -quiet -Count 2){
Write-Host -ForegroundColor Green "Computer $ComputerName has network connection"</code>
}
Else{
Write-Host -ForegroundColor Red "Computer $ComputerName does not have network connection"}

And to test it:

Cool, it works!
Cool, it works!

So we know our base code is working.  Now to create the outline of the GUI we want.

First and foremost, in order to have access to the Systems.Windows.Forms .NET elements we need to draw our GUI, we have to load the assemblies.  This is done with the following two lines, added to the top of our script.

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") 

Void is a .NET parameter (sort of) that tells the method here to do the operation but be quiet about it

Next, we will define the basic form onto which our application will be built, calling it $form, and then setting properties for its title (via the .Text property), size and position on screen.

#begin to draw forms
$Form = New-Object System.Windows.Forms.Form
$Form.Text = "Computer Pinging Tool"
$Form.Size = New-Object System.Drawing.Size(300,150)
$Form.StartPosition = "CenterScreen"

One of the cool things you can do with the System.Windows.Forms object is to modify it’s KeyPreview property to True and then add listeners for certain key presses to have your form respond to them. Basically if you enable KeyPreview, your form itself will intecept a key press and can do things with it before the control that is selected gets the press. So instead of the user having to click the X button or click enter, yowe can tell the form to do something when the user hits Escape or Enter instead.

With that in mind, lets add a hook into both the Enter and Escape keys to make them function.

$Form.KeyPreview = $True
$Form.Add_KeyDown({if ($_.KeyCode -eq "Enter")
{$x=$ListBox.SelectedItem;$Form.Close()}})
$Form.Add_KeyDown({if ($_.KeyCode -eq "Escape")
{$Form.Close()}})

And now, lets comment block out the Ping section of the script (we’ll also use #region and #endregion to allow us to collapse away that block for the time being) and add the following lines to the bottom to display our form.

#Show form
$Form.Topmost = $True
$Form.Add_Shown({$Form.Activate()})
[void] $Form.ShowDialog()

If you’ve been following along from home, you should have something similar to this.

pingtool_02

And now lets give it a try!

Mmmm, tasty progress.
Mmmm, tasty progress.

Ah, the sweet taste of progress.  We now have a form and some code which works. Lets add a box where a user can specify the computer name to test, and then a button to start the test.

We’ll be using the Systems.Windows.Forms.Label (Abbreviated as simply Forms.whatever from now on), Forms. TextBox and Forms.Button controls to add the rest of our app for now.  While I’m here, if you’d like a listing of all of the available other .net controls you can make use of, check out this link: http://msdn.microsoft.com/en-us/library/system.windows.forms.aspx

First, lets add a label (a field of uneditable text), in which we will describe what this tool will do.  To start, instantiate a new System.Windows.Forms object of type .label, and then we’ll set the location, size and text properties.  Finally, we’ll add the control to our form using the .Add() method, as you’ll see below.  Add the following text above the commented out Actual Code region.

$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Size(5,5)
$label.Size = New-Object System.Drawing.Size(240,30)
$label.Text = "Type any computer name to test if it is on the network and can respond to ping"
$Form.Controls.Add($label)

Some notes about this.  Location here is given in (x,y) with distances being pixels away from the upper left hand corner of the form.  Using this method of building a GUI from scratch, it is not uncommon to spend some time fiddling with the sizing by tweaking values and executing, back and forth.

Note the #regions used to make editing our code a bit cleaner
Note the #regions used to make editing our code a bit cleaner

Lets save our script and see what happens!

We are making a GUI interface using Visual Basi--er, PowerShell!
We are making a GUI interface using Visual Basi–er, PowerShell!

Alright, next, to throw a textbox on there and add a button to begin the ping test, add the following lines in the ‘#region begin to draw forms’.

$textbox = New-Object System.Windows.Forms.TextBox
$textbox.Location = New-Object System.Drawing.Size(5,40)
$textbox.Size = New-Object System.Drawing.Size(120,20)
#$textbox.Text = "Select source PC:"
$Form.Controls.Add($textbox)

$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(140,38)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
$OKButton.Add_Click($ping_computer_click)
$Form.Controls.Add($OKButton)

$result_label = New-Object System.Windows.Forms.label
$result_label.Location = New-Object System.Drawing.Size(5,65)
$result_label.Size = New-Object System.Drawing.Size(240,30)
$result_label.Text = "Results will be listed here"
$Form.Controls.Add($result_label)

At this point you may be noticing a whole lot of forms form forms.  I know, there is a lot of retyping the same things.  In fact, this whole procedure is begging to be made into a simple New-Control function, but I haven’t had time to hash it out yet, maybe in a future post!  Also, one thing I want to draw attention to is the $OKButton.Add_Click; specifying this property will associate the contents of the $ping_computer_click variable (currently empty) as a function to execute when the button is clicked.  We’ll go over that in a future post!

Before heading out, lets see what we have thus far.

Seeing the UI come together is such a satisfying feeling
Seeing the UI come together is such a satisfying feeling

Alright, next time we reconvene, we’ll link our pinging function from earlier into this tool, and see if it works!

About these ads

6 thoughts on “Creating a GUI Natively for your PowerShell tools using .NET methods

    Kenny Baldwin said:
    October 25, 2013 at 12:25 am

    Great post Stephen! Like I mentioned at happy hour, I always like to use custom icons with my forms. This is easy to embed in a Posh Script, you just need to pass the icon image’s converted Base64 string to the .Icon property of the Form object (http://webcodertools.com/imagetobase64converter makes it easy):

    $Form.Icon = ([System.Drawing.Icon](New-Object System.Drawing.Icon((New-Object System.IO.MemoryStream(($$ = [System.Convert]::FromBase64String(ConvertedStringHere)),0,$$.Length)))))

    So if I wanted a XenDesktop 7 icon (25 x 25 8bit PNG), I’d set $Form.Icon to a Base64 string of:

    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

    Thanks again for the beer! Cheers!

    Jack Levy said:
    October 30, 2013 at 9:09 pm

    Great article, I think I am one step ahead…

    [void] [System.Reflection.Assembly]::LoadWithPartialName(“System.Drawing”)
    [void] [System.Reflection.Assembly]::LoadWithPartialName(“System.Windows.Forms”)

    #begin to draw forms
    $Form = New-Object System.Windows.Forms.Form
    $Form.Text = “Computer Pinging Tool”
    $Form.Size = New-Object System.Drawing.Size(300,150)
    $Form.StartPosition = “CenterScreen”

    $Form.KeyPreview = $True
    $Form.Add_KeyDown({if ($_.KeyCode -eq “Enter”)
    {ping_computer}})
    $Form.Add_KeyDown({if ($_.KeyCode -eq “Escape”)
    {$Form.Close()}})

    $label = New-Object System.Windows.Forms.Label
    $label.Location = New-Object System.Drawing.Size(5,5)
    $label.Size = New-Object System.Drawing.Size(240,30)
    $label.Text = “Enter a PC name or IP to test if it can respond to ping. Click ‘OK’ or hit ENTER to run…”
    $Form.Controls.Add($label)

    $textbox = New-Object System.Windows.Forms.TextBox
    $textbox.Location = New-Object System.Drawing.Size(5,40)
    $textbox.Size = New-Object System.Drawing.Size(120,20)
    $textbox.Text = “”
    $Form.Controls.Add($textbox)

    $OKButton = New-Object System.Windows.Forms.Button
    $OKButton.Location = New-Object System.Drawing.Size(140,38)
    $OKButton.Size = New-Object System.Drawing.Size(75,23)
    $OKButton.Text = “OK”
    $OKButton.Add_Click({ping_computer_click})
    $Form.Controls.Add($OKButton)

    $result_label = New-Object System.Windows.Forms.label
    $result_label.Location = New-Object System.Drawing.Size(5,65)
    $result_label.Size = New-Object System.Drawing.Size(240,30)
    $result_label.Text = “Results:”
    $Form.Controls.Add($result_label)

    function ping_computer {
    #ping the comptuer from $textbox.Text
    $computername=$textbox.Text
    if ($computername -ne “”) {
    if (Test-Connection $ComputerName -quiet -Count 2){
    $result_label_results = New-Object System.Windows.Forms.label
    $result_label_results.Location = New-Object System.Drawing.Size(5,95)
    $result_label_results.Size = New-Object System.Drawing.Size(275,30)
    $result_label_results.Text = “$ComputerName has network connection”
    $Form.Controls.Add($result_label_results)
    }
    else{
    $result_label_results = New-Object System.Windows.Forms.label
    $result_label_results.Location = New-Object System.Drawing.Size(5,95)
    $result_label_results.Size = New-Object System.Drawing.Size(275,30)
    $result_label_results.Text = “$ComputerName does not have network connection”
    $Form.Controls.Add($result_label_results)

    }
    }
    else{
    $result_label_results = New-Object System.Windows.Forms.label
    $result_label_results.Location = New-Object System.Drawing.Size(5,95)
    $result_label_results.Size = New-Object System.Drawing.Size(240,30)
    $result_label_results.Text = “No comptuer name entered”
    $Form.Controls.Add($result_label_results)
    }

    }

    #Show form
    $Form.Topmost = $True
    $Form.Add_Shown({$Form.Activate()})
    [void] $Form.ShowDialog()

    But now how do I allow for the utility to be used more than once in the same session? I have to close it out and then type a new computer name to run it against a new machine….

      FoxDeploy responded:
      November 1, 2013 at 9:04 pm

      First off, making this into a function is exactly the way it should be done. In hindsight, boy was I sleep-deprived to leave that out and instead call Invoke-Expression on a variable. Silly me!

      Good catch!

      As for whats going on with your scrip; here’s the problem: you’ve already got a perfectly good control on your form called $result_label. And yet when you’re running your script, you add a new control on top of the old one, called $results_label_results. The reason this is failing the second time is that you can only add a control of a given name to a form once. If you want to continually write new forms on top of the old, you COULD using an incrementing name, like $results_$i, and then have $i++ at the top of your function. But that would be really weird, and kind of hard.

      Here is an easier method. Just change the text of your label. Here is your original code with just a few edits. I’ve removed the definition of $results_label_result and all of its sizing, etc. If you give it a try, it should work!

      function ping_computer {
      #ping the comptuer from $textbox.Text
      $computername=$textbox.Text
      write-host "pinging $computername"
      if ($computername -ne “”) {
      if (Test-Connection $ComputerName -quiet -Count 2){

      $result_label.Text = “$ComputerName has network connection”
      write-host “$ComputerName has network connection”

      }
      else{
      write-host “$ComputerName does not have network connection”
      $result_label.Text = “$ComputerName does not have network connection”
      }
      }
      else{
      $result_label.Text = “No comptuer name entered”
      }
      }

        naveen basati said:
        November 24, 2014 at 4:17 am

        The code is working really fine. But if I click on OK button i am getting this error

        The term ping_computer_click : The term ‘ping_computer_click’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

        FoxDeploy responded:
        November 24, 2014 at 1:07 pm

        Double check the function names, I think I made a mistake in my own code. Good catch!

    naveen basati said:
    November 24, 2014 at 1:42 pm

    Thanks ,
    I have changed the function name as ping_computer_click. Its working fine now. I want to add a browse button to load multiple ips and pinging it back with output to grid view. Your suggesstions are appreciable!!!!!!!!!!!!!!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s