How to Build PowerShell GUIs with XAML and WPF

Listen to this blog post!

Table of contents:

A key advantage of WPF-based user interfaces is that they can be defined using XAML, an XML-based markup language. In the previous tip, we created a simple WPF user interface programmatically:

# load the required .NET assemblies (requires Windows operating system)
Add-Type -AssemblyName PresentationCore
Add-Type -AssemblyName PresentationFramework


# define content of user interface
$WindowTitle = 'Enter User Name'
$OkText = 'Log On'
$CancelText = 'Abort'
$Prompt = 'Please enter user name:'
$DefaultValue = $env:USERDOMAIN + '\' + $env:USERNAME

# create blank window
$form = [System.Windows.Window]::new()
$form.Title = $WindowTitle
$form.SizeToContent = [System.Windows.SizeToContent]::WidthAndHeight
$form.WindowStartupLocation = [System.Windows.WindowStartupLocation]::CenterScreen


# use a grid to position elements
$grid = [System.Windows.Controls.Grid]::new()
# use a uniform 10 units margin
$grid.Margin = 10

# Define rows
$row1 = [System.Windows.Controls.RowDefinition]::new()
$row2 = [System.Windows.Controls.RowDefinition]::new()
$grid.RowDefinitions.Add($row1)
$grid.RowDefinitions.Add($row2)

# Define columns
$col1 = [System.Windows.Controls.ColumnDefinition]::new()
$col2 = [System.Windows.Controls.ColumnDefinition]::new()
$grid.ColumnDefinitions.Add($col1)
$grid.ColumnDefinitions.Add($col2)

# create OK button
$OKButton = [System.Windows.Controls.Button]::new()
$OKButton.Width = 100
$OKButton.Content = $OkText
# this is the official OK button and can be operated via ENTER
$OKButton.add_click({ $form.DialogResult = $true })

# place button in grid cell 1/1
[System.Windows.Controls.Grid]::SetRow($OKButton, 1)
[System.Windows.Controls.Grid]::SetColumn($OKButton, 1)
$null = $grid.Children.Add($OKButton)

# create CANCEL button
$CancelButton = [System.Windows.Controls.Button]::new()
$CancelButton.Width = 100
$CancelButton.Content = $CancelText
# this is the official cancel button and can be operated via ESC
$CancelButton.IsCancel = $true
# place button in grid cell 1/0
[System.Windows.Controls.Grid]::SetRow($CancelButton, 1)
[System.Windows.Controls.Grid]::SetColumn($CancelButton, 0)
$null = $grid.Children.Add($CancelButton)

# create a label
$label = [System.Windows.Controls.Label]::new()
$label.Content = $Prompt
# place label in grid cell 0/0
[System.Windows.Controls.Grid]::SetRow($label, 0)
[System.Windows.Controls.Grid]::SetColumn($label, 0)
$null = $grid.Children.Add($label)

# create textbox to enter a text
$textBox = [System.Windows.Controls.TextBox]::new()
$textBox.Text = $DefaultValue
# use a small margin
$textBox.Margin = 5
# place textbox in grid cell 0/1
[System.Windows.Controls.Grid]::SetRow($textBox, 0)
[System.Windows.Controls.Grid]::SetColumn($textBox, 1)
$null = $grid.Children.Add($textBox)


# add grid to window
$form.Content = $grid

# make it top-most
$form.Topmost = $true

# when the form shows, select the textbox
$null = $textBox.Focus()

# show dialog in a modal dialog window
$result = $form.ShowDialog()
$hasCanceled = $result -ne $true

if ($hasCanceled)
{
  throw 'User canceled dialog'
}

# read textbox content
$textBox.Text


The very same user interface can be defined without code, using XAML like this:

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        SizeToContent="WidthAndHeight"
        WindowStartupLocation="CenterScreen">
  <Grid Margin="10">
    <Grid.RowDefinitions>
      <RowDefinition />
      <RowDefinition />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
      <ColumnDefinition />
      <ColumnDefinition />
    </Grid.ColumnDefinitions>

    <Button Name="OKButton" Content="Button" Width="100" Grid.Column="1" Grid.Row="1" />
    <Button Name="CancelButton" IsCancel="True" Content="Button" Width="100" Grid.Column="0" Grid.Row="1" />
    <Label Name="label" Grid.Column="0" Grid.Row="0" />
    <TextBox Name="textBox" Margin="5" Grid.Column="1" Grid.Row="0" />
  </Grid>
</Window>


As you can see, the XAML defines the same object instances and properties as the earlier code-based approach. The Name property assigns a name to each UI element, allowing your code to reference it.

It is now relatively easy to make adjustments to the XAML, and you can use general-purpose XAML tools—such as the Visual Studio designer—to create and edit the markup.

In PowerShell, you now need a helper function to convert the XAML to real objects:

function Convert-XAMLtoWindow
{
  param (
    [Parameter(Mandatory = $true)]
    [string] $XAML
  )

  Add-Type -AssemblyName PresentationFramework

  $reader = [XML.XMLReader]::Create([IO.StringReader]$XAML)
  $result = [Windows.Markup.XAMLReader]::Load($reader)
  $reader.Close()

  $reader = [XML.XMLReader]::Create([IO.StringReader]$XAML)
  while ($reader.Read()) {
    $name = $reader.GetAttribute('Name')
    if (!$name) { $name = $reader.GetAttribute('x:Name') }
    if ($name) {
      $result | Add-Member NoteProperty -Name $name -Value $result.FindName($name) -Force
    }
  }
  $reader.Close()
  $result
} 

Here is the complete script that uses a XAML definition instead of defining the UI by code:

# define interface values
$WindowTitle = 'Enter User Name'
$OkText = 'Log On'
$CancelText = 'Abort'
$Prompt = 'Please enter user name:'
$DefaultValue = $env:USERDOMAIN + '\' + $env:USERNAME

# XAML layout
$xaml = @'
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        SizeToContent="WidthAndHeight"
        WindowStartupLocation="CenterScreen">
  <Grid Margin="10">
    <Grid.RowDefinitions>
      <RowDefinition />
      <RowDefinition />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
      <ColumnDefinition />
      <ColumnDefinition />
    </Grid.ColumnDefinitions>

    <Button Name="OKButton" Content="Button" Width="100" Grid.Column="1" Grid.Row="1" />
    <Button Name="CancelButton" IsCancel="True" Content="Button" Width="100" Grid.Column="0" Grid.Row="1" />
    <Label Name="label" Grid.Column="0" Grid.Row="0" />
    <TextBox Name="textBox" Margin="5" Grid.Column="1" Grid.Row="0" />
  </Grid>
</Window>
'@

$window = Convert-XAMLtoWindow -XAML $xaml

# configure window and UI elements
$window.Title = $WindowTitle
$window.label.Content = $Prompt
$window.textBox.Text = $DefaultValue
$window.CancelButton.Content = $CancelText
$window.OKButton.Content = $OkText
$window.OKButton.Add_Click({ $window.DialogResult = $true })

# Show Window
$result = $window.ShowDialog()

# evaluate result
if ($result -ne $true) {
  throw 'User canceled dialog'
}

# read textbox content
$window.textBox.Text

Your ultimate PowerShell Cheat Sheet

Unleash the full potential of PowerShell with our handy poster. Whether you're a beginner or a seasoned pro, this cheat sheet is designed to be your go-to resource for the most important and commonly used cmdlets.The poster is available for download and in paper form.

Get your poster here!

Related links