Creating a PowerShell GUI (Part 12)

If you would like to read the other parts in this article series please go to:

So far in this article series, we have created a script that lets the user pick a virtual machine, and then displays information about that VM. That’s all well and good, but the script would be much more useful if it could be used to actually do something other than passively displaying information. In this article, we will add some functionality to the script.

My goal in this article is to create a toggle switch that can be used to start or stop a virtual machine. Now there is no such thing as a toggle switch object in PowerShell, so we have to be a little bit creative. My plan is to create a pair of buttons – one to start a VM, another to stop the VM. I can then use some simple logic to control which button is displayed. If the selected VM is currently running, I will display the Stop button. If the VM is currently stopped, I will display the Start button.

So the first thing that I want to do is to create a pair of buttons. This is really simple to do. As you may recall, the script already contains four button objects. I’m going to create two more. Here is a sample code block:

# Define Button 5 - This button will be used to start a virtual machine.
$Button5 = New-Object System.Windows.Forms.Button
$Button5.Location = New-Object System.Drawing.Size(140,260)
$Button5.Size = New-Object System.Drawing.Size(150,30)
$Button5.BackColor ="Green"
$Button5.ForeColor ="White"
$Button5.Text = "Start VM"
$Button5.Add_Click({$Form.Close()})
# Define Button 6 - This button shuts down a virtual machine.
$Button6 = New-Object System.Windows.Forms.Button
$Button6.Location = New-Object System.Drawing.Size(320,260)
$Button6.Size = New-Object System.Drawing.Size(150,30)
$Button6.BackColor ="Red"
$Button6.ForeColor ="White"
$Button6.Text = "Stop VM"
$Button6.Add_Click({$Form.Close()})

Right now these buttons don’t do anything, but I’m about to change that. Before I do, I want to show you what the buttons look like if I add them to the form. Notice in Figure A, that I have colored the buttons red and green, with white text. I didn’t have to color the buttons, but I went ahead and used color so that you could see how the BackColor and ForeColor attributes work. You can see how these attributes are configured in the code block above.

Figure A

Image

I have created a Start and Stop button.

So now that the buttons have been created, let’s build some logic that will display either one button or the other, depending on whether or not the VM is running. This involves doing three things.

First, we need to create a pair of If statements that add the buttons to the interface depending on the state of the virtual machine. As you may recall, we previously created a function called Display-VMInfo that displays all of the virtual machine information shown in the figure above. We can easily add the two if statements to this function. Here is the portion of the function that displays the screen contents:

#Display Screen Contents
$Form.Controls.Add($Label)
$Form.Controls.Add($Label4)
$Form.Controls.Add($Label5)
$Form.Controls.Add($Label6)
$Form.Controls.Add($Label7)
$Form.Controls.Add($Label3)
$Form.Controls.Add($Button3)
$Form.Controls.Add($Button4)
If ($State -eq 'Off')
{
$Form.Controls.Add($Button5)
}
If ($State -EQ 'Running')
{
$Form.Controls.Add($Button6)
}
}

As you can see in the code block above, if the virtual machine is off, then Button 5 (Start VM) will be displayed. Likewise, if the virtual machine is running then Button 6 (Stop VM) will be displayed.

The second thing that we need to do is to add some code to remove the buttons from the interface wherever it is appropriate to do so. For example, the Return-MainScreen function contains code to remove various graphical elements from the screen. We need to add lines to remove Button5 and Button6. You can see what that code looks like below:

# ---------- Button Click Actions ------
#Define Return Button Click Function
Function Return-MainScreen
{
$Form.Controls.Remove($Label)
$Form.Controls.Remove($Label3)
$Form.Controls.Remove($Label4)
$Form.Controls.Remove($Label5)
$Form.Controls.Remove($Label6)
$Form.Controls.Remove($Label7)
$Form.Controls.Remove($Button3)
$Form.Controls.Remove($Button4)
$Form.Controls.Remove($Button5)
$Form.Controls.Remove($Button6)
$Form.Refresh()
Display-MainScreen
}

Finally, we should probably change the button location for button 6 so that the button displays in the same place on the screen as button 5. Remember, the two buttons will never be on the screen at the same time. Instead, the goal is to create the illusion of a toggle switch that we can use to turn the VM on or off. Here is what the location command for Button 6 should look like:

$Button6.Location = New-Object System.Drawing.Size(140,260)

So now let’s take a look at how the script works. If you look at Figure B, you can see that the VM is currently powered off, and the GUI presents an option to start the VM.

Figure B

Image

The GUI provides the option to start the VM.

Likewise, Figure C shows an example of a VM that is running. Because the VM is currently powered on, there is a button that we can use to stop the VM.

Figure C

Image

The GUI provides an option to stop a running virtual machine.

As it stands right now, the buttons don’t actually do anything. We need to change the button action for the Start and Stop button to point to a function that can change the VM state. There are a few different ways of handling this. We could create a single function that re-checks the state of the VM, and then uses If statements to either start or stop the VM. The code used for doing so would be very similar to the code used to display the Start and Stop buttons.

Another, slightly simpler approach would be to create two different functions, one to start the VM, and one to stop the VM. That way, each of the two buttons calls a very simple, dedicated function.

The easiest method of all is to just build the Start-VM and Stop-VM cmdlet directly into the button click actions. In order to do so, there is one change that must be made to the function. Right now, the Display-VMInfo function contains the following line of code, which assigns the virtual machine name to a variable called $Name:

$Name = $SelectedVM.VMName

We need to make $Name a global variable so that it will be recognized outside of the function. Remember, out button click action is defined in the script’s main body, where $Name is currently unrecognized. We can change $Name into a global variable by modifying the variable assignment to look like this:

$Global:Name = $SelectedVM.VMName

Now, we can set the button click actions to:

$Button5.Add_Click({Start-VM $Name})

And

$Button6.Add_Click({Stop-VM $Name})

This technique works pretty well, but it’s not quite perfect, at least not as written. As it stands right now, clicking the Start VM button will indeed start the VM, but the display is not refreshed to reflect the new status. Clicking Stop-VM causes the PowerShell window to stop and wait for confirmation before shutting down the virtual machine. Hence, the shutdown process is not truly automated. So here is how we fix these problems.

We can fix the shutdown problem by appending the -Force parameter to the Stop-VM command. We can then fix the screen refresh issue by appending the pipe symbol to both of the click actions, and then calling the Display-VMInfo function. When doing so, be sure to pass the $Name variable to the function. You will also need to add instructions to the beginning of the function to remove buttons 5 and 6 so that the buttons will display correctly.

So here is what the button click actions look like now:

$Button5.Add_Click({Start-VM $Name | Display-VMInfo $Name})

And

$Button6.Add_Click({Stop-VM $Name -Force | Display-VMInfo $Name})

So with that said, here is the code in its entirety:

#Load Assemblies
[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") | Out-Null
$net = New-Object -ComObject Wscript.Network

# Display the Main Screen
Function Display-MainScreen
{
$Form.Controls.Add($ListBox1)
$Form.Controls.Add($Button1)
$Form.Controls.Add($Button2)
$Form.Controls.Add($Label2)
}

# ---------- Button Click Actions ------

#Define Return Button Click Function

Function Return-MainScreen
{
$Form.Controls.Remove($Label)
$Form.Controls.Remove($Label3)
$Form.Controls.Remove($Label4)
$Form.Controls.Remove($Label5)
$Form.Controls.Remove($Label6)
$Form.Controls.Remove($Label7)
$Form.Controls.Remove($Button3)
$Form.Controls.Remove($Button4)
$Form.Controls.Remove($Button5)
$Form.Controls.Remove($Button6)
$Form.Refresh()
Display-MainScreen
}

 

#Define OK Button Click Function

Function Display-VMInfo($ChosenItem)
{
#Clear the window
$Form.Controls.Remove($ListBox1)
$Form.Controls.Remove($Label2)
$Form.Controls.Remove($Button1)
$Form.Controls.Remove($Button2)
$Form.Controls.Remove($Button5)
$Form.Controls.Remove($Button6)
$Form.Refresh()

#Create Output

Add-Type -AssemblyName System.Windows.Forms
# Get Virtual Machine Information
$SelectedVM = Get-VM $ChosenItem
$Global:Name = $SelectedVM.VMName
$State = $SelectedVM.State
$RunTime = $SelectedVM.UpTime
$Memory = $SelectedVM.MemoryAssigned
$CPU = $SelectedVM.ProcessorCount

#Create text

$Label.Text = 'Virtual Machine Name: ' + $Name
$Label4.Text = 'VM State: ' + $State
$Label5.Text = 'VM Up Time: ' + $RunTime
$Label6.Text = 'Memory Assigned: ' + $Memory
$Label7.Text = 'Virtual CPUs Assigned :' + $CPU

#Display Screen Contents

$Form.Controls.Add($Label)
$Form.Controls.Add($Label4)
$Form.Controls.Add($Label5)
$Form.Controls.Add($Label6)
$Form.Controls.Add($Label7)
$Form.Controls.Add($Label3)
$Form.Controls.Add($Button3)
$Form.Controls.Add($Button4)
If ($State -eq 'Off')
{
$Form.Controls.Add($Button5)
}
If ($State -EQ 'Running')
{
$Form.Controls.Add($Button6)
}
}

 

 #—— Define All GUI Objects——-

# Define Label – This is a text box object that will display VM data

$Label = New-Object System.Windows.Forms.Label
$Label.AutoSize = $False
$Label.Location = new-object System.Drawing.Size(50,50)
$Label.Size = New-Object System.Drawing.Size(200,20)
$Label.ForeColor = "Black"
$label.BackColor = 'White'

# Define Label2 – The Please Make a Selection Text

$Label2 = New-Object System.Windows.Forms.Label
$Label2.AutoSize = $True
$Label2.Location = new-object System.Drawing.Size(20,50)
$Label2.ForeColor = "DarkBlue"
$Label2.Text = "Please select a virtual machine from the list."

# Define Label3 – The This is Your Selected Virtual Machine Text

$Label3 = New-Object System.Windows.Forms.Label
$Label3.AutoSize = $True
$Label3.Location = new-object System.Drawing.Size(50,30)
$Label3.ForeColor = "DarkBlue"
$Label3.Text = "Your selected virtual machine:"

# Define Label4 – The This is the VM State label

$Label4 = New-Object System.Windows.Forms.Label
$Label4.AutoSize = $False
$Label4.Location = new-object System.Drawing.Size(50,70)
$Label4.Size = New-Object System.Drawing.Size(200,20)
$Label4.ForeColor = "Black"
$Label4.BackColor = 'White'

# Define Label5 – The This is the VM State label

$Label5 = New-Object System.Windows.Forms.Label
$Label5.AutoSize = $False
$Label5.Location = new-object System.Drawing.Size(50,90)
$Label5.Size = New-Object System.Drawing.Size(200,20)
$Label5.ForeColor = "Black"
$Label5.BackColor = "White"

# Define Label6 – The This is the VM State label

$Label6 = New-Object System.Windows.Forms.Label
$Label6.AutoSize = $False
$Label6.Location = new-object System.Drawing.Size(50,110)
$Label6.Size = New-Object System.Drawing.Size(200,20)
$Label6.ForeColor = "Black"
$Label6.BackColor = 'White'

# Define Label7 – The This is the VM State label

$Label7 = New-Object System.Windows.Forms.Label
$Label7.AutoSize = $False
$Label7.Location = new-object System.Drawing.Size(50,130)
$Label7.Size = New-Object System.Drawing.Size(200,20)
$Label7.ForeColor = "Black"
$Label7.BackColor = 'White'

# Define List Box – This will display the virtual machines that can be selected

$ListBox1 = New-Object System.Windows.Forms.ListBox 
$ListBox1.Location = New-Object System.Drawing.Size(20,80) 
$ListBox1.Size = New-Object System.Drawing.Size(260,20) 
$ListBox1.Height = 80

# This code populates the list box with virtual machine names

$VirtualMachines = Get-VM
ForEach ($VM in $VirtualMachines)
{
[void] $ListBox1.Items.Add($VM.Name)
}

# Define Button 1 – This is the selection screen’s OK button

$Button1 = new-object System.Windows.Forms.Button
$Button1.Location = new-object System.Drawing.Size(20,170) 
$Button1.Size = new-object System.Drawing.Size(70,30)
$Button1.BackColor ="LightGray"
$Button1.Text = "OK"
$Button1.Add_Click({$ChosenItem=$ListBox1.SelectedItem;Display-VMInfo $ChosenItem})

# Define Button 2 – This is the selection screen’s Cancel button

$Button2 = New-Object System.Windows.Forms.Button
$Button2.Location = New-Object System.Drawing.Size(120,170)
$Button2.Size = New-Object System.Drawing.Size(70,30)
$Button2.BackColor ="LightGray"
$Button2.Text = "Cancel"
$Button2.Add_Click({$Form.Close()})

# Define Button 3 – This is the Return to Main Screen button

$Button3 = New-Object System.Windows.Forms.Button
$Button3.Location = New-Object System.Drawing.Size(50,220)
$Button3.Size = New-Object System.Drawing.Size(70,30)
$Button3.BackColor ="LightGray"
$Button3.Text = "Return"
$Button3.Add_Click({Return-MainScreen})

# Define Button 4 – This button doesn’t do anything yet, but we will use it eventually.

$Button4 = New-Object System.Windows.Forms.Button
$Button4.Location = New-Object System.Drawing.Size(140,220)
$Button4.Size = New-Object System.Drawing.Size(150,30)
$Button4.BackColor ="LightGray"
$Button4.Text = "Reserved for Future Use"
$Button4.Add_Click({$Form.Close()})

# Define Button 5 – This button will be used to start a virtual machine.

$Button5 = New-Object System.Windows.Forms.Button
$Button5.Location = New-Object System.Drawing.Size(140,260)
$Button5.Size = New-Object System.Drawing.Size(150,30)
$Button5.BackColor ="Green"
$Button5.ForeColor ="White"
$Button5.Text = "Start VM"
$Button5.Add_Click({Start-VM $Name | Display-VMInfo $Name})

# Define Button 6 – This button shuts down a virtual machine.

$Button6 = New-Object System.Windows.Forms.Button
$Button6.Location = New-Object System.Drawing.Size(140,260)
$Button6.Size = New-Object System.Drawing.Size(150,30)
$Button6.BackColor ="Red"
$Button6.ForeColor ="White"
$Button6.Text = "Stop VM"
$Button6.Add_Click({Stop-VM $Name -Force | Display-VMInfo $Name})
# -------- This is the end of the object definition section ------

 

# —–Draw the empty form—-

$Form = New-Object System.Windows.Forms.Form
$Form.width = 525
$Form.height = 350
$Form.BackColor = "lightblue"
$Form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::Fixed3D
$Form.Text = "Hyper-V Virtual Machines"
$Form.maximumsize = New-Object System.Drawing.Size(525,350)
$Form.startposition = "centerscreen"
$Form.KeyPreview = $True
$Form.Add_KeyDown({if ($_.KeyCode -eq "Enter") {}})
$Form.Add_KeyDown({if ($_.KeyCode -eq "Escape") 
{$Form.Close()}})

#—-Populate the form—-

Display-MainScreen
$Form.Add_Shown({$Form.Activate()})
$Form.ShowDialog()

Conclusion

In this article, I have shown you how to add a button that toggles the virtual machine state. In the next article in the series, I want to show you how use a script to modify the virtual machine’s memory configuration.

About The Author

3 thoughts on “Creating a PowerShell GUI (Part 12)”

  1. Hi Brien,
    I spent my spare time on my annual leave in reading through your guides and now I am using the techniques you taught me to automate a lot of manual tasks in my ICT environment. It’s really rewarding & so I would like to say thank you for publishing this.
    Your approach is amazing. I liked how you used a relevant example with techniques that can be recycled and used in many different ways & you explained everything so well.

    By the way, what was the explanation for the line of code after loading the assemblies?
    $net = New-Object -ComObject Wscript.Network

    Do you have (or know) a similar series on creating programs in C# by chance?

  2. Thank you for the kind words Joel. It took a lot of work to put together this article series, so I am so glad to hear that someone found it to be useful. You really made my day. Thank you.

    I have to confess that I am stumped by the $Net line of code. It has been a while since I wrote the series, but I probably started to do things one way and then decided on a different technique and accidentally left a line in place from the old code. The line assigns a variable that never gets used, so it is probably OK to remove the line.

    I don’t know of any similar series on C#. I don’t really do very much in C# so it is very possible that a resource exists that I haven’t seen.

    Thanks again,
    Brien

  3. Hi Brien/Joel,

    Look Like code “$net = New-Object -ComObject Wscript.Network” is use to map the network drive. I got the below example when I am searching for the code on internet.

    $cred = get-credential
    $domain = “t2.local”
    $user = $cred.UserName
    $pwd = $cred.Password
    $net = New-Object -com WScript.Network
    $drive = “J:”
    $path = “\\server\shared”
    #$net.RemoveNetworkDrive($drive)
    $net.mapnetworkdrive ($drive, $path, “true”, $user, $pwd)
    https://www.tek-tips.com/viewthread.cfm?qid=1512361

Leave a Comment

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

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Scroll to Top