PowerShell is great at simplifying and automating your daily tasks. And it’s only natural that after you have automated or scripted most of your simple tasks, you start creating bigger scripts. If you work in a large environment, running for instance a script that uses WMI to query computers for data can take a long time to complete. The solution? Multi-threading of course. One way of doing this is using the built-in ‘Jobs’ cmdlets, but what this post is about is Runspaces.
Runspaces lets you create separate PowerShell sessions running in separate runspaces (hence the name), within the same process. This means that you won’t see any difference when looking at Task Manager (except CPU and memory will probably go up, depending on the number of runspaces you use).
Working with runspaces have for some been a daunting task, as it means you need to work with .NET directly. I have created a set of wrappers for working with runspaces that simplifies this and lets you work with functions that behave as common PowerShell cmdlets.
The following functions are included:
This function will create a new runspace pool. A runspace pool can be thought of as the object that is controlling the behaviour of you runspaces, as well as tying them all together. Setting up the runspace pool lets you control how many concurrent threads you want to run, as well as control snapins, modules, functions and variables that you want each runspace to have access to when spawned. The New-RunspacePool function returns a RunspacePool object that you will need to capture in a variable, as this is used together with the next function: New-RunspaceJob.
This is the function that takes the supplied code (in the form of a script block), creates a new runspace and injects the code together with any parameters (in the form of a hashtable) that you want the code to use. Each new runspace job is connected to the runspace pool, and the pool takes care of how many runspaces are current running at the same time, and will automatically start a new one when a new opening in the queue comes up. All the runspace jobs are saved in the global ‘runspaces’ variable. You can name your jobs as well, so if you are running several different jobs at the same time, it’s easy to differentiate between them when looking in the ‘runspaces’ array.
Receive-RunspaceJob will check if the job is finished and return the result if it is. This function have a ‘Wait’ parameter that you can use (together with a ‘Timeout’ parameter) to let it run until all jobs are finished (or the timeout value is exceeded). It also supports getting only runspaces belonging to a certain job, or even by the unique ID of each job.
Show-RunspaceJob will list out all jobs currently in the global runspaces array. It only have one parameter, ‘JobName’, that lets you list only runspaces belonging to a particular job.
Lastly we have the Clear-Runspaces function. The only thing this one does, is remove everything from the global runspaces variable (it is in fact deleting the variable). Hopefully you would never need this, but working with WMI I know from experience that sometimes a WMI session can hang, and it’s nice to be able to completely remove any jobs left.
Creating these functions, I had a goal of creating something that I could use both in scripts, but also directly from the console, and I hope I have succeeded. To get you going I have also created a basic template that you can use when running code against an array of computers. It is by no means a complete script, and should only be used as a starting-point you can work on, but it should be enough to give you a picture of how to use the different functions.
As always, if you spot any bugs, or have any suggestions for future improvements, or just need some input on how to use these functions, don’t hesitate to contact me!