Building the Foundation for Windows Presentation
Before we get into today's topic, a few housekeeping items. In the last few articles, I took advantage of the attachment feature. I knew I couldn't send .ps1 files, so I attached a zip file. Unfortunately, if you have a Gmail address, the emails still bounced. It appears that Gmail is looking at the zip file contents and bouncing the email because the attachment contains forbidden files. If you missed them, you can find the articles on the archive page, including the zip file attachments. For now, I will stop using the attachment feature and will look for a better long-term solution. I also encourage you to ensure that the email addresses jhicks@jdhitsolutions.com and behind@jdhitsolutions.com are on your safe sender list.
In the recent articles that shared my toolmaking experiences building an archive summary/search solution for this newsletter, I showed a simple script that generated a GUI which allowed me to edit preview text. I created this script using PowerShell and Windows Presentation Foundation (WPF). Creating graphical PowerShell scripts always seems like a popular topic. WPF was built for .NET developers so it can be a bit daunting for PowerShell scripters. But I think it is worth the effort. In this new series of articles, I want to show you how to build a simple WPF GUI using PowerShell.
WPF is a .NET class library for building GUI applications. You might want to have a graphical front-end for a user to input data for a PowerShell command. Or maybe you want to present PowerShell-generated data in a user-friendly, graphical format. At its core, such a tool is running PowerShell code. The best practice is to write your PowerShell code first and confirm that it runs. Then build the GUI on top of it. This is the proper design pattern.
You may have seen other graphical scripts that use Windows Forms (WinForms). Window Forms shares some similarities with WPF, but WPF is the newer technology and is the preferred choice. The biggest difference is that WinForms doesn't scale well to different resolutions. WinForms worked better when we had older monitors and 1024x768 was considered a high resolution. WPF automatically scales to different resolutions and simply looks better.
Requirements
WPF, as the name implies, requires a Windows platform. It is supported in PowerShell 7, but not on Linux or Mac. At the beginning of your script, you might need to load assemblies.
if ($IsWindows -OR $PSEdition -eq 'desktop') {
Try {
Add-Type -AssemblyName PresentationFramework -ErrorAction Stop
Add-Type -AssemblyName PresentationCore -ErrorAction Stop
}
Catch {
#Failsafe error handling
Throw $_
Return
}
} else {
Write-Warning 'This requires Windows PowerShell or PowerShell Core on Windows.'
#Bail out
Return
}
With these assemblies loaded, you should be able to begin building your WPF GUI. Traditionally, WPF makes it easier for developers to separate the code for generating the GUI from the code that runs the GUI. This is a good practice to follow. You can create a XAML file that defines the GUI and then use PowerShell to load and run the GUI. However, I'm going to show you how to build a GUI using PowerShell code alone. This is a bit more challenging, but I think it is worth the effort because it forces you to learn more about the WPF classes and how they work. Don't worry, we'll eventually look at using XAML.
Window
WPF GUIs are built on the idea of layers. At the top is a window. This is the main layer for your GUI.
You may see this referred to as a container, but don't confuse this with things like Docker containers. WPF "containers" are more like nested layers that hold controls. This should become clearer as we go along.
You can set properties for the window such as title and size
$window = New-Object System.Windows.Window
$window.Title = 'WPF Window Demo'
$window.Height = 350
$window.Width = 500
This is a very rich object. You can pipe it to Get-Member
or use my Get-TypeMember
command.

You might want to specify the startup location.
PS C:\> Get-TypeMember System.Windows.Window -MemberName WindowStartupLocation
Type: System.Windows.Window
Name MemberType ResultType IsStatic IsEnum
---- ---------- ---------- -------- ------
WindowStartupLocation Property WindowStartupLocation True
The ResultType
tells you what type of object or value the property is expecting.

Based on this, I can add this line to my window definition.
$window.WindowStartupLocation = 'CenterScreen'
As an alternative, you can define properties as a hashtable.
$window = [System.Windows.Window]@{
Title = 'WPF Window Demo'
Height = 350
Width = 500
WindowStartupLocation = 'CenterScreen'
}
To display the window, you should call the ShowDialog()
method.
$window.ShowDialog()

This will block your PowerShell prompt until the window is closed. I haven't added any buttons or code to close the window, but I can use the x
in the upper right corner.
There is also a Show()
method which will also display the window. This method will not block your PowerShell prompt. But it also will disconnect the window from your PowerShell session. If you don't define any controls to close the window, you'll have to kill your PowerShell session to close the window. I always use ShowDialog()
. When we get to advanced stuff, I'll show you how to run the WPF GUI in a separate runspace.
Adding A Button
The empty form isn't very useful, so let's add a control. You'll use buttons a lot in WPF so let's start there.
$btnTest = New-Object System.Windows.Controls.Button
$btnTest.Content = 'Click Me'
$btnTest.Width = 75
$btnTest.Height = 25
You will want to define the button size. I generally find these dimensions work well. In order to display the button, it needs to be added to the window.
$Window.AddChild($btnTest)
I'll run the code I have thus far.

I can click the button all I want, but nothing happens. That's because I need to add code to handle the button click event.
Button Code
In the world of WPF, things are event-driven. When you click a button, an event is raised. YTou can discover the events with Get-Member
or Get-TypeMember
.
PS C:\> Get-TypeMember System.Windows.controls.button -MemberType Event
Type: System.Windows.Controls.Button
Name MemberType ResultType IsStatic IsEnum
---- ---------- ---------- -------- ------
Click Event
ContextMenuClosing Event
ContextMenuOpening Event
DataContextChanged Event
DragEnter Event
DragLeave Event
DragOver Event
Drop Event
...
You can write code to handle the event. The code will be a PowerShell script block. The hidden Add_Click()
method is used to add the script block to the button. Click
is the event name and I'm adding a script block to handle the event.
$btnTest.Add_Click({
#change the button text
$btnTest.Content = 'I was clicked'
#modify other controls in the script
$window.Background = 'LightBlue'
#do something in PowerShell
Write-Host "The button was clicked!"
#you won't see pipeline output in the console
Get-Service bits
#but this will happen
Get-Service bits | Out-File c:\temp\bits.txt
})
You can work with other elements in your code. In this example I am changing the button text and the window background color. You can interact with the PowerShell session, but be aware that pipeline output is blocked. In this example, I'll see the Write-Host
output, but not the Get-Service
output. However, I can still redirect the output to a file.

I moved the form to the side to capture everything in a single screen shot. Even though I don't see it, the Get-Service
command ran and created a file.
Get-Content C:\temp\bits.txt
Status Name DisplayName
------ ---- -----------
Running bits Background Intelligent Transfer Servi…
Closing the Window
The close the window, you can use the Close()
method. Typically, you would define this in a button click event.
$btnTest.Add_Click({
$window.close()
Write-Host "The button was clicked and the form closed"
})
Here's a complete demo script with a few other examples of integrating PowerShell into the WPF form.
if ($IsWindows -OR $PSEdition -eq 'desktop') {
Try {
Add-Type -AssemblyName PresentationFramework -ErrorAction Stop
Add-Type -AssemblyName PresentationCore -ErrorAction Stop
}
Catch {
#Failsafe error handling
Throw $_
Return
}
}
else {
Write-Warning 'This requires Windows PowerShell or PowerShell Core on Windows.'
#Bail out
Return
}
$window = New-Object System.Windows.Window
$window.Title = "WPF Window Demo PS $($PSVersionTable.PSVersion)"
$window.Height = 350
$window.Width = 500
$window.WindowStartupLocation = 'CenterScreen'
$btnTest = New-Object System.Windows.Controls.Button
$btnTest.Content = "Click Me, $env:UserName"
$btnTest.Width = 75
$btnTest.Height = 25
#set focus on the button
[void]$btnTest.Focus()
$btnTest.Add_Click({
$window.close()
Write-Host "The button was clicked and the form closed"
})
$window.AddChild($btnTest)
[void]$window.ShowDialog()
Some methods will write a Boolean result to the pipeline which is why I am casting the output as [void]
.

Because the button has focus, all I need to do is press Enter to click the button and close the window. The other part of the click event writes a host message to the console. This is more for demonstration purposes than anything.
PS C:\> .\demo2.ps1
The button was clicked and the form closed
PS C:\>
Summary
This should be enough to whet your appetite for more and give you something to play with. Obviously, we need to add more controls like text boxes and labels. Generally, we don't add them directly to the window as I did with the button. But I'll get to that next time. I think we're going to have fun with this.