Adding Toolbar Icons to Your PowerShell WPF GUI

This post builds on some previous post titled Use base64 for Images in WPF-based PowerShell Forms.

Show-WpfIconExample.ps1, which can be found below and on github, demonstrates how to overlay the default PowerShell toolbar icon with an image that's been transformed to a base64 string to System.Windows.Media.ImageSource.

An icon overlay is the mini icon that sometimes shows up on top of a taskbar application icon.

Icon-Overlay

The overlay icon is controlled by $window.TaskbarItemInfo.Overlay. I'm actually unsure if the PowerShell toolbar icon can be replaced entirely ($window.Icon usually takes care of that) without a ton of code.

As a bonus, I also included how to change the taskbar popup description from "Windows PowerShell" or nothing to the title of the $window.

The secret sauce of this is $window.TaskbarItemInfo.Overlay = $bitmap $window.TaskbarItemInfo.Description = $window.Title

If you'd like to learn more about the flag of my people, the Cajuns, check out the Wikipedia article "Flag of Acadiana".

Note that this code can't be copy/pasted into the console because the PowerShell window will disappear before it can complete the paste. Be sure to download the script or paste the code below into a .ps1 file and execute. It does appear to work well in the ISE, though!

 1# THIS CODE CANNOT BE COPY/PASTED INTO THE CONSOLE. PLEASE SAVE IT AS A FILE AND EXECUTE.
 2 
 3# Add required assemblies
 4Add-Type -AssemblyName PresentationFramework, System.Drawing, System.Windows.Forms, WindowsFormsIntegration
 5 
 6# Setup the XAML
 7[xml]$script:xaml = '<Window 
 8        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 9        Title="Acadiana (Cajun) Flag" Height="240" Width="320" Background="Gray">
10 <Window.TaskbarItemInfo>
11         <TaskbarItemInfo/>
12 </Window.TaskbarItemInfo>
13    <Grid>
14        <Image Name="image" Height="64" Width="64"/>
15    </Grid>
16</Window>'
17 
18# Create the form and set variables
19$script:window = [Windows.Markup.XamlReader]::Load((New-Object System.Xml.XmlNodeReader $xaml))
20$xaml.SelectNodes("//*[@Name]") | ForEach-Object { Set-Variable -Name ($_.Name) -Value $window.FindName($_.Name) -Scope Script }
21 
22# here's the base64 string of the image
23$base64 = "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"
40 
41 
42# Create a streaming image by streaming the base64 string to a bitmap streamsource
43$bitmap = New-Object System.Windows.Media.Imaging.BitmapImage
44$bitmap.BeginInit()
45$bitmap.StreamSource = [System.IO.MemoryStream][System.Convert]::FromBase64String($base64)
46$bitmap.EndInit()
47$bitmap.Freeze()
48 
49# This is the pic in the middle
50$image.source = $bitmap
51 
52# This is the icon in the upper left hand corner of the app
53$window.Icon = $bitmap
54 
55# This is the toolbar icon and description
56$window.TaskbarItemInfo.Overlay = $bitmap
57$window.TaskbarItemInfo.Description = $window.Title
58 
59# Add Exit (Thanks, Ryan!)
60$window.Add_Closing({[System.Windows.Forms.Application]::Exit(); Stop-Process $pid})
61 
62# Make PowerShell Disappear 
63$windowcode = '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);' 
64$asyncwindow = Add-Type -MemberDefinition $windowcode -name Win32ShowWindowAsync -namespace Win32Functions -PassThru 
65$null = $asyncwindow::ShowWindowAsync((Get-Process -PID $pid).MainWindowHandle, 0) 
66
67# Allow input to window for TextBoxes, etc
68[System.Windows.Forms.Integration.ElementHost]::EnableModelessKeyboardInterop($window)
69
70# Running this without $appContext and ::Run would actually cause a really poor response.
71$window.Show()
72
73# This makes it pop up
74$window.Activate()
75 
76# Create an application context for it to all run within. 
77# This helps with responsiveness and threading.
78$appContext = New-Object System.Windows.Forms.ApplicationContext 
79[void][System.Windows.Forms.Application]::Run($appContext)

Up next, I'll be talking more about running your WPF GUIs using ApplicationContext instead of $window.ShowDialog()