Multithreading, Multitasking PowerShell

A key characteristic of Windows PowerShell v1 was that it could basically only do one thing at a time. Run "Get-WmiObject -class Win32_BIOS -computer server,1server2,server3,server4,server5" and you'd be waiting for a few minutes while the shell completed that task. With v2, however, we get a brand-new Background Jobs feature. With it, you can put long-running jobs onto a "background thread," enabling you to continue using the shell for other tasks. Once the job is complete, you can retrieve any results from it. Here's how they work.

Don Jones

July 22, 2010

4 Min Read
ITPro Today logo in a gray background | ITPro Today

A key characteristic of Windows PowerShell v1 was that it could basically only do one thing at a time. Run "Get-WmiObject -class Win32_BIOS -computer server,1server2,server3,server4,server5" and you'd be waiting for a few minutes while the shell completed that task. With v2, however, we get a brand-new Background Jobs feature. With it, you can put long-running jobs onto a "background thread," enabling you to continue using the shell for other tasks. Once the job is complete, you can retrieve any results from it. Here's how they work.
There are three main ways to start a job:
1. Use the -AsJob parameter of Get-WmiObject. This creates a new job with a default name, like "Job1" or "Job2."
2. Use the Start-Job cmdlet. This lets you specify a job name, and lets you create a job that runs one or more local commands or scripts.
3. Use the -AdJob parameter of Invoke-Command, causing your remote commands to run on a background thread. A -JobName parameter lets you specify a custom job name, if desired.
For example:
Get-WmiObject -class Win32_BIOS -computer server,1server2,server3,server4,server5 -AsJob
When you create a new job, you're always creating a single top-level "master" job, and at least one "child" job. For jobs that connect to multiple computers, you'll have a "child" job for each computer. Start-Job only starts jobs that run ON the local computer. That doesn't mean the commands WITHIN that job can't connect to other computers:
Start-Job -command { Get-Service -computername server1,server2 }
In that case, it might be more efficient to use remoting to distribute the workload and communicate over WinRM (assuming you've enabled remoting). Compare the above command with this one:
Invoke-Command -command { Get-Service } -computername server1,server2 -asjob
Here, the remote computers are actually running Get-Service and returning their results to you, all over the single port required by WinRM. Remoting isn't really the subject of this article, but hopefully this brief illustration gives you the basic idea.
You can run Get-Job to see what jobs are currently defined, running, completed, and so on; Stop-Job will kill a job that seems stuck, and Remove-Job will delete a job (and any results it may be storing) from memory. You can also use Wait-Job to cause the shell to pause until a specified job has completed; this can be useful inside of a script if you need to start a job and then have the script wait for it to finish. All of these job cmdlets accept parameters like -id or -name that let you specify which job or jobs you want to manage.
Here's a trick: Say you have a job, Job1, that is connecting to four computers. It will contain child jobs, one for each computer. To see them, run:
Get-Job -name Job1 | Format-List *
You'll see the property that lists the child jobs' names, which might be Job2, Job3, Job4, and Job5. You can use those job names to manage JUST one of the child jobs. Note that the master job will only show "Completed" as its status if EVERY child job also completed; the failure of a single child will result in a "Failed" status for the master job. Therefore, it's pretty useful to be able to check on the individual child jobs, as well.
When there are results for a job, you get them by using the Receive-Job cmdlet. Receiving results for a master job grabs all of the child jobs' results; you can also specify a -name or -id to just get the results for a child. Results are buffered in memory ONLY UNTIL they are received by you; once you run Receive-Job, by default, that memory buffer is empty - so be sure you store your results in a file, a variable, or someplace else if you expect to need those results again:
$results = Receive-Job -name Job2
Alternately, you can add the -keep parameter. That forces the shell to leave a copy of the results in the memory buffer:
Receive-Job -name Job3 -keep
Finally, keep in mind that jobs are actually one of PowerShell's "extensibility points." That means other developers can write cmdlets that create different kinds of jobs. They may behave differently, or be used differently. What I've written about here only covers the built-in job mechanism provided by Microsoft.
A quick reminder: My PowerShell home page offers access to the latest blog articles, along with PowerShell FAQs - why not bookmark it (your visits help pay to keep this blog alive)? You can also get the latest on Windows PowerShell in my Twitter feed @concentrateddon. And, if you’d like to download recent conference materials (slide decks, scripts, etc) I’ve delivered, visit ConcentratedTech.com. That site also contains details on upcoming classes and conferences.

Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like