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
Hello 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.
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 tea—and 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.
If you run the Enable-PSRemoting cmdlet with the –Force parameter, Windows PowerShell displays the output seen in the following image.
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 it—the 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 job—because 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:> S tart-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&
0 comments