Automation tails from the fox hole

This post is part of the Learning GUI Toolmaking Series, here on FoxDeploy. Click the banner to return to the series jump page!

Where we left off

If you’ve followed this series, you should know how to make some really cool applications, using WPF for the front-end and PowerShell for the code-behind.

What will now probably happen is you’ll make a cool app and go to show it off to someone with a three letter title and they’ll do something you never imagined, like drag a CSV file into the text box….(true story). Then this happens.

We don’t want this to happen. We want our apps to stay responsive!

In this post we’ll be covering how to implement progress bars in your own application, and how to multi-thread your PowerShell applications so they don’t hang when background operations take place. The goal here is to ensure that our applications DON’T do this.

Do you even thread, bro?

Here’s why this is happening to us…

if we are running all operations in the same thread, from rendering the UI to code behind tasks like waiting for something slow to finish, eventually our app will get stuck in the coding tasks, and the UI freezes while we’re waiting. This is bad.

Windows will notice that we are not responding to the user’s needs and that we’re staying late at the office too often and put a nasty ‘Not Responding’ in the title. This is not to mention the passive-aggressive texts she will leave us!

If thing’s don’t improve, Windows will then gray out our whole application window to show the world what a bad boyfriend we are.

Should we still blunder ahead, ignoring the end user, Windows will publicly dump us, by displaying a ‘kill process’ dialog to the user. Uh, I may have been transferring my emotions there a bit…

All of this makes our cool code look WAY less cool.

To keep this from happening and to make it easy, I’ve got a template available here which is pretty much plug-and-play for keeping your app responsive. And it has a progress bar too!

After defining a synchronized variable, we then proceed to create a runspace for the first thread of our app.

What’s a runspace?

This is a really good question. A runspace is a stripped down instance of the PowerShell environment. It basically tacks an additional thread onto your current PowerShell process, and away it goes.

Similar to a PowerShell Job, but they’re much, much quicker to spawn and execute.

However, where PSJobs are built-in and have tools like get-job and the like, nothing like that exists for runspaces. We have to do a bit of work to manage and control Runspaces, as you’ll see below.

Short version: a runspace is a super streamlined PowerShell tangent process with very quick spin up and spin down. Great for scaling a wide task.

So, back to the code, we begin by defining a variable, $syncHash which will by synchonized from our local session to the runspace thread we’re about to make. We then describe $newRunSpace, which will compartmentalize and pop-out the code for our app, letting it run on it’s own away from our session. This will let us keep using the PowerShell or ISE window while our UI is running. This is a big change from the way we were doing things before, which would lockup the PowerShell window while a UI was being displayed.

If we collapse the rest of the code, we’ll see this.

The entire remainder of our code is going into this variable called $pscmd. This big boy holds the whole script, and is the first thread which gets “popped out”.

The code ends on line 171, triggering this runspace to launch off into its own world with beginInvoke(). This allows our PowerShell window to be reused for other things, and puts the App in its own memory land, more or less.

Within the Runspace

Let’s look inside $pscmd to see what’s happening there.

Finally, something familiar! Within $pscmd on lines 10-47, we begin with our XAML, laying out the UI. Using this great tip from Boe, we have a new and nicer approach to scraping the XAML and search for everything with a name and mount it as a variable.

This time, instead of exposing the UI elements as $WPFControlName, we instead add them as members within $syncHash. This means our Console can get to the values, and the UI can also reference them. For example:

Even though the UI is running in it’s own thread, I can still interact with it using this $syncHash variable from the console

Thread Count: Two and climbing

Now we’ve got the UI in it’s own memory land and thread…and we’re going to make another thread as well for our code to execute within. In this next block of code, we use a coding structure Boe laid out to help us work across the many runspaces that can get created here. Note that this time, our synchronized variable is called $jobs.

This code structure sets up an additional runspace to do memory management for us.

For the most part, we can leave this as a ‘blackbox’. It is efficient and practical code which quietly runs for as long as our app is running. This coding structure becomes invoked and then watchs for new runspaces being created. When they are, it organizes them and tracks them to make sure that we are memory efficient and not sprawling threads all over the system. I did not create this logic, by the way. The heavy lifting has already been done for us, thanks to some excellent work by Joel Bennett and Boe Prox.

So we’re up to thread two. Thread 1 contains all of our code, Thread 2 is within that and manages the other runspaces and jobs we’ll be doing.

Now, things should start to look a little more familiar as we finally see an event listener:

We’re finally interacting with the UI again. on line 85, we register an event handler using the Add_Click() method and embed a scriptblock. Within the button, we’ve got another runspace!

This multi threading is key though to making our app stay responsive like a good boyfriend and keep the app from hanging.

Updating the Progress Bar

When the button is clicked, we’re going to run the code in its own thread. This is important, because the UI will still be rendered in its own thread, so if there is slowness off in ‘buttonland’, we don’t care, the UI will still stay fresh and responsive.

Now, this introduces a bit of a complication here. Since we’ve got the UI components in their own thread, we can’t just reach over to them like we did in the previous example. Imagine if we had a variable called $WPFTextBox. Previously, we’d change the $WPFTextBox.Text member to change the text of the box.

However, if we try that now, we can see that we get an error because of a different owner.

Exception setting Text The calling thread cannot access this object because a different thread owns it.

We actually created this problem for ourselves by pushing the UI into its own memory space. Have no fear, Boe is once again to the rescue here. He created a function Called-Update window, which makes it easy to reach across threads. (link)

The key to this structure is its usage of the Systems.Windows.Threading.Dispatcher class. This nifty little guy appears when a threaded UI is created, and then sits, waiting for update requests via its Invoke() method. Simply provide the name of a control you’d like to change, and the updated value.

Function Update-Window {
Param (
$Control,
$Property,
$Value,
[switch]$AppendContent
)
# This is kind of a hack, there may be a better way to do this
If ($Property -eq &amp;quot;Close&amp;quot;) {
$syncHash.Window.Dispatcher.invoke([action]{$syncHash.Window.Close()},&amp;quot;Normal&amp;quot;)
Return
}
# This updates the control based on the parameters passed to the function
$syncHash.$Control.Dispatcher.Invoke([action]{
# This bit is only really meaningful for the TextBox control, which might be useful for logging progress steps
If ($PSBoundParameters['AppendContent']) {
$syncHash.$Control.AppendText($Value)
} Else {
$syncHash.$Control.$Property = $Value
}
}, &amp;quot;Normal&amp;quot;)
}

We’re defining this function within the button click’s runspace, since that is where we’ll be reaching back to the form to update values. When I load this function from within the console, look what I can do!

Enter a caption

With all of these tools in place, it is now very easy to update the progress bar as we progress through our logic. In my case, I read a big file, sleep for a bit to indicate a slow operation, then update a text box, and away it goes.

If you’re looking to drag and drop some logic into your code, this is where you should put all of your slow operations.

Sources

That’s all there is to it! The hard part here was containing our app into separate threads, but hopefully with the template involved you can easily see where to drop you XAML and how to make your application hum along swimmingly!

I could not have done this post without the many examples provided by Boe Prox on his blog:

That’s because you can close the whole script and compile it as an exe.
(I added my user voice request for XAML to be added to the PowerShell ISE and also have Export script as exe… Which will keep software light on the machine – no need for VS)
🙂

Absolutely awesome Stephen. I’ve read countless articles of Boe, and his knowledge on the topic is great.
I’ll be using these techniques soon for one of my long running scripts. However, it’s going to be runspace inception.

This is a great post (and series)! This has been a huge help on a project I have been working on. I do have a quick question which is how to access input from a user when using this multi-thread setup. I have removed a lot of the code to make it shorter but here is what I’m trying but it’s not working. https://codeshare.io/q0u18
Thank you in advance!

Sometimes WordPress messes with my code because of its syntax highlighting. There’s a link to a github post in this post for just that reason! Thanks for the comment, I’ll fix the code when I can get on a pc ！

The line where you are updating the textblock with the values of $x is what is causing the hang. It’s listing 1-15,000,000, so it causes update-window to hang until $x can be completely parsed. I tried a foreach loop on that line and it eliminated the “Not responding…” message, but the window is still somewhat unresponsive.

You only need to include the function in a button or script block if you plan on running the script block within its own runspace. If not (like if the operation is really fast) then feel free to just call the function instead.

I responded a little bit earlier with a Read-Textbox function I found, not sure if my comment went through, Stephen might have to approve it for it to show up. However, in the meantime, I have discovered that adding the value to $synchash is definitely way easier. I’ve had luck so far just doing it like this:

I’m just starting to study Runspaces. I’ve read the stuff you cited from Boe Prox that compares them so favorably to Start-Job. Boe’s article doesn’t mention Windows Workflow, though. (I think it was not yet available when he wrote his article.)

How would using a Workflow compare to using a Runspace for handling a bunch of parallel tasks?

This blog series has been really useful. I have used it to build a batch printing tool . At the moment, the tool looks great and technically does what it needs to. The area I am trying to improve is around threads.

At the moment, the script will use a third party product to render the PDF documents and print them, but this process can take some time. Currently I have popped up a “please wait” box to the user, which, as you say will become (not responding) if the user tries to interact with it. Obviously this is not ideal. Also, I would like to provide the stdout information into the GUI as the print job is taking place, but at the moment it seems like an all or nothing approach.

This blogpost looked like it could answer my prayers, but I have hit a snag with trying to copy my code into XAML portion. When I try to hit run, and then return $synchhash I get a “cannot call a method on a null valued expression”.

Name : Error
Value : {You cannot call a method on a null-valued expression., You cannot call a method on a null-valued expression., You cannot call a method on a null-valued expression., Multiple ambiguous overloads found for
“Load” and the argument count: “1”….}

Since you are talking about breaking out runspaces into their own threads, and you reference the ISE, you may want to make mention that simply launching the ISE via double click runs it in STA (Single Thread Apartment) mode.

To avoid potential issues you might want to make a shortcut that launches the ISE with the -mta switch, which makes it run in Multi Thread Apartment mode. As a bonus, this also makes the ISE run in the same apartment model as the console, so you won’t see any different behavior if you, for example, run a script as a scheduled task. Not that it causes issues very often, but it can happen.

I am adding an output textbox to my GUI, I am able to get everything in to the textbox, but I would want the textbox to autoscroll down to the last line each time I add something to the textbox. Are you able to see what I am doing wrong with the following example code? It is in the second line I am trying to force it to scroll down after each update to the window:

In this lesson, you removed the try/catch when loading the WPF XML because everything is loaded in $pscmd and run in the default runspace. I would like to add the try/catch, but post the error messages back to the command-prompt if it could not load the WPF. Any idea how the initial runspace can talk back to the main process, i.e., command-line ?

Unfortunately, the try/catch code exists in the $pscmd within the initial runspace, so any errors are displayed out of sight within the runspace (I assume). I know there are errors because my WPF never displayed.

Were you ever able to figure out how to provide feedback back to the parent process that spawned the runspace in the first place.

Thank you.

P.S.

Also noticed that you edited the default WPF XML to get it to load in this lesson… specifically…

Good points man, and this highlights that I need to revisit this post and revise it for clarity. I agree, me switching the xaml header makes it incompatible with my previous walk through.

I’ll add that to the to do list. Also, good point about nesting try catch in with the runspaces. I am still trying to work through a good approach for threading and error handling. I plan to look into how this is handled in traditional app development, because I think there will be some good lessons I can learn and apply to PowerShell from there.

Hey,
Thanks for this series, I’ve followed every part and created a GUI for my admin tool. However, I had been putting off this last step for a while until I had my app created using the techniques discussed in Parts I-VI. Anyway, I’m having a bit of trouble with something that seems so simple. In the previous section I was populating a variable from text entered by a user as follows:
$Username = $WPFtxtbox_ADCmds_username.Text
I’ve tried the following but can’t seem to be able to read the text entered in the GUI
$username = $syncHash.txtbox_ADCmds_username.Text
and
$username = $syncHash.txtbox_ADCmds_username.Text.Value
Do I need to do something to push the user input into the $syncHash

Hi,
Thanks for the great post! It finally made me understand how to make and like GUI script, since when I first started looking at that, I got the hung application so didn’t really like it at the time 🙂 .
So I made a GUI script, which works great. My only question is how can you open it by double-clicking on it? I googled this, but so far no solution works (like create a shortcut,…). I’m thinking it might be because of the whole code being in a runspace?
Tom

Stephen, thanks to this series, I feel like I am being spoiled by PowerShell. I haven’t touched C since. Will definitely point my friends to this blog in the future when they are bamboozled by the speed at which my GUIs are created.

Hiya Stephen! Big fan of your blog and of foxes too! I hate to nag you, I’m certain you have better things to do, but I am desperate now. What would be the equivalent of using “Get-Job –Name Yourjobname1 | Stop-Job” or say, “ctrl + break” if I wanted to stop, something like an infinite ping, a loop or a very long running process in the runspace such as sending keystrokes for 10 straight hours, but without killing the UI? I got a responsive UI with jobs, but I was never able to make it read the strings from the UI’s input textboxes, and I got my synchash responsive UI thanks to your blog and a nice youtube bloke named Jim Moyle, it can read from the textbox strings, but I haven’t been able to figure out how to stop/abort the runspace function while on the fly, I just get to kill powershell, close the form and the runspace remains doing its thing. I tried messing around with the code used for the jobcleanup bit (.dispose() etc), but no luck there. An answer may grant you an eternal supply of cookies.
Here is my code, sorry if it looks messy, I love using hashtags:https://gist.github.com/esevmont/0042200ee0f7b2115483e2da894c6d4d

Hi again Stephen, I managed to get what I wanted to work with jobs (It’s wonderful what a little sleep can do huh). I’d like to replicate the same with SyncHash, but I got stuck at the “stop/abort/cancel” process part. Posting it as a reply to my previous comment in case it helps: https://gist.github.com/esevmont/6353dba29dfebfc3ebe8e0f30eac9138

I found PoshRSJobs. I guess that answers how to do the stop-job with runspaces huh! It also makes it easier to pass variables to the “jobs”, now I’m only stuck at the event handling to make the active $true flag work, i will give it a go with the “$using:” poshrsjob and see how it goes

Thanks for the article series, I’ve followed it along from beginning to end, and I’ve learned loads. At the completion of part IV I’d created a quite sophisticated GUI app (new AD user creation), which is working pretty much exactly as I need, although I did notice that it would constantly “crash” the PoSh session (whether ISE or console), which lead me to delve into part IV, as I assume it’s the resource starvation during the initial startup of the app which was causing the crash problem (It only *ever* crashed PoSh when starting up the app). I’d initially skim-read part IV & dismissed it as “that looks too difficult”, however I have persevered and I am making great progress, and more to the point, enhancing my understanding hugely. I do have a couple of questions/thoughts though…
thought: – having created the single-threaded app, the process of conversion to the multi-threaded version is/was quite tiresome!
Question: – I can’t currently understand the runspace cleanup element of the example… – I (think) I understand the principle of why it’s there and what it’s *supposed* to do – but for the life of me I cannot see that it ever actually *does* anything…
Every time my app starts, I can see 2/3 more runspaces get created (using get-runspace) – I even changed the code to name the runspaces to be more recognisable… as long as the app is running, I can see the IsCompleted property on the handles are false, the runspace.availability is busy, and as soon as I close the app, all those runspaces change to IsCompleted = True and runspace.availability = Available…all of which makes complete sense to me… – however, what I *don’t* see, – and thought I was expecting to see, is that the cleanup code should then remove those runspaces when the form is closed? – that never happens.. – during testing when I’m stopping & starting the app dozens of times, I end up with hundreds of opened runspaces, and that strikes me as probably being “a very bad thing” ?? – to try to understand what it was doing, I even added some diagnostic logging within the code (the usual “got here” kind of messages) – going out to a text logfile since I can’t simply write to the screen from inside the separated runspace… – I never see any of my diagnostic messages in the log, and it simply appears that it’s never doing anything… – NB this isn’t just with my app, – I’ve also tested extensively with your unmodified example from GitHub. It could be of course, that my log writes are throwing an error, but since (as was mentioned previously) I don’t see the error stream from the isolated runspaces, I’m not aware of any errors that occur within them, which leads to my next thought…

As each runspace is a powershell session, would I be right in imagining that each has it’s own set of system objects & variables, including the $error object? – Is there a way to add the error variable to the synchash object perhaps? – or any other way I can “get at” the $error variable inside the child runspaces? – especially since the runspaces are left intact after my app has closed, they would presumably still be available…

And reading that back to myself just gave me another thought… – could not the error stream be redirected to a text file in each runspace?

Hope you can help with some additional information, and many thanks for the uplift this has given my PowerShell skills so far!

I agree with you too, I could refresh the syntax and make the transition from a single threaded to multi-threaded GUI simpler in hindsight. However, I’d hate to edit an old blog post so I think I’ll revisit this and add a module to make it simpler to add and manage runspaces instead of all of the code I used in my walktrhough today. This will be a new entry in the series.

As for the issue you’re describing, would you mind making a GIST with the code and leave some notes in the gist as to how you ended up with dozens of threads? If you share it, I’ll review and try to see why it’s not taking out the garbage like it should.

I’ll admit that I barely understood runspaces and all of that dotnetty goodness when I first wrote that post. In my current job I’ve been writing c# for months now, so I think I could do better next time.

Hi There Stephen, I found this post extraordinarily helpful and am about 90% in understanding. The multi-threading capability is something I will make a point of building on.

However, I am having trouble with the Update-Window function, Powershell ISE always tells me this: “The term ‘Update-Window’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.”

It happens whenever I run it in either powershell ise and visual studio code – funnily enough it worked _once_ in VSC. I am stumped!

Update-Window function, Powershell ISE always tells me this: “The term ‘Update-Window’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.”