Hey, Scripting Guy! How Can I Start, Receive, and Manage Jobs in Windows PowerShell 2.0?

Hey, Scripting Guy! How Can I Start, Receive, and Manage Jobs in Windows PowerShell 2.0?

  • Comments 2
  • Likes

Bookmark and Share 

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! I have been thinking about writing a script and using the .NET Framework class system.thread to give me the ability to do multiple things at once. I need to perform a query of Active Directory Domain Services (AD DS) that will consume a decent amount of time. While waiting for that query to complete, I could be doing other things in my script. Therefore, I am thinking about implementing the system.thread class. If I do it as a function, I can easily re-use the code. What do you think? Is it doable? Have you written such a script yourself?

-- FS

 

Hey, Scripting Guy! AnswerHello FS,

Microsoft Scripting Guy Ed Wilson here. I am drinking a cup of English Breakfast tea this morning with a stick of cinnamon in it, while listening to Mariza sing Fado. It is a cool, but damp morning in Charlotte, North Carolina, and if I close my eyes, I can almost feel the gentle breezes of the Mediterranean Sea, and smell the pastel de nata. I spent a month in Lisbon a couple years ago while I was there teaching a series of Windows PowerShell classes, and it was a wonderful time. Here is a photograph of an old lighthouse that I snapped during a coastal drive with Luis, my friend and contact in Portugal.

Photograph Ed took of a Spanish lighthouse


In fact, my trick of putting cinnamon sticks in my tea is something I picked up in Lisbon. Of course, they put the cinnamon stick in their coffee, but I absconded with one of the cinnamon sticks and put it in my teaand it worked out really well. What is cool is that by the end of the week, several of the students switched to the joys of tea, and they were using the cinnamon trick I learned from them that they learned from me.

FS, to answer directly your threading question: I have not written a script using the system.thread class, but I have thought about it. The reason I never got around to it is Windows PowerShell 2.0 introduces the concept of jobs. Using a job, either from the Windows PowerShell console or from within a script, allows you to start a long running procedure and return immediately to doing something else.

To start a job, you must first enable remoting. This may sound a little strange, but Windows PowerShell uses the remoting infrastructure to run the jobs. To enable remoting use the Enable-PSRemoting cmdlet. Keep in mind this command must be run as an administrator, and you will need to right-click the Windows PowerShell icon and choose Run as administrator. You will, more than likely, (depending on your User Account Control settings), see a prompt that asks you, “Do you want to allow the following program to make changes to this computer?” The prompt is a little misleading because you have not done anything yet; therefore, Windows PowerShell makes no changes to the system. After Windows PowerShell is running with Administrator rights, use the Enable-PSRemoting cmdlet to configure Windows PowerShell for remoting. A prompt will appear, unless you use the –Force parameter.

Image of the Enable-PSRemoting prompt


If you run the Enable-PSRemoting cmdlet with the –Force parameter, Windows PowerShell displays the output seen in the following image.

Image of output when running Enable-PSRemoting cmdlet with -Force parameter


After remoting is configured, you can begin a new Windows PowerShell job by using the Start-Job cmdlet. The command is run as a job is placed in a script block, and the jobs are sequentially named Job1, Job2, and so on. This is shown here:

PS C:\> Start-Job -ScriptBlock { get-process }

Id              Name            State      HasMoreData     Location             Command
--              ----            -----      -----------     --------             -------
1               Job1            Running    True            localhost             get-process


PS C:\>

The jobs receive job IDs that are also sequentially numbered. The first job created in a Windows PowerShell console is always job ID 1. You can use either the job ID or the job name to obtain information about the job. This is shown here:

PS C:\> Get-Job -Name job1

Id              Name            State      HasMoreData     Location             Command
--              ----            -----      -----------     --------             -------
1               Job1            Completed  True            localhost             get-process


PS C:\> Get-Job -Id 1

Id              Name            State      HasMoreData     Location             Command
--              ----            -----      -----------     --------             -------
1               Job1            Completed  True            localhost             get-process


PS C:\>

After you see that the job has completed, you can receive the job. The Receive-Job cmdlet returns the same information that is returned if a job is not used. The Job1 output seen here (truncated to save space):

PS C:\> Receive-Job -Name job1

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
     62       9     1672       6032    80     0.00   1408 apdproxy
    132       9     2316       5632    62            1364 atieclxx
    122       7     1716       4232    32             948 atiesrxx
    114       9    14664      15372    48            1492 audiodg
    556      62    53928       5368   616     3.17   3408 CCC
     58       8     2960       7068    70     0.19    928 conhost
     32       5     1468       3468    52     0.00   5068 conhost
    784      14     3284       5092    56             416 csrss
    529      27     2928      17260   145             496 csrss
    182      13     8184      11152    96     0.50   2956 DCPSysMgr
    135      11     2880       7552    56            2056 DCPSysMgrSvc
 ... (truncated output)

After a job has been received, that is itthe data is gone, unless you save it to a variable. The following code illustrates this concept:

PS C:\> Receive-Job -Name job1
PS C:\>

What can be confusing about this is that the job still exists, and the Get-Job cmdlet continues to retrieve information about the job. This is shown here:

PS C:\> Get-Job -Name job1

Id              Name            State      HasMoreData     Location             Command
--              ----            -----      -----------     --------             -------
1               Job1            Completed  False           localhost             get-process


PS C:\>

As a best practice, use the Remove-Job cmdlet to delete remnants of completed jobs when you are finished using the job object. This will avoid confusion regarding active jobs, completed jobs, and jobs waiting to be processed. After a job has been removed, the Get-Job cmdlet returns an error if you attempt to retrieve information about the jobbecause it no longer exists. This is illustrated here:

PS C:\> Remove-Job -Name job1
PS C:\> Get-Job -Name job1
Get-Job : The command cannot find the job because the job1 name was not found. Verify the value of the Name parameter,
and then try the command again.
At line:1 char:8
+ Get-Job <<<<  -Name job1
    + CategoryInfo          : ObjectNotFound: (job1:String) [Get-Job], PSArgumentException
    + FullyQualifiedErrorId : JobWithSpecifiedNameNotFound,Microsoft.PowerShell.Commands.GetJobCommand

PS C:\>

When working with the job cmdlets, I like to give the jobs their own name. A job that returns process objects via the Get-Process cmdlet might be called getProc. A contextual naming scheme works better than trying to keep track of names such as Job1 or Job2. Do not worry about making your job names too long, because you can use wildcard characters to simplify the typing requirement. When you receive the job, make sure you store the returned objects in a variable. This is shown here:

PS C:\> Start-Job -Name getProc -ScriptBlock {get-process}

Id              Name            State      HasMoreData     Location             Command
--              ----            -----      -----------     --------             -------
3               getProc         Running    True            localhost            get-process


PS C:\> Get-Job -Name get*

Id              Name            State      HasMoreData     Location             Command
--              ----            -----      -----------     --------             -------
3               getProc         Completed  True            localhost            get-process


PS C:\> $procObj = Receive-Job -Name get*
PS C:\>

After you have the returned object in a variable, you can use the object with other Windows PowerShell cmdlets. One thing to keep in mind is that the object is deserialized. This is shown here where I use gm as an alias for the Get-Member cmdlet:

PS C:\> $procObj | gm


   TypeName: Deserialized.System.Diagnostics.Process

This means that not all the normal members from the System.Diagnostics.Process .NET Framework object are available. The normal methods are shown here (gps is an alias for the Get-Process cmdlet, gm is an alias for Get-Member, and –m is enough of the –membertype parameter to distinguish it on the Windows PowerShell console line):

PS C:\> gps | gm -m method


   TypeName: System.Diagnostics.Process

Name                      MemberType Definition
----                      ---------- ----------
BeginErrorReadLine        Method     System.Void BeginErrorReadLine()
BeginOutputReadLine       Method     System.Void BeginOutputReadLine()
CancelErrorRead           Method     System.Void CancelErrorRead()
CancelOutputRead          Method     System.Void CancelOutputRead()
Close                     Method     System.Void Close()
CloseMainWindow           Method     bool CloseMainWindow()
CreateObjRef              Method     System.Runtime.Remoting.ObjRef CreateObjRef(type requestedType)
Dispose                   Method     System.Void Dispose()
Equals                    Method     bool Equals(System.Object obj)
GetHashCode               Method     int GetHashCode()
GetLifetimeService        Method     System.Object GetLifetimeService()
GetType                   Method     type GetType()
InitializeLifetimeService Method     System.Object InitializeLifetimeService()
Kill                      Method     System.Void Kill()
Refresh                   Method     System.Void Refresh()
Start                     Method     bool Start()
ToString                  Method     string ToString()
WaitForExit               Method     bool WaitForExit(int milliseconds), System.Void WaitForExit()
WaitForInputIdle          Method     bool WaitForInputIdle(int milliseconds), bool WaitForInputIdle()

Methods from the deserialized object are shown here where I use the same command I used previously:

PS C:\> $procObj | gm -m method


   TypeName: Deserialized.System.Diagnostics.Process

Name     MemberType Definition
----     ---------- ----------
ToString Method     string ToString(), string ToString(string format, System.IFormatProvider formatProvider)


PS C:\>

A listing of the cmdlets that use the noun Job is shown here:

PS C:\> Get-Command -Noun job | select name

Name
----
Get-Job
Receive-Job
Remove-Job
Start-Job
Stop-Job
Wait-Job


PS C:\>

FS, as you can see, we have looked at four of the six cmdlets. That is all there is to starting, receiving, and managing jobs. Jobs Week will continue tomorrow when we will talk about…wait a minute.

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys

 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Nice article. Very excited to meet another fan of Fado! You must give a listen to Cristina Branco - http://www.cristinabranco.com/

    Pete Zerger, MVP

    SC OpsMgr and Essentials

  • And while I am here, thanks Ed for your recent presentation at our user group meeting in Atlanta.