ShowDialog() Sucks: Use ApplicationContext and Run Instead

Update Jan 7: Added KeyboardInterop so that TextBoxes will work.

One of the lessons learned when making Popups and NotifyIcons in PowerShell and WPF is that the WPF window must be run the the proper context.

I was tipped off to this by Denniver Reining’s PowerShell / NotifyIcon article and Johnny J’s “Doing a (C#) NotifyIcon program the right way“.

While neither of the articles were using WPF, extensive testing showed that using ApplicationContexts and Application.Run instead of ShowDialog() made WPF (and WinForms) work far better. The NotifyIcon issue took a number of days to resolve as I battled with unresponsiveness when clicking on the ContextMenu to Exit. Then it would take about 5 seconds to disappear.

For about a week, this was the story of my life:


And it seemed especially true after hiding the Powershell taskbar application. So I scoured the Internet and found that a lot of other people had the same issue. Just search Google for “NotifyIcon doesn’t disappear.

In addition, the mouse sometimes showed as busy when hoovering over the popup window itself and sometimes the right clicking worked only once.


System.Windows.Forms to the rescue (!?)

So none of the solutions I found worked, but I remembered that Denniver’s NotifyIcon script was responsive, so I went back and noticed his app ended with this important line


This information, along with Johnny J’s article about NotifyIcons helped me figure out that the following would probably work, even for WPF. Now these two lines, along with Hide-PowerShell are always included in my finalized PowerShell-based WPF scripts.

$appContext = New-Object System.Windows.Forms.ApplicationContext

According to Microsoft, “Application.Run begins running a standard application message loop on the current thread, with an ApplicationContext, which specifies the contextual information about an application thread.”

I’ll be honest, I’m probably not the best person to explain ApplicationContexts in depth. If you’d like to learn more, check out this article titled Use the ApplicationContext Class to Fully Encapsulate Splash Screen Functionality where the author goes into detail about the ApplicationContext class.

The code below (which is also the code from my previous post) shows a fully functioning WPF GUI App that runs in an application with an ApplicationContext. It also has a few other cool techniques you may enjoy.


Note that this code should’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!

# Add required assemblies
Add-Type -AssemblyName PresentationFramework, System.Drawing, System.Windows.Forms, WindowsFormsIntegration
# Setup the XAML
[xml]$script:xaml = '
# Create the form and set variables
$script:window = [Windows.Markup.XamlReader]::Load((New-Object System.Xml.XmlNodeReader $xaml))
$xaml.SelectNodes("//*[@Name]") | ForEach-Object { Set-Variable -Name ($_.Name) -Value $window.FindName($_.Name) -Scope Script }
# here's the base64 string of the image
# Create a streaming image by streaming the base64 string to a bitmap streamsource
$bitmap = New-Object System.Windows.Media.Imaging.BitmapImage
$bitmap.StreamSource = [System.IO.MemoryStream][System.Convert]::FromBase64String($base64)
# This is the pic in the middle
$image.source = $bitmap
# This is the icon in the upper left hand corner of the app
$window.Icon = $bitmap
# This is the toolbar icon and description
$window.TaskbarItemInfo.Overlay = $bitmap
$window.TaskbarItemInfo.Description = $window.Title
# Add Exit (Thanks, Ryan!)
$window.Add_Closing({[System.Windows.Forms.Application]::Exit(); Stop-Process $pid})
# Make PowerShell Disappear 
$windowcode = '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);' 
$asyncwindow = Add-Type -MemberDefinition $windowcode -name Win32ShowWindowAsync -namespace Win32Functions -PassThru 
$null = $asyncwindow::ShowWindowAsync((Get-Process -PID $pid).MainWindowHandle, 0) 

# Allow input to window for TextBoxes, etc

# Running this without $appContext and ::Run would actually cause a really poor response.

# This makes it pop up
# Create an application context for it to all run within. 
# This helps with responsiveness and threading.
$appContext = New-Object System.Windows.Forms.ApplicationContext 

Even though I had changed my own usage of WPF forms, it didn’t occur to me to blog about it until Doug Finke mentioned on Twitter that his WPF apps are sometimes unstable and crash. When I saw the ShowDialog() in his code, I knew exactly what the problem was, and knew I had to get the word out.

As of today, one of my servers has been running its WPF/PowerShell based-monitor for almost a month!


In conclusion, you should probably never use ShowDialog() to run your primary window in PowerShell unless you’re testing it (and I should probably update my old blog posts to clarify that).

Also, I don’t totally know what I’m doing, so take this with a grain of salt ;) It’s just worked better for me than ShowDialog(). I will probably revisit this one day with a PowerShell implementation of InitializeComponent().

Chrissy is a Cloud and Datacenter Management & Data Platform MVP who has worked in IT for over 20 years. She is the creator of the popular SQL PowerShell module dbatools, holds a master's degree in Systems Engineering and is coauthor of Learn dbatools in a Month of Lunches. Chrissy is certified in SQL Server, Linux, SharePoint and network security. You can follow her on Twitter at @cl.

Posted in PowerShell, WPF