Summary: Richard Siddaway introduces you to Windows PowerShell jobs.
Hey, Scripting Guy! I’ve just starting learning Windows PowerShell, and I have some long running tasks to perform. What’s the best way of running these tasks?
—LJ
Hello LJ,
Honorary Scripting Guy, Richard Siddaway, here today filling in for my good friend, The Scripting Guy. LJ, you need to use Windows PowerShell jobs for your long running tasks. Jobs are an area of Windows PowerShell that I feel is neglected. The headline piece of functionality in Windows PowerShell 2.0 was remoting. In many respects, Windows PowerShell jobs are just as important and beneficial, to your automation efforts as remoting has been.
This is the first of a series of posts that, hopefully, will shine the spotlight on Windows PowerShell jobs, remind people of their capabilities, and encourage their greater adoption. The full series comprises:
- Introduction to PowerShell jobs (this post)
- WMI and CIM Jobs
- Remote Jobs
- Scheduled Jobs
- Workflow Jobs
- Job Processes
- Jobs in the Enterprise
This introductory post will cover the basics of using Windows PowerShell jobs.
Jobs were introduced in Windows PowerShell 2.0 and helped to solve a problem inherent in the command-line tools. That is, if you start a long running task, your prompt is unavailable until the task finishes. As an example of a long running task, think of this simple Windows PowerShell command:
Get-ChildItem -Path c:\ -Recurse
If you run the command, you will get a full directory list of your C: drive. That will take some time. As an aside, if you are experimenting with performance counters in Windows PowerShell, running this command in a couple of consoles is a good way to give you CPU a work out.
Back when I was using VBScript, I would have multiple command prompts open—each running a script. That can get confusing and annoying. With Windows PowerShell, you can also run multiple consoles or instances of the Windows PowerShell ISE. But a better approach is to use a Windows PowerShell job.
To create a Windows PowerShell job, simply put your code into the script block of the Start-Job cmdlet:
Start-Job -ScriptBlock {Get-ChildItem -Path c:\ -Recurse}
The cmdlet will return information about the job as shown in the following screenshot.
Windows PowerShell will assign a name and numeric ID. You can see that the job type is a Background job (job types are explained in the Help file about_Job_Details), and that it’s running on the local machine. The final column shows (at least some of) the command that is running.
Note The numeric IDs that you see on your machine may vary from those displayed in this post. The important point is the relationship between the jobs and their IDs.
You can check on the progress of your jobs using Get-Job:
The same information is presented as when the job started.
You can use the job name or ID to pull back information on an individual job:
Get-Job -Id 4
Get-Job -Name Job4
Both commands return the same information about your job. It is interesting to use the –IncludeChildJob parameter, as shown in the next screenshot.
The job with an ID of 4 is the data you’ve been seeing all along. Now a job with an ID of 5 has suddenly appeared. Actually, it’s been there all the time because Windows PowerShell jobs created through Start-Job always consist of a parent job and a child job. The child job does the actual work. If you were running the job against a number of remote machines by using Invoke-Command and its –AsJob parameter, you would get one child job per remote machine.
When you manage jobs, anything you do to the parent job is automatically applied to any child jobs. Removing or stopping the parent job performs the same action on the child jobs. Getting the results of the parent job means you get the results of all the child jobs. As you will see later, you can access the child jobs directly to retrieve their data.
If you start another job as shown here…
…you’ll see that it is given an ID of 6. Windows PowerShell automatically assigns the next ID number, taking child job IDs into account. This can cause confusion to people new to Windows PowerShell jobs.
When a job has completed, you can retrieve the data stored in the job.
You can see that the job consists of a parent job (ID 6) and a child job (ID 7). You can use Receive-Job to get the data from the job. I added the –Keep parameter to prevent the default action of the data being deleted from the job. In a simple job, as in the example, you can access the data through the parent or child jobs:
Receive-Job -Id 6 -Keep
Receive-Job -Id 7 –Keep
When you have multiple child jobs, its usually easier to access the child jobs in turn:
$jobs = Get-Job -Name Job6 | select -ExpandProperty ChildJobs
foreach ($job in $jobs){Receive-Job -Job $job -Keep}
You’ve seen some of the cmdlets that are associated with Windows PowerShell jobs. The full set is:
- Get-Job
Gets Windows PowerShell background jobs that are running in the current session - Receive-Job
Gets the results of the Windows PowerShell background jobs in the current session - Remove-Job
Deletes a Windows PowerShell background job - Resume-Job
Restarts a suspended job - Start-Job
Starts a Windows PowerShell background job - Stop-Job
Stops a Windows PowerShell background job - Suspend-Job
Temporarily stops workflow jobs - Wait-Job
Suppresses the command prompt until one or all of the Windows PowerShell background jobs that are running in the session are complete
Suspend-Job and Resume-Job only apply to workflow jobs. You’ll learn more about them on day 5.
Each cmdlet has an associated Help file (remember that you can use Update-Help in Windows PowerShell 4.0 and Windows PowerShell 3.0). There is also an extensive set of about files:
- about_Jobs
- about_Job_Details
- about_Remote_Jobs
- about_Scheduled_Jobs
- about_Scheduled_Jobs_Advanced
- about_Scheduled_Jobs_Basics
- about_Scheduled_Jobs_Troubleshooting
One important point to note is that the simple Windows PowerShell jobs that you’ve seen so far are isolated to the Windows PowerShell session in which you started the job. If you close the session that contains the jobs, you will lose the jobs and your results unless you’ve saved the data to disk.
Another way to work with jobs is to create a variable that holds the job object:
You create the variable when you start the job:
$myjob = Start-Job -ScriptBlock {Get-ChildItem }
Notice in the screenshot that there is no output as the job starts. You can use the variable to display job information or any of the techniques you’ve seen already:
Get-Job -Id 10
Get-Job -Name Job10
You can also do this:
$myjob | Get-Job
The variable can be used directly when retrieving job data:
Receive-Job -Job $myjob –Keep
Many people like to name their jobs rather than allowing Windows PowerShell to name them:
Start-Job -ScriptBlock {Get-ChildItem } -Name myjob
Get-Job -Name myjob
Receive-Job -Name myjob -Keep
You can see the start and end times of your jobs:
If you want to see the execution time, you need to do a little bit of work:
Get-Job | select PSBeginTime, PSEndTime, @{N='ExecutionTime'; E={$_.PSEndTime - $_.PSBeginTime}}
The execution time is calculated by subtracting the begin time from the end time, which results in a timespan object:
Notice that the first job run, which was Get-ChildItem, runs recursively against the C: drive, and it took over 52 minutes to run. That’s a long time to have your Windows PowerShell prompt locked up by a single task. Use a job and you can carry on working.
You should always aim to clean up your Windows PowerShell environment and remove any lingering artifacts such as jobs, remoting, or CIM sessions before shutting Windows PowerShell. In the case of jobs, they will be removed for you if you don’t do it, but I think that is a sloppy approach. Especially when you can remove all old jobs in one pass:
Get-Job | Remove-Job
Or you can remove individual jobs:
Get-Job -Id 12 | Remove-Job
Remember that Name can also be used as an identifier.
Alternatively, you can filter on job completion:
Get-Job -State Completed | Remove-Job
You need to ensure that your jobs have all completed before you delete them and close Windows PowerShell:
Get-Job | where State -ne 'Completed'
One common mistake when people start working with jobs is to forget to retrieve the data before deleting the job. Try to make using jobs a process: Run the job, get the data, delete the job. That way, you clean up as you work with the jobs, and you don’t forget to retrieve your data.
That’s it for today. Tomorrow you’ll learn about WMI and CIM jobs. Bye for now.
~Richard
Thanks, Richard.
Richard Siddaway is based out of the UK, and he spends his time automating anything and everything, for Kelway, Ltd. A six-year Windows PowerShell MVP, Richard is a prolific blogger, mainly about Windows PowerShell (Richard Siddaway's Blog: Of PowerShell and Other Things) and a frequent speaker at user groups and Windows PowerShell conferences. He has written a number of Windows PowerShell books: PowerShell in Practice; PowerShell and WMI, PowerShell in Depth (co-author); PowerShell Dive (co-editor), and he is currently finishing Learn Active Directory Management in a Month of Lunches, which features lots of Windows PowerShell. All of the books are available from Manning Publications.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy