During our exploration of creating a Windows Presentation Foundation (WPF) PowerShell script, I've been taking a programmatic approach. That is, I've been creating instances of the relevant .NET classes in a PowerShell script, updating properties, defining events, and launching the script. I believe this is a good way to teach you how the works. Now that you understand the mechanics, we can move on and separate the form description with the code necessary to run the form. This is the WPF model.
## XAML
The traditional approach is to define the visual elements of the form in a special XML format called `XAML`. You can pronounce it *zamel*. For our purposes, the `XAML` definition can be loaded from a file or embedded in the script file. The idea is that you can modify the layout or the code separately.
Here's a simple `XAML` file.
Now that you've seen how we create a WPF form in code, you can probably make sense of the XML file. The first step is to get this definition into our PowerShell script.
During our exploration of creating a Windows Presentation Foundation (WPF) PowerShell script, I've been taking a programmatic approach. That is, I've been creating instances of the relevant .NET classes in a PowerShell script, updating properties, defining events, and launching the script. I believe this is a good way to teach you how the works. Now that you understand the mechanics, we can move on and separate the form description with the code necessary to run the form. This is the WPF model.
XAML
The traditional approach is to define the visual elements of the form in a special XML format called XAML. You can pronounce it zamel. For our purposes, the XAML definition can be loaded from a file or embedded in the script file. The idea is that you can modify the layout or the code separately.
Now that you've seen how we create a WPF form in code, you can probably make sense of the XML file. The first step is to get this definition into our PowerShell script.
The XAML only defines what the form looks like. You will need to add code to handle things like button clicks. For that, you need to be able to find the relevant controls in the form. We will do that with an XmlNodeReader.
$reader=New-ObjectSystem.Xml.XmlNodeReader$xaml
Finding Controls
To find controls, you will need a reference to the parent form or window. We will create an object that can read the XAML and find things.
$form=[Windows.Markup.XamlReader]::Load($reader)
You have to some idea on what the XAML will define. The example I'm using defines a text box. I want the default to the be local computer name. I have to define this outside of the XAML.
You need to make sure that the controls in your XAML have a defined name.
x:Name="ComputerTextBox"
That is the value you are finding.
Creating Events
The XAML data is static and knows nothing about PowerShell. In your script, you need to define actions such as mouse clicks. First, find the control.
$quit=$form.FindName("QuitButton")
Then, as I did when writing PowerShell scripts using WPF code, add a Click event.
$quit.Add_Click({$form.Close()})
You can reference controls in your script, but you need a reference to them. In my previous example I created new controls and assigned them to variables. Using XAML you can still create the variable, but you need to "find" the control in the XAML.
$results=$form.FindName('ResultsTextBlock')
I can use this variable in another script block.
$go=$form.FindName("GoButton")$Action={#Invoke a private helper function to resetSetForm$data=Invoke-Command{Get-Service}-ComputerName$txtComputer.Text-HideComputerNameWrite-Host"Found $($data.count) items"-foregroundGreenStart-Sleep-Seconds2$results.Text=$data|Get-Random-count10|Out-String}$go.Add_click($Action)
Here's my full example.
#requires -version 5.1Add-Type-AssemblyNamePresentationFramework$XamlPath=Convert-Path"$PSScriptRoot\Demo.xaml"[xml]$xaml=Get-Content-Path$XamlPathWrite-Host"Loading content from $XamlPath"-ForegroundColorcyan$reader=New-ObjectSystem.Xml.XmlNodeReader$xaml$form=[Windows.Markup.XamlReader]::Load($reader)$txtComputer=$form.FindName('ComputerTextBox')$txtComputer.text=$env:COMPUTERNAME$quit=$form.FindName("QuitButton")$quit.Add_Click({$form.Close()})$results=$form.FindName('ResultsTextBlock')$go=$form.FindName("GoButton")$Action={SetForm$data=Invoke-Command{Get-Service}-ComputerName$txtComputer.Text-HideComputerNameWrite-Host"Found $($data.count) items"-foregroundGreenStart-Sleep-Seconds2$results.Text=$data|Get-Random-count10|Out-String}$go.Add_click($Action)FunctionSetForm{#clear the results box#write-host "Init" -ForegroundColor Yellow$results.Focus()$results.Background="White"$results.Text="Working..."}$timer=New-ObjectSystem.Windows.Threading.DispatcherTimer#create an interval of 500ms$timer.Interval=[TimeSpan]"0:0:0.500"$timer.Add_Tick({Init$timer.Stop()})$txtComputer.Add_TextChanged({$timer.Stop()$timer.Start()})#display the form[void]$form.showDialog()
The script is simplified compared to my other examples because all the code to define the form is in the XAML file.
figure 1
In the download zip file, I will include another example script.
Creating XAML
I'm hoping some of you are wondering where I got the XAML code. Once you understand the format, you could manually create the XML file. For a simple form, that might be doable. However, there's a better way.
Download and install Visual Studio Community Edition. You don't need the full Visual Studio product, unless you can get it from your company. The Community Edition is a trimmed down version that meets our needs. The best part is that it is free. You can download the setup file from https://visualstudio.microsoft.com/downloads/. If you are a winget user, this might work better.
The installer will download and install core packages.
figure 2
Using Visual Studio
After installation, search for and open Visual Studio 2022. You don't need an account to use the product. I skip the prompted logon. On a new install, you can pick a color theme and development theme. You should be able to use General.
Click on Create a new project.
figure 3
You will need to install additional resources. Click on Install more tools and features. You need the .NET resources to create WPF-based projects.
figure 4
After you check the box, you'll get installation options. You probably don't need many of them since don't intend to write code.
figure 5
You can always download features later.
After you get everything downloaded, you can create a new WPF project. The language shouldn't matter since we aren't going to write any code. I'll create a C# WPF project.
figure 6
Give your project a name. When prompted for a .NET Version, it shouldn't matter. You might as well select the latest version.
Designing the Form
Visual Studio will create an empty form.
figure 7
Right-click on the form and select Properties to open the properties window. Give the window a name so that you can find it later in the XAML. You can also set other properties such as the window title, size, and startup location. You can always adjust properties as you want.
Now, click on the Toolbox menu on the left side of the screen. This will open a palette of WPF controls like label and text boxes.
figure 8
Now for the fun part. Make sure your form has a grid control (or a stack panel). Drag and drop a control onto the form like a label. Position and resize as you want. In the properties panel, give the control a name so you can reference it later if you need to and set other properties. You can also set properties in the XAML that you'll see. I won't sugar coat it. There is a learning curve and expect a little frustration. But eventually you will be able to drop controls onto the form, position them where you want, and set properties.
You can click the green arrow key at the top to preview the form.
After you have the form laid out you want you can save the XAML file.
Once you have the XAML file you can save your project and close Visual Studio. You can use the XAML file in your PowerShell script. I suggest making a copy because you will need to edit it.
You only need to reference controls you are using in your script. Here's my demo script:
#this script has no error handling and is for demonstration purposes onlyAdd-Type-AssemblyNamePresentationFramework$XamlPath=Convert-Path"$PSScriptRoot\diskstat.xaml"[xml]$xaml=Get-Content-Path$XamlPath# Write-Host "Loading content from $XamlPath" -ForegroundColor cyan$reader=New-ObjectSystem.Xml.XmlNodeReader$xaml$form=[Windows.Markup.XamlReader]::Load($reader)$dataGrid=$form.FindName('dataGrid')$btnQuit=$form.FindName('btnQuit')$btnQuit.Add_Click({$form.Close()})$btnRun=$form.FindName('btnRun')$btnRun.Add_Click({#Write-Host "Querying $($ComboNames.SelectedItem)" -foreground Yellow$dataGrid.ItemsSource=$null$dataGrid.Items.Clear()$dataGrid.Items.Refresh()$form.Dispatcher.Invoke([action]{},[System.Windows.Threading.DispatcherPriority]::Render)$data=[System.Collections.Generic.list[object]]::new()Get-CimInstancewin32_LogicalDisk-ComputerName$ComboNames.SelectedItem|Select-Object-propertyDeviceID,@{Name="Volume";Expression={if($_.VolumeName){$_.VolumeName}else{"none"}}},@{Name="SizeGB";Expression={($_.Size/1gb)-as[INT]}},@{Name="FreeGB";Expression={[math]::Round($_.FreeSpace/1gb,2)}},@{Name="PercentFree";Expression={[math]::round(($_.FreeSpace/$_.size)*100,2)}},@{Name="Partition";Expression={(Get-CimAssociatedInstance-InputObject$_-ResultClassNameWin32_DiskPartition).Name}}|ForEach-Object{$data.Add($_)}$dataGrid.ItemsSource=$data})#add computers to the WPF combo box$comboNames=$form.FindName('comboNames')$computers=Get-Content-Path"$PSScriptRoot\computers.txt"#add the local computer to the list$computers+=$env:COMPUTERNAME$computers|ForEach-Object{[void]$comboNames.Items.Add($_)}$comboNames.SelectedIndex=0[void]$form.ShowDialog()
The script populates the combo box with computer names from a text file. It also adds the local computer name. I added code to query disk information for the selected computer. The results are displayed in a data grid.
figure 10
If I want to adjust the form, I can edit the saved XAML file or go back to Visual Studio to refine the form, re-save the XAML files, and then remove the problematic lines.
Summary
Creating the XAML file requires a different skill set that will take a little time and practice to master. But it will allow you to create more complex forms with less trial and error. You can then separate the code to define the form from the code that runs the form. This will make your work easier to maintain and update. At least, that's the idea.
You can download a zip file with all my examples, plus a few other from Dropbox.
I'd love to hear what you think about this topic.
(c) 2022-2025 JDH Information Technology Solutions, Inc. - all rights reserved
Don't miss what's next. Subscribe to Behind the PowerShell Pipeline: