How To Use PowerShell and WPF To Create Advanced GUIs

This video tutorial demonstrates integrating PowerShell with WPF (Windows Presentation Foundation) to create advanced graphical user interfaces (GUIs).

Brien Posey

December 2, 2024

17 Min View
ITPro Today

This tutorial covers integrating PowerShell and WPF (Windows Presentation Foundation) to create advanced graphical user interfaces (GUIs). It guides you through setting up a WPF XAML file, linking it with PowerShell scripts, and using this combination to enable Windows Inking, a feature for drawing or annotating directly within the GUI. 

Subscribe to ITPro Today’s YouTube channel for PowerShell tutorials and more.

The following transcript has been edited for length and clarity. 

Transcript:

Brien Posey: Hello, greetings and welcome. I'm Brien Posey. In this video, I want to show you how to use the Windows Presentation Foundation with PowerShell. 

For those of you who know my work, I write a lot of PowerShell scripts. These PowerShell scripts are often GUI-enabled, meaning that I build a GUI interface so that you're not just looking at a plain, black screen. Well, lately, I've been exploring the limits of PowerShell. What can you truly accomplish with PowerShell, and what's beyond Powershell’s capabilities? 

One idea I had when exploring some of these limits was to check Windows Inking to see if it would work with PowerShell. For anybody unfamiliar with Windows Inking, it's the idea that you can use a pen, a stylus, or even your finger to write on the screen. Microsoft originally introduced this, I think, with the Surface devices. You could use the Surface pen and write on the screen. Many other devices support Inking today, and I was curious if you could do this with PowerShell.

Related:How To Use PS2EXE To Convert PowerShell Scripts Into EXE Files

You can, but not with a normal GUI. 

Why Use WPF Over Windows Forms?

All the GUIs I've built in my previous articles and videos (to the best of my memory) use Windows Forms. Windows Forms are great because they're intuitive and easy to follow. You can create all the usual GUI elements using Forms. You can make things like buttons, list boxes, and text boxes. However, there is a limit to what you can do with Windows Forms. This limit is where the Windows Presentation Foundation comes into play.

The Windows Presentation Foundation is a part of Microsoft .NET. Originally introduced with .NET 3.0, it's a framework for building rich Windows desktop applications. WPF, or the Windows Presentation Foundation, is based on XAML (Extensible Application Markup Language). XAML is perfect for creating complex graphical interfaces. 

So, we must use WPF to enable Inking within a PowerShell application. Let's see what this looks like. 

Demonstrating a Simple Inking Application

Before I do, I will show you a simple Inking application. I'll run that now. Here's what my application looks like. As you can see, we have an empty PowerShell GUI window, but this window is enabled for Inking, meaning that if I were using a Surface pen, I could write within this window. With a touch screen device, I could use my finger and write within the window. I can even use my mouse. That's all that this application does. I designed this to be lightweight and hopefully easy to understand. I wanted to show you what the application does before I show you the code behind it. 

Related:How To Create Your Own PowerShell Cmdlets (Video Tutorial)

Now, one thing to consider is what it would take to build something like this using PowerShell Forms. I haven't tried making an application like this using Forms. My best guess is you would have to trace the mouse movements and plot every pixel that the mouse goes over while trying to draw. I honestly don't know if it's possible or not. 

Setting up WPF for PowerShell

Let me show you how easy it is to use the Windows Presentation Foundation. Here's what the code looks like: 

# Load the necessary assembly for WPF
Add-Type -AssemblyName PresentationCore, PresentationFramework
# Create the main WPF window
[XML]$XAML = @”
<Window xmlns=“http://schemas.microsoft.com/winfx/2006/xaml/presentation
                                    Title=“Inking Canvas” Height=“600” Width=“800” WindowStartupLocation=“CenterScreen”>
                  <Grid>
                                    <InkCanvas Name=“inkCanvas” Background=“White” />
                  <Grid>
</Window>
“@
# Parse the XAML to create the WPF window and components
$Reader = (New-Object System.Xml.XmlNodeReader $XAML)
$Window = [Windows.Markup.XamlReader]::Load($Reader)
# Display the window
$Window.ShowDialog()

The first thing that we must do is to load the necessary assemblies for the Windows Presentation Foundation. There are two assemblies that we must load. We must load PresentationCore and PresentationFramework. 

As you'll recall, we must load an assembly when creating a PowerShell GUI based on Windows Forms. Loading an assembly is necessary anytime you do anything graphical inside a PowerShell. The difference is that you'll use one assembly. If you're using Windows Forms, you'll use a different collection of assemblies if you plan to use the Windows Presentation Foundation. So, the next thing we must do is create the main Windows Presentation Foundation window. 

Related:ChatGPT Integration in PowerShell (Scripting Demo)

Before I break down the code block for creating the main Windows Presentation Foundation window, I want to show you this: If you plan to build a PowerShell script that uses the WPF, you must load the necessary assemblies. Then, create a block of XML code (and I'll return to this in a moment). Then, parse that XML code—that's what the third section does. You can see that this is essentially just two lines of code. I'll come back to these in a moment. 

The last thing to do is display the GUI, and that's where the $Window.ShowDialog() command comes into play. 

So, if you break all of this down, using the Windows Presentation Foundation involves four lines of code:

  1. Add-Type -AssemblyName PresentationCore, PresentationFramework

  2. $Reader = (New-Object System.Xml.XmlNodeReader $XAML)

  3. $Window = [Windows.Markup.XamlReader]::Load($Reader)

  4. $Window.ShowDialog()

That's the bare minimum for using the Windows Presentation Foundation. 

Let's look at what we're doing in this script. As I mentioned, the first thing that we must do is load the necessary assemblies: 

# Load the necessary assembly for WPF
Add-Type -AssemblyName PresentationCore, PresentationFramework

Then, we have a block to define the Windows Presentation Foundation code. You'll notice that we have the [XML] tag. This tag tells PowerShell that we're creating a variable, which you can see we're creating the variable: $XAML. Then, I also have two @ signs in this block. The @ signs tell PowerShell that we're generating a multi-line string. So, everything between the @ signs is essentially one big, long string. 

Understanding the XAML Structure  

So, what are we doing from there? Well, everything that here is in XAML format:

<Window xmlns=“http://schemas.microsoft.com/winfx/2006/xaml/presentation
                                    Title=“Inking Canvas” Height=“600” Width=“800” WindowStartupLocation=“CenterScreen”>
                  <Grid>
                                    <InkCanvas Name=“inkCanvas” Background=“White” />
                  <Grid>
</Window>

As I mentioned, XAML is the Extensible Application Markup Language. That's the language used to define the user interface in WPF-based applications. 

The first thing that we must do is define a window. You can see we start the window definition – <Window – and then we have a tag – </Window> –at the end of that block where we're ending the window definition. 

Within that window definition, there are a few things that we're doing. First, we are defining the schema that the window will use:

xmlns=“http://schemas.microsoft.com/winfx/2006/xaml/presentation

It points to a URL. Incidentally, there is a way of getting around this. I'll show you what that looks like with a different script. 

Then, I'm setting a few attributes associated with that window. I'm defining a window title:

Title=“Inking Canvas”

And I'm defining the window resolution: 

Height=“600” Width=“800”

Then, I'm defining the window startup location, which is “CenterScreen”: 

WindowStartupLocation=“CenterScreen”

I will run that application again, and you can see the 800 by 600 resolution window. You can also see the title, Inking Canvas, displayed at the top of the window. 

So, that's what this section does.

The next thing that I did was define a grid:

<Grid>
                                    <InkCanvas Name=“inkCanvas” Background=“White” />
                  <Grid>

A grid will be new if you're used to working with Windows Forms. You can use a grid to choose where to plot different elements within the window. In this case, we have only a single element, so we don't have to worry about the location of that element. If we had multiple elements, we would need to control where we put those elements on the screen, and that's where a grid comes into play. 

As I mentioned, we only have one element, and that element is an InkCanvas. In the application I created that allows you to use "ink" within a window, I showed you that a single line of code controls all the inking:

<InkCanvas Name=“inkCanvas” Background=“White” />

We're defining an ink canvas. We're naming it “inkCanvas.” I'm setting the background to “White.” That's all we must do to enable Inking. Hopefully, you're sensing how powerful the Windows Presentation Foundation is. I used a single line of code to create an ink canvas.

PowerShell and WPF: Integrating the Pieces

As you saw, my application was simplistic, but it doesn't have to be that way. You can do pretty much anything that you want with Windows Presentation Foundation. When I first started experimenting with this, I saw some things work great in Windows Presentation Foundation and some things, like Inking, that you must use Windows Presentation Foundation for. With that in mind, other things might be easier to do with Windows Forms. Can you mix the two? And if you do mix the two, can you also mix in some traditional PowerShell elements? The answer to those questions is, “Yes.” 

Combining WPF, Windows Forms, and PowerShell

Let me show you another application that I've written. I'm not going to go through every single code line of this application because it's long and relatively complex, but there are a few things that I wanted to point out to you. This application mainly blends Windows Presentation Foundation and Windows Forms in a single application. 

I had to load two assemblies when I used Windows Presentation Foundation in the previous application. The assemblies that I loaded were PresentationFramework and PresentationCore. I'm still doing that in this application, but I'm also loading an assembly called WindowsBase and an assembly called System.Windows.Forms: 

# Load required assemblies
Add-Type -AssemblyName PresentationFramework
Add-Type -AssemblyName PresentationCore
Add-Type -AssemblyName WindowsBase
Add-Type -AssemblyName System.Windows.Forms
 
System.Windows.Forms is the assembly you use anytime you're working with Windows Forms. 

I mentioned throwing in some traditional PowerShell. I also noted that you don't necessarily have to call a URL to define a schema for a Windows Presentation Foundation application. This application does not use a URL. I've also defined things a bit differently.

You'll remember we had to define a window in the previous application. However, this window is in XML format. There isn't an easy way of interacting with it with PowerShell. Now, in the section where I'm parsing it, I created a variable called $Reader. I set it equal to (New-Object System.Xml.XmlNodReader $XML). Now, $XML is the lengthy text string containing all my XML code. So, essentially, I'm taking all that XML code and putting it into a format that PowerShell can work with, and I'm assigning the variable named $Reader. Then, I have this line: 

$Windows = [Windows.Markup.XamlReader]::Load($Reader)

I'm making a .NET call (as evidenced by the double colon) and loading $Reader. It gives us a way of interacting with some of this XML code. For example, I'm calling $Window and appending .ShowDialog() to display the window: 

$Window.ShowDialog()

Now, this gets messy from a PowerShell standpoint. It works fine for something short like this, but if you're trying to incorporate traditional PowerShell code, it's not the easiest thing to do when your script is like this. So, I did things quite a bit differently in the other script. Let’s switch back.

In this script, rather than using all the XML as I did before, I'm using a style that's more typical of PowerShell. Here, I created a variable called $Window. 

#Create a WPF window
$Window = New-Object System.Windows.Window
$Window.Title = “WPF InkCanvas with Menu”
$Window.Width = 1024
$Window.Height = 768

My last script had a variable called $Window, but I set it here equal to New-Object System.Windows.Window. Then, I set various attributes, such as the title, the width, and the height. 

I also defined a grid, just like I did in that last script, but this time, I'm using a PowerShell variable:

# Create a Grid and define rows with adjusted heights
$Grid = New-Object System.Windows.Controls.Grid
$Grid.RowDefinitions.Add((New-Object System.Windows.Controls.RowDefinition -Property @{ Height = “Auto” }))  # Row for the Menu (Auto size)
$Grid.RowDefinitions.Add((New-Object System.Windows.Controls.RowDefinition -Property @{ Height = “*” }))   #Row for the InkCanvas (fill remaining space
$Window.Content = $Grid

Again, I've set that equal to a New-Object. I've typed System.Windows.Controls.Grid, and then I'm defining the placement of a couple of objects on that grid. Now, what type of objects? Well, I've created two different objects. One is an Inking canvas, just like what we had in the previous script. The other is a menu. I made a menu that would allow me to change the color of the ink. 

Let's take a brief look at how this menu works: 

# Create a Menu and add it to the grid
$Menu = New-Object System.Windows.Controls.Menu
$Menu.SetValue([System.Windows.Controls.Grid]::RowProperty, 0)   #Place menu in the first row
$Grid.Children.Add($Menu)

The first thing that I do is create a variable called $Menu. It’s going to be an object of type System.Windows.Controls.Menu. So, I'm creating a menu object. Next, I'm referencing that variable. I'm setting its value equal to all this: ([System.Windows.Controls.Grid]::RowProperty, 0). It defines the menu’s position within the grid and, in turn, will control where that menu is on the screen. Then, I must add that menu to the grid: $Grid.Children.Add($Menu).

The next thing that I'm doing is creating a MenuItem right here: 
# Create a MenuItem for color options
$ColorMenuItem = New-Object System.Windows.Controls.MenuItem
$ColorMenuItem.Header = “Options”  # Main menu title

“Options” is going to be the name of the menu. 

Then, I'm creating a submenu that appears when the user clicks on options: 

# Create a submenu item for choosing ink color
$SelectColorItem = New-Object System.Windows.Controls.MenuItem
$SelectColorItem.Header = “Choose Ink Color”
$SelectColorItem.Add_Click({

“Choose Ink Color” will be what the menu option shows. In other words, there's going to be a menu that appears at the top of the screen. It says “Options.” When you click Options, you'll see an option to choose the ink color. 

After that, we must define a click action. We handle that here:

$SelectColorItem.Add_Click({
                  # Open ColorDialog to select color
                  $ColorDialog = New-Object System.Windows.Forms.ColorDialog
                  if ($ColorDialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
 
                                    # Convert System.Drawing.Color to System.Windows.Media.Color
                                    $DrawingColor = $ColorDialog.Color
                                    $MediaColor = [System.Windows.Media.Color]::FromArgb($DrawingColor.A, $DrawingColor.R, $DrawingColor.G, $DrawingColor.B)
 
                                    # Apply the color to the InkCanvas’s DefaultDrawingAttributes
                                    $InkCanvas.DefaultDrawingAttributes.Color = $MediaColor
                  }
})

So, what are we doing here? Well, the first thing is I've created a variable called $ColorDialog, and I'm setting that equal to New-Object System.Windows.Forms.ColorDialog. Here's where Windows Forms come into play. The form I'm opening is ColorDialog, and this is a dialog box that's built into .NET. It is preconstructed for your use. Anybody can use it. You don't have to create this from scratch. 

So, essentially, I'm saying, “When somebody clicks on the menu option, then this color dialog box will open.” 

Then, we have this line right here: 

if ($ColorDialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {

In other words, if the user picks a color in this dialog box and then clicks “OK,” this block of code will execute:

# Convert System.Drawing.Color to System.Windows.Media.Color
                                    $DrawingColor = $ColorDialog.Color
                                    $MediaColor = [System.Windows.Media.Color]::FromArgb($DrawingColor.A, $DrawingColor.R, $DrawingColor.G, $DrawingColor.B)
 
                                    # Apply the color to the InkCanvas’s DefaultDrawingAttributes
                                    $InkCanvas.DefaultDrawingAttributes.Color = $MediaColor

So, we're taking the color from that dialog box and putting it into a format that Windows Presentation Foundation can understand. That's what the $MediaColor line does. Then, we apply that color to the InkCanvas: 

$InkCanvas.DefaultDrawingAttributes.Color = $MediaColor

$InkCanvas.DefaultDrawingAttributes.Color controls the ink color, and we're setting that equal to $MediaColor, which is the color that came from the dialog box but in the proper format. 

The remaining code adds the different items to the screen. We also have the section where we define our InkCanvas, and I'm handling this a little bit different than I did in the previous script: 

# Create an InkCanvas
$InkCanvas = New.Object System.Windows.Controls.InkCanvas
$InkCanvas.Background = [System.Windows.Media.Brushes]::White
$InkCanvas.HorizontalAlignment = “Stretch
$InkCanvas.VerticalAlignment = “Stretch”
$InkCanvas.Margin = “10”
$InkCanvas.SetValue([System.Windows.Controls.Grid]::RowProperty, 1)  # Place InkCanvas in the second row

As you'll recall in the other script, a single line of code created the InkCanvas. In this case, I've broken it out a little bit. I'm defining some individual attributes. I'm doing it this way because it's easier to work with in PowerShell. We have conventional variable names (as opposed to everything being purely in XML format).

Then, we're adding our InkCanvas to the grid:

# Add the InkCanvas to the Grid
$Grid.Children.Add($InkCanvas)

Then, finally, I have the ShowDialog line that causes all this to display on the screen:

# Show the window
$Window.ShowDialog() | Out-Null

So, what does all this look like? I’ll switch back to PowerShell and run the script.

At first glance, the window looks identical to what you saw earlier, but you'll notice the word “Options” in the upper-left corner. So, I can draw using ink, and then if I click Options, you'll see the “Choose Ink Color” option. If I click that, we have a popup, and we can choose a color. I'll pick blue and click OK. That changes the ink color to blue. If I want to select a different color, I can click Options and Choose Ink Color. Maybe we'll go with red this time. I'll click OK, and now we have red. 

So, that's what that script does. At any rate, I just wanted to give you a taste of how Windows Presentation Foundation integrates with PowerShell and how you can blend your use of Windows Presentation Foundation and Windows Forms into a single script. 

With that said, I hope you enjoyed the information. I'm Brien Posey. Thanks for watching.

About the Author

Brien Posey

Brien Posey is a bestselling technology author, a speaker, and a 20X Microsoft MVP. In addition to his ongoing work in IT, Posey has spent the last several years training as a commercial astronaut candidate in preparation to fly on a mission to study polar mesospheric clouds from space.

https://brienposey.com/

Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like