Wednesday, June 2, 2010

Things I learned from the Scripting Games 2010 #4: Background Jobs

Okay, so background jobs themselves aren’t really the subject of this post.  But a handy function to make efficient use of them, and a quick gotcha when using background jobs via Start-Job is.  Often times we need to execute the same code over and over, with one or two variations in each iteration.  That's what the function we’ll look at today is built to do.  The two mandatory parameters for this function, -scriptblock & -collection, really allow for some flexible backgrounding of similar tasks.  Take a moment and think, how many times have I needed to run the same or similar code against 10+ computers, users, files, directories, etc.  Probably doesn’t take too long before you have at least a few examples.

Before we get into the function let’s address the gotcha.  I learned about a new parameter to Start-Job in Event #5 this year, ArgumentList.  If you look at the documentation on this parameter it is a little bit misleading.  I’ve filed a documentation bug on connect should you wish to vote for it.  The short of it is, even though the parameter help doesn’t mention that the ArgumentList parameter works with the ScriptBlock parameter, it does.  Which is pretty important.  To illustrate this, try the following:
  1. Create a variable
    $arr = 1..5
  2. Start a background job that writes that variable to output
    $job = Start-Job –Scriptblock { Write-Output $arr }
  3. Receive the job
    Receive-Job $job
  4. Notice that there is no output, sadface.
So, what happened?  Why don’t we have any output?  The answer lies in two bits of information.  The first we get from Get-Help about_jobs:
Important: Background jobs that are started by using Start-Job or the AsJob parameter of Invoke-Command rely on the Windows PowerShell remoting infrastructure. To use these features, Windows PowerShell must be configured for remoting, even if the background job is run only on the local computer. For more information, see about_Remote_Requirements.
The second from Get-Help about_scopes:
Sessions: A session is an environment in which Windows PowerShell runs. When you create a session on a remote computer, Windows PowerShell establishes a persistent connection to the remote computer. The persistent connection lets you use the session for multiple related commands.
Because a session is a contained environment, it has its own scope, but a session is not a child scope of the session in which is was created. The session starts with its own global scope. This scope is independent of the global scope of the session. You can create child scopes in the session. For example, you can run a script to create a child scope in a session.
Aha!  The variable scope is separate and distinct inside of the scriptblock that we pass to Start-Job.  So we use the ArgumentList parameter to get the information that we need to the scriptblock.  Let’s see that in action:
  1. Create a variable:
    $arr = 1..5
  2. Start a background job, passing the variable as an argument:
    $job = Start-Job –Scriptblock { Write-Output $args } –ArgumentList $arr
  3. Receive the job
    Receive-Job $job
  4. Hey there’s our information!
Sweet.  Hopefully that information will make working with background jobs a little easier for you.  Below is a function I wrote as part of my solution for Event 5.  It takes a scriptblock and a collection of items & iterates through the collection passing each item into the scriptblock.  By default it displays a progress bar to keep you abreast of the progress of the jobs.  For detailed information on how you can use it and examples, import the function into your powershell session and run ‘Get-Help Start-BackgroundJobs’
Get the function here.