More WPF Controls
I want to quickly apologize for the odd email you may have received. I was testing the Buttondown API for creating emails and I accidentally sent the email instead of saving it as a draft. I made a mistake by assuming I knew the default behavior of the API. A good reminder to read documentation and to do your homework before trying anything in code.
I will continue expanding the script I started last time by adding more controls to the form. I will also add some basic functionality to the controls to demonstrate how to interact with them. I'll begin with adding an option for the user to enter an alternate set of credentials. This looks better when it is grouped so I will use a GroupBox
control to contain the alternate credential controls.
Listing Controls
Before we get to that, let me give you a tip on how to list .NET types that are related to WPF controls. You've seen that the controls, like buttons, belong to the System.Windows.Controls
namespace. Here's one way to discover the controls that are available in that namespace.
First, we need to get a reference to the parent PresentationFramework assembly.
$pf = [System.AppDomain]::CurrentDomain.GetAssemblies() | where location -match PresentationFramework
You should get something like this:
PS C:\> $pf
GAC Version Location
--- ------- --------
True v4.0.30319 C:\WINDOWS\Microsoft,Net\assembly\GAC_MSIL\Presentation...
Don't forget to add the type first.
Add-Type -AssemblyName PresentationFramework
Then you can use the GetTypes()
method to list the types in the assembly.
$pf.GetTypes() | where basetype -match 'System\.Windows\.Controls' |
Sort-Object Name | Select-Object Name,FullName
I'm filtering and sorting to only show controls. Here's a sample of the output that I've sent to the Out-GridView
cmdlet.

Group Box
Once you know the full type name, you can use Get-TypeMember to explore it.

I'm going to create a test WPF form with a GroupBox
control that will eventually contain the alternate credential controls.
$grp = [System.Windows.Controls.GroupBox]@{
Header = 'Alternate Credentials'
Width = $window.Width - 100
Height = 100
HorizontalAlignment = 'Left'
FontSize = 12
Margin = '5,0,0,0'
}
This gets added to the to stack panel.
$stack.AddChild($grp)
$window.AddChild($stack)
[void]$window.ShowDialog()

This is where things get interesting.
You'll recall that I said that WPF is based on the idea of nested layers. I want to add labels and text boxes to the group, but I can't add them to the GroupBox
like I did when using a StackPanel
. The GroupBox
control can only contain one child. This could be a StackPanel
or another container control. I'll take this opportunity to introduce you to the Grid
control.
Adding a Grid
Think of a grid like a spreadsheet with rows and columns. You can add controls to specific cells in the grid. For my group control I know I will need three columns and three rows.
$allCol=@()
$allRow = @()
$allCol += $col0 = [System.Windows.Controls.ColumnDefinition]@{ Width = [System.Windows.GridLength]::Auto }
$allCol += $col1 = [System.Windows.Controls.ColumnDefinition]@{ Width = [System.Windows.GridLength]::Auto }
$allCol += $col2 = [System.Windows.Controls.ColumnDefinition]@{ Width = [System.Windows.GridLength]::Auto }
$allRow+= $row0 = [System.Windows.Controls.RowDefinition]@{Height="Auto"}
$allRow+= $row1 = [System.Windows.Controls.RowDefinition]@{Height="Auto"}
$allRow+= $row2 = [System.Windows.Controls.RowDefinition]@{Height="Auto"}
$grpGrid = [System.Windows.Controls.Grid]@{
Width = $grp.Width
Height = $grp.Height
#showing grid lines helps you visualize where controls should be placed
#You will need to add controls if automatically sizing
ShowGridLines = $False
}
$allCol | ForEach-Object { $grpGrid.ColumnDefinitions.Add($_) }
$allRow | ForEach-Object { $grpGrid.RowDefinitions.Add($_) }
I am defining the columns and rows to automatically size based on the controls that will be added to the grid.
Now to add items to the grid. First, a label and a text box for the user name.
$lblUser = [System.Windows.Controls.Label]@{
Content = 'Username:'
FontSize = 12
}
#position label on the grid
$lblUser.SetValue([System.Windows.Controls.Grid]::RowProperty, 0)
$lblUser.SetValue([System.Windows.Controls.Grid]::ColumnProperty, 0)
$txtUser = [System.Windows.Controls.TextBox]@{
Width = 150
Height = 20
FontSize = 12
ToolTip = 'Enter your the alternate credential username'
}
#position text box on the grid
$txtUser.SetValue([System.Windows.Controls.Grid]::RowProperty, 0)
$txtUser.SetValue([System.Windows.Controls.Grid]::ColumnProperty, 1)
Notice that I am specifying the row and column for each control. I'll add a label for the password using the same technique.
$lblPassword = [System.Windows.Controls.Label]@{
Content = 'Password:'
FontSize = 12
}
$lblPassword.SetValue([System.Windows.Controls.Grid]::RowProperty, 1)
$lblPassword.SetValue([System.Windows.Controls.Grid]::ColumnProperty, 0)
Now for the new bits. I want to include a text box for the password. This should be masked, yet I also want to provide an option to reveal the password. I could use a normal TextBox
control and manipulate the properties. But let's use a new control, the PasswordBox
. This control is designed to handle passwords. It will automatically mask them. You can get the plaintext password with the Password
property or the secure string with the SecurePassword
property.
$txtPassword = [System.Windows.Controls.PasswordBox]@{
Width = 150
Height = 20
FontSize = 12
ToolTip = 'Enter your the alternate credential password'
Password = ''
}
$txtPassword.SetValue([System.Windows.Controls.Grid]::RowProperty, 1)
$txtPassword.SetValue([System.Windows.Controls.Grid]::ColumnProperty, 1)
The control can't show the password in plain text, but I can add a checkbox and an event handler to temporarily display the password. I'm going to create a hidden label.
$lblPlainText = [System.Windows.Controls.Label]@{
Content = ' '
FontSize = 12
FontStyle = 'Italic'
Foreground = 'Red'
Visibility = 'Hidden'
}
$lblPlainText.SetValue([System.Windows.Controls.Grid]::RowProperty, 1)
$lblPlainText.SetValue([System.Windows.Controls.Grid]::ColumnProperty, 2)
I can dynamically display the plaintext password using this label.
CheckBox
I'll create a checkbox to toggle the visibility of the password.
$chkShowPassword = [System.Windows.Controls.CheckBox]@{
Content = 'Show Password'
FontSize = 12
#indent the checkbox a little in the grid cell
Margin = '5,0,0,0'
}
$chkShowPassword.SetValue([System.Windows.Controls.Grid]::RowProperty, 2)
$chkShowPassword.SetValue([System.Windows.Controls.Grid]::ColumnProperty, 0)
Now, I need an event handler when the checkbox is checked or unchecked. When checked, I want to make the password label visible.
$chkShowPassword.Add_Checked( {
$lblPlainText.Visibility = 'Visible'
$lblPlainText.Content = $txtPassword.Password
})
When unchecked, I want to hide the label.
$chkShowPassword.Add_UnChecked( {
$lblPlainText.Visibility = 'Hidden'
$lblPlainText.Content = ' '
})
Here's a preview of the controls in a form.

Combobox
Let's also try a new control, a ComboBox
. This control is a drop-down list of items.
$combo = [System.Windows.Controls.ComboBox]@{
Width = 150
Height = 20
HorizontalAlignment = 'Left'
FontSize = 12
#set the default selected item
SelectedIndex = 0
Margin = '5,0,0,0'
}
You should be seeing some patterns by now. The control needs items to display. I'll add some items to the combo box in much the same way I added items to the list box in the previous article.
[void]$combo.Items.Add('Option 1')
[void]$combo.Items.Add('Option 2')
[void]$combo.Items.Add('Option 3')
[void]$combo.Items.Add('Option 4')
The method will write the index number to the pipeline so I'm casting it to void to suppress the output.
Here's what I end up with.

You can reference the selected item by its index ($combo.SelectedIndex
) or by its content ($combo.SelectedItem
).
DataGrid
The last control I want to introduce is a DataGrid
. This control is used to display tabular data. It's a bit more complex than the other controls I've shown you. I'll create a simple example to demonstrate how to use it.
$DataGrid = [System.Windows.Controls.DataGrid]@{
Width = $window.Width - 50
Height = $window.Height - 50
HorizontalAlignment = 'Left'
VerticalAlignment = 'Center'
FontSize = 12
CanUserAddRows = $False
CanUserDeleteRows = $False
CanUserReorderColumns = $False
CanUserSortColumns = $False
Margin = '10,0,0,0'
}
I'm not going to get into advanced techniques for manipulating the data grid such as sorting. Maybe I'll cover that in a future article. This is a simple example to show you how to use the control.
Instead of adding columns and rows to the grid, I can simply define a source and PowerShell will do the rest.
$DataGrid.ItemsSource = Get-Process | Select-Object -Property ID, Name, WS, Path

The other thing you might want to do is automatically re-size the grid should the window be resized. That's assuming you don't define the the window to be a static size.
I'll add an event handler to the window to handle this option.
$Window.add_SizeChanged({
$DataGrid.Width = $window.Width - 50
$DataGrid.Height = $window.Height - 50
})
If the window size changes, the grid will automatically resize accordingly.
Summary
I thought I'd cover an updated complete script, but I think that would be too much for one article. I'll continue with that in the next article. I can also cover a few other controls and discuss issues you might encounter working with WPF scripts in PowerShell.
In the meantime, you can download a zip file of my demo code sample from Dropbox. Including file attachments causes more problems than it solves.
As always, feel free to leave questions and comments.