Posey's Tips & Tricks
Why PowerShell Timers Cause Problems in GUI Environments, Part 1
Brien Posey explains why PowerShell timers can cause Windows Forms dashboards to freeze or fail to update, and why keeping GUI scripts responsive requires a different approach to timed events.
Recently, I set out to build a GUI based PowerShell script that displayed various metrics in near real time. In doing so however, I discovered a serious problem related to time based event handling. Ultimately, I was able to figure out a solution to the problem, but doing so was far more difficult than I ever would have guessed.
The problem that I ran into stems from the way that PowerShell handles timed events. PowerShell includes a built-in timer. You can create a timer object by writing code such as this:
$Timer = New-Object System.Timers.Timer
$Timer.Interval = 5000
$Timer.AutoReset = $True
$Timer.Start()
Register-ObjectEvent -InputObject $Timer -EventName Elapsed -Action {
Write-Host "Timer tick at $(Get-Date -Format HH:mm:ss)"
} | Out-Null
# Start timer
$Timer.Start()
Read-Host "Press Enter to exit"
$Timer.Stop()
$Timer.Dispose()
This simple block of code creates a timer object. Every 5000 milliseconds (every five seconds) the timer displays the time when the most recent timer tick occurred.
So with that in mind, my original approach to building a Windows Forms based GUI dashboard that refresh every few seconds involved simply building a GUI and then using a timer to update the data displayed on the GUI.
For those who might not be familiar with Windows Forms based GUIs, Windows Forms uses a label object to display a line of text on the screen. You create the label object and then add it to the form. Once you have done that, you can update the label's text any time you want. As such, my basic approach was to do something like this:
Add-Type -AssemblyName System.Windows.Forms
$Form = New-Object System.Windows.Forms.Form
$Form.Size = New-Object System.Drawing.Size(1024, 768)
$Label = New-Object System.Windows.Forms.Label
$Label.Font = New-Object System.Drawing.Font("Arial", 72)
$Label.AutoSize = $True
$Label.Location = New-Object System.Drawing.Point(250,300)
$Form.Controls.Add($Label) | Out-Null
$Timer = New-Object System.Timers.Timer
$Timer.Interval = 5000
$Timer.AutoReset = $True
$Timer.Start()
Register-ObjectEvent -InputObject $Timer -EventName Elapsed -Action {
$Label.Text = "Timer tick at $(Get-Date -Format HH:mm:ss)"
} | Out-Null
# Start timer
$Timer.Start()
$Form.ShowDialog() | Out-Null
$Timer.Stop()
$Timer.Dispose()
This script creates a Windows Forms GUI containing a single label object. I then set up a timer in exactly the same way as before. The only difference is that rather than using Write-Host to display a row of text, I am updating $Label.text. However, when I run the script, PowerShell displays a blank window.
So why does PowerShell show a blank window? It has to do with the fact that the timer runs in one thread, but the GUI runs in a completely different thread. Unfortunately, PowerShell requires that all UI updates, including updating label text, happen inside of the user interface thread.
Fortunately, there is a way of getting around this problem. There is actually a different timer object that you can use. This one is specifically dedicated to the GUI. You can create this timer object by using a command like this one:
Timer = New-Object System.Windows.Forms.Timer
As you can see, this timer is actually a part of System.Windows.Forms, which means that it executes within the same thread as the GUI. So problem solved, right? Not quite. The problem that I mentioned at the beginning of this article actually had nothing to do with the normal PowerShell timer. I already knew that there was a special timer that you had to use if the event handler was going to be making updates to the GUI interface. My problem was that although my script worked, the GUI became completely unresponsive. The data displayed within the GUI was being refreshed every few seconds, just as it was supposed to. However, none of the buttons or combo boxes worked. The interface was so unresponsive that I couldn''t even drag the window to a different location on the screen. In fact, I had to use Task Manager just to stop the script.
So why did the timer cause my user interface to become unresponsive? The problem stems from the fact that the timer runs in the same thread as the user interface. In other words, if you try to perform anything beyond the most basic task within the timer loop, the user interface comes to a grinding halt. The thread becomes busy with trying to process the task within the timer loop and that ties up the thread that would normally handle the user interface.
So this is the problem that I found myself having to deal with. I needed to update a GUI interface in near real time. I couldn't use a normal PowerShell timer, because the timer object is unable to update the GUI. At the same time, I couldn't use the Windows Forms timer either, because doing so locked up the GUI window.
As I said at the beginning of this article, I did eventually figure out a creative way of dealing with this problem. In doing so, I successfully created a GUI interface that updates every few seconds while also remaining responsive. I will tell you how I did it in Part 2.
About the Author
Brien Posey is a 22-time Microsoft MVP with decades of IT experience. As a freelance writer, Posey has written thousands of articles and contributed to several dozen books on a wide variety of IT topics. Prior to going freelance, Posey was a CIO for a national chain of hospitals and health care facilities. He has also served as a network administrator for some of the country's largest insurance companies and for the Department of Defense at Fort Knox. In addition to his continued work in IT, Posey has spent the last several years actively training as a commercial scientist-astronaut candidate in preparation to fly on a mission to study polar mesospheric clouds from space. You can follow his spaceflight training on his Web site.