Decorating Your PowerShell WPF Script
Let's get back to exploring what we can do with Windows Presentation Foundation (WPF) PowerShell scripts. You've seen how to create a simple form using a StackPanel
. I also demonstrated the flexibility of the Grid
layout. I showed you how to add common controls like text boxes and buttons. However, you may want to add a little eye-candy to your forms. In this article, I'll show you how to add images to your forms, use progress bars, and more. I'll have a download link at the end so you can grab all of my demo code. Let's get started.
Progress Bars
Progress bars are a common control used in many applications. They provide feedback to the user about the status of a task. You can use them to show the progress of a file download, a script execution, or any other task that takes time to complete. Or you can use them as a simple graphing element.
Once you've loaded the required WPF assemblies into your PowerShell session, you can use the Get-TypeMember
command to explore the available properties and methods of the ProgressBar
control.

Although, as you'll see, you only need to work with a few properties.
$diskProgress = [System.Windows.Controls.ProgressBar]@{
Width = 200
Height = 20
Minimum = 0
Maximum = 100
Value = 0
Foreground = 'Lime'
HorizontalAlignment = 'Left'
Margin = '25,2,0,0'
}
You will most likely want to set the dimensions. The default color is a green tint, but you can use any [System.Drawing.Color] value.
If you would like to see available color names, you can try this code:
Add-Type -AssemblyName System.Drawing
[System.Drawing.color].GetProperties() | Where {$_.PropertyType.Name -match "color"}| Select-Object Name
The Value
property can be any value. It is up to you to decide what value makes the most sense given the dimensions of your progress bar. For my disk usage progress bar, I have a helper function that sets the value based on the percentage of free disk space.
Function GetDiskValue {
$disk = Get-CimInstance -Class Win32_LogicalDisk -Filter "DeviceID='C:'" -CimSession $txtComputerName.Text
[int]$DiskSizeGB = $disk.Size / 1GB
[double]$DiskFreeGB = [math]::Round($disk.FreeSpace / 1GB, 2)
$diskProgress.Value = $disk.FreeSpace / $disk.Size * 100
$diskProgress.ToolTip = "Drive C: $DiskFreeGB GB Free out of $DiskSizeGB GB"
}
I have another progress bar in my demo to display memory usage. I can dynamically set the progress bar color based on a value.
if ($memProgress.Value -gt 60) {
$memProgress.Foreground = 'MediumSeaGreen'
}
elseif ($memProgress.Value -gt 25) {
$memProgress.Foreground = 'OrangeRed'
}
else {
$memProgress.Foreground = 'Red'
}
By default, progress bars are horizontal. But you can make them vertical.
$processProgress = [System.Windows.Controls.ProgressBar]@{
Width = 50
Height = 100
Minimum = 0
Maximum = 100
Value = 30
Foreground = 'MediumSlateBlue'
HorizontalAlignment = 'Left'
Orientation = 'Vertical'
Margin = '25,2,0,0'
}
You will need to adjust the width and height to fit your form. I'm setting the value using a relative scale.
Function GetProcessCount {
$processCount = (Get-CimInstance -ClassName Win32_Process -CimSession $txtComputerName.Text).Count
Switch ($processCount) {
{ $_ -gt 500 } {
$processProgress.Value = 100
}
{ $_ -gt 100 } {
$processProgress.Value = $processCount * .2
}
Default {
$processProgress.Value = $processCount
}
}
$processProgress.ToolTip = "Running Processes: $($processCount)"
}
Here's are a few samples.


I'll have a download link for all my samples at the end.
Graphics
You might also want to include a graphic into your image. You can create a System.Windows.Controls.Image
setting.
$GraphicImage = [System.Windows.Controls.Image]@{
Source = (Convert-Path '.\search-server.png')
Width = 50
Height = 50
HorizontalAlignment = 'Left'
Margin = '25,5,0,0'
}
You need to make sure PowerShell can find the path to the file. You can use the Width
and Height
properties to adjust the size of the image.

As an alternative, you could embed the image directly into the script file by converting it to a base64 string.
[Convert]::ToBase64String([IO.File]::ReadAllBytes(<path to the image file>))
You can paste the string into your script file as a variable.
$imgString = "iVBORw0KGg...
You can recreate the icon image using the .NET Framework.
$Image = [System.Windows.Media.Imaging.BitmapImage]::new()
$Image.BeginInit()
$Image.StreamSource = [System.IO.MemoryStream]::new([Convert]::FromBase64String(<base64string>))
$Image.EndInit()
In my script file, I'll use a helper function.
Function GetImage {
Param([string]$Base64String)
$Image = [System.Windows.Media.Imaging.BitmapImage]::new()
$Image.BeginInit()
$Image.StreamSource = [System.IO.MemoryStream]::new([Convert]::FromBase64String($Base64String))
$Image.EndInit()
#write the image as the output
$Image
}
I can then convert the base64 string into an image that I can use in the form.
$GraphicImage = [System.Windows.Controls.Image]@{
Source = GetImage $imgString
Width = 50
Height = 50
HorizontalAlignment = 'Left'
Margin = '25,5,0,0'
}
The output is no different than referencing an external file. This will, of course, increase your file size. My script went from from 6KB to 14KB. But, it is now completely portable.
Icons
The last graphical element you might want to include is an icon. You don't have to rely on Windows to supply an icon. You can specify any icon file.
$window = [System.Windows.Window]@{
Title = 'Service Detail'
Height = 350
Width = 500
WindowStartupLocation = 'CenterScreen'
Icon = Convert-Path ".\server.ico"
}
You should be able to specify any graphic file. PowerShell will scale it down to fit.
If you are looking for an icon, take a look at http://www.iconarchive.com/tag/public-domain Many of the icons are available for free for non-commercial use.

Like images, you can also embed the icon file in your script. Using the same code I showed earlier, convert the file to a base64 string, then reconstitute it and assign it as the property value.
Cursors
Finally, you may want to adjust the cursor on-the-fly. You can assign a cursor to the window using one of these properties.
PS C:\> Get-TypeMember system.windows.input.cursors -MemberType Property
Type: System.Windows.Input.Cursors
Name MemberType ResultType IsStatic IsEnum
---- ---------- ---------- -------- ------
AppStarting Property Cursor
Arrow Property Cursor
ArrowCD Property Cursor
Cross Property Cursor
Hand Property Cursor
Help Property Cursor
IBeam Property Cursor
No Property Cursor
None Property Cursor
Pen Property Cursor
ScrollAll Property Cursor
ScrollE Property Cursor
ScrollN Property Cursor
ScrollNE Property Cursor
ScrollNS Property Cursor
ScrollNW Property Cursor
ScrollS Property Cursor
ScrollSE Property Cursor
ScrollSW Property Cursor
ScrollW Property Cursor
ScrollWE Property Cursor
SizeAll Property Cursor
SizeNESW Property Cursor
SizeNS Property Cursor
SizeNWSE Property Cursor
SizeWE Property Cursor
UpArrow Property Cursor
Wait Property Cursor
I can set the cursor when creating the window.
$window = [System.Windows.Window]@{
Title = 'Service Detail'
Height = 350
Width = 500
WindowStartupLocation = 'CenterScreen'
Icon = $icon
Cursor = 'AppStarting'
}
Although, I'll probably never see this cursor. When the window loads, I'll set it to the default arrow cursor.
$window.Add_Loaded({ $window.cursor = 'Arrow' })
You will most likely change the cursor during click events.
$btnGetDetail.Add_Click({
$splat = @{
ComputerName = $txtComputerName.Text
ClassName = 'Win32_Service'
Filter = "DisplayName = '$($listDisplayName.SelectedItem)'"
}
#change the cursor
$window.cursor = 'Wait'
Get-CimInstance @splat | Select-Object -Property * -ExcludeProperty CIM* | Out-String | Write-Host
#artificially sleep to demonstrate the cursor change
Start-Sleep -Seconds 3
$window.cursor = 'Arrow'
})
When I change the computer name and click the button, the cursor changes.

Summary
I tried to keep code samples to a minimum to make this easier to read. You can download wpf-graphics.zip from Dropbox. Adding these types of elements makes your WPF PowerShell script more professional looking. I hope you'll try out my demo code and let me know what you think.