You might have noticed these two accelerators in the list I published recently along with the technique how to add your own type accelerators. It’s not immediately clear that they are related, but they very much are. This is going to be a fairly developer-oriented post, so there won’t be any hand-holding here.
[PowerShell]
The [PowerShell] accelerator is aliased to System.Management.Automation.PowerShell. It has one static method, Create(), which returns an instance of the class itself. In v1, if you wanted to set up asynchronous pipelines and do other fancy stuff, you had to get your hands dirty with Runspace and Pipeline instances and know how to wire them all together. The [PowerShell] class makes it a lot easier to do this; think of it a “PowerShell Pipeline Runner” class. At its simplest, you can just “new up” an instance, assign some script using the AddScript method and call Invoke(). You can run this asynchronously using the BeginInvoke/EndInvoke pattern which should be familiar to all .NET developers who’ve done a bit of threading work.
-
- $ps = [powershell]::create()
-
-
- [void] $ps.AddScript("ls")
-
-
- $results = $ps.Invoke()
-
-
- $ps.dispose()
[RunspaceFactory]
This accelerator is aliased to System.Management.Automation.Runspaces.RunspaceFactory. It has two static methods of interest, CreateRunspace and CreateRunspacePool. I’m going to focus on the latter because it has more interesting uses which you will see. This latter method lets you create a collection of Runspace instances that are essentially reusable. The great thing is that you don’t have to worry about any of the details. It just works; this leads me to the next part: queuing local pipeline jobs to be run in the background.
The pool you create can be constrained in many ways by using the various overloads of the CreateRunspacePool. You can even pass it a RunspaceConnectionInfo object so that the queued pipelines are run on remote servers. This is done by using the New-PSSession cmdlet to create a session to a remote machine running PowerShell 2.0 with WinRM configured correctly.
Queueing Pipelines to a Runspace Pool
This is where the magic really happens. Simply new up a PowerShell instance, assign the pool to it and run a command. New up as many PowerShell instnaces as you like, and as long as you assign each of them the same pool, the pool automagically looks after processing them as fast as it can and will never go over its hard limits you give it for the number of simultaneous runspaces. In this next script, I set up a pool of three runspaces. I then queue up six pipelines to be run. I am using the BeginInvoke method to start the command in the background. You’ll see when it runs that each command will make a beep of a certain frequency when it finally starts up. You can hear the first three jobs start up pretty much straight away, as each completes, another starts up. Magic!
-
-
-
- $pool = [runspacefactory]::CreateRunspacePool(1, 3)
- $pool.Open()
-
- write-host "Available Runspaces: $($pool.GetAvailableRunspaces())"
-
- $jobs = @()
- $ps = @()
- $wait = @()
-
-
- for ($i = 0; $i -lt 6; $i++) {
-
-
- $ps += [powershell]::create()
-
-
- $ps[$i].runspacepool = $pool
-
- $freq = 440 + ($i * 10)
- $sleep = (1 * ($i + 1))
-
-
- [void]$ps[$i].AddScript(
- "[console]::Beep($freq, 30); sleep -seconds $sleep")
-
-
- write-host "Job $i will run for $sleep second(s)"
- $jobs += $ps[$i].BeginInvoke();
-
- write-host "Available runspaces: $($pool.GetAvailableRunspaces())"
-
-
- $wait += $jobs[$i].AsyncWaitHandle
- }
-
-
- $success = [System.Threading.WaitHandle]::WaitAll($wait, 20000)
-
- write-host "All completed? $success"
-
-
- for ($i = 0; $i -lt 6; $i++) {
-
- write-host "Completing async pipeline job $i"
-
- try {
-
-
- $ps[$i].EndInvoke($jobs[$i])
-
- } catch {
-
-
- write-warning "error: $_"
- }
-
-
- $info = $ps[$i].InvocationStateInfo
-
- write-host "State: $($info.state) ; Reason: $($info.reason)"
- }
-
-
- write-host "Available runspaces: $($pool.GetAvailableRunspaces())"
Feel free to post questions or requests for clarification, but I did say it was a bit tough and developer-oriented. I have a hint that our good friend and fellow MVP, Karl Prosser (aka Klumsy), will be wrapping up some of this stuff into some nice admin-oriented background command functions.
IMPORTANT:
Due to the WaitAll call at line 40, this script will not work in an STA thread (i.e. in PowerShell ISE). Use PowerShell.EXE to run this script. Everything else will work in ISE fine.
Have fun!