Using PowerShell Remoting and Jobs
- 2/15/2013
- Understanding Windows PowerShell remoting
- Using Windows PowerShell jobs
- Using Windows PowerShell remoting: step-by-step exercises
- Chapter 4 quick reference
Using Windows PowerShell jobs
Windows PowerShell jobs permit you to run one or more commands in the background. Once you start the Windows PowerShell job, the Windows PowerShell console returns immediately for further use. This permits you to accomplish multiple tasks at the same time. You can begin a new Windows PowerShell job by using the Start-Job cmdlet. The command to 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 PSJobTypeName State HasMoreData Location -- ---- ------------- ----- ----------- -------- 10 Job10 BackgroundJob Running True localhost PS C:\>
The jobs receive job IDs that are also sequentially numbered. The first job created in a Windows PowerShell console always has a job ID of 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 job10 Id Name PSJobTypeName State HasMoreData Location -- ---- ------------- ----- ----------- -------- 10 Job10 BackgroundJob Completed True localhost PS C:\> Get-Job -Id 10 Id Name PSJobTypeName State HasMoreData Location -- ---- ------------- ----- ----------- -------- 10 Job10 BackgroundJob Completed True localhost PS C:\>
Once you see that the job has completed, you can receive the job. The Receive-Job cmdlet returns the same information that returns if a job is not used. The Job1 output is shown here (truncated to save space):
PS C:\> Receive-Job -Name job10 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)
Once a job has been received, that is it—the data is gone, unless you saved it to a variable or you call the Receive-Job cmdlet with the -keep switched parameter. The following code attempts to retrieve the information stored from job10, but as appears here, no data returns:
PS C:\> Receive-Job -Name job10 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 -Id 10 Id Name PSJobTypeName State HasMoreData Location -- ---- ------------- ----- ----------- -------- 10 Job10 BackgroundJob Completed False localhost
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. Once 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 job10 PS C:\> Get-Job -Id 10 Get-Job : The command cannot find a job with the job ID 10. Verify the value of the Id parameter and then try the command again. At line:1 char:1 + Get-Job -Id 10 + ~~~~~~~~~~~~~~ + CategoryInfo : ObjectNotFound: (10:Int32) [Get-Job], PSArgumentException + FullyQualifiedErrorId : JobWithSpecifiedSessionNotFound,Microsoft.PowerShell. Commands.GetJobCommand
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 and 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 a 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 PSJobTypeName State HasMoreData Location -- ---- ------------- ----- ----------- -------- 12 getProc BackgroundJob Running True localhost PS C:\> Get-Job -Name get* Id Name PSJobTypeName State HasMoreData Location -- ---- ------------- ----- ----------- -------- 12 getProc BackgroundJob Completed True localhost PS C:\> $procObj = Receive-Job -Name get* PS C:\>
Once you have the returned objects in a variable, you can use the objects 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 standard members from the System.Diagnostics.Process .NET Framework object are available. The default 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 Resume-Job Start-Job Stop-Job Suspend-Job Wait-Job
When starting a Windows PowerShell job via the Start-Job cmdlet, you can specify a name to hold the returned job object. You can also assign the returned job object in a variable by using a straightforward value assignment. If you do both, you end up with two copies of the returned job object. This is shown here:
PS C:\> $rtn = Start-Job -Name net -ScriptBlock {Get-Net6to4Configuration} PS C:\> Get-Job -Name net Id Name PSJobTypeName State HasMoreData Location -- ---- ------------- ----- ----------- -------- 18 net BackgroundJob Completed True localhost PS C:\> $rtn Id Name PSJobTypeName State HasMoreData Location -- ---- ------------- ----- ----------- -------- 18 net BackgroundJob Completed True localhost
Retrieving the job via the Receive-Job cmdlet consumes the data. You cannot come back and retrieve the returned data again. This code shown here illustrates this concept:
PS C:\> Receive-Job $rtn RunspaceId : e8ed4ab6-eb88-478c-b2de-5991b5636ef1 Caption : Description : 6to4 Configuration ElementName : InstanceID : ActiveStore AutoSharing : 0 PolicyStore : ActiveStore RelayName : 6to4.ipv6.microsoft.com. RelayState : 0 ResolutionInterval : 1440 State : 0 PS C:\> Receive-Job $rtn PS C:\>
The next example illustrates examining the command and cleaning up the job. When you use Receive-Job, an error message is displayed. To find additional information about the code that triggered the error, use the job object stored in the $rtn variable or the Get-Net6to4Configuration job. You may prefer using the job object stored in the $rtn variable, as shown here:
PS C:\> $rtn.Command Get-Net6to4Configuration
To clean up first, remove the leftover job objects by getting the jobs and removing the jobs. This is shown here:
PS C:\> Get-Job | Remove-Job PS C:\> Get-Job PS C:\>
When you create a new Windows PowerShell job, it runs in the background. There is no indication as the job runs whether it ends in an error or it’s successful. Indeed, you do not have any way to tell when the job even completes, other than to use the Get-Job cmdlet several times to see when the job state changes from running to completed. For many jobs, this may be perfectly acceptable. In fact, it may even be preferable, if you wish to regain control of the Windows PowerShell console as soon as the job begins executing. On other occasions, you may wish to be notified when the Windows PowerShell job completes. To accomplish this, you can use the Wait-Job cmdlet. You need to give the Wait-Job cmdlet either a job name or a job ID. Once you have done this, the Windows PowerShell console will pause until the job completes. The job, with its completed status, displays on the console. You can then use the Receive-Job cmdlet to receive the deserialized objects and store them in a variable (cn is a parameter alias for the -computername parameter used in the Get-WmiObject command). The command appearing here starts a job to receive software products installed on a remote server named hyperv1. It impersonates the currently logged-on user and stores the returned object in a variable named $rtn.
PS C:\> $rtn = Start-Job -ScriptBlock {gwmi win32_product -cn hyperv1} PS C:\> $rtn Id Name PSJobTypeName State HasMoreData Location -- ---- ------------- ----- ----------- -------- 22 Job22 BackgroundJob Running True localhost PS C:\> Wait-Job -id 22 Id Name PSJobTypeName State HasMoreData Location -- ---- ------------- ----- ----------- -------- 22 Job22 BackgroundJob Completed True localhost PS C:\> $prod = Receive-Job -id 22 PS C:\> $prod.Count 2
In a newly open Windows PowerShell console, the Start-Job cmdlet is used to start a new job. The returned job object is stored in the $rtn variable. You can pipeline the job object contained in the $rtn variable to the Stop-Job cmdlet to stop the execution of the job. If you try to use the job object in the $rtn variable directly to get job information, an error will be generated. This is shown here:
PS C:\> $rtn = Start-Job -ScriptBlock {gwmi win32_product -cn hyperv1} PS C:\> $rtn | Stop-Job PS C:\> Get-Job $rtn Get-Job : The command cannot find the job because the job name System.Management.Automation.PSRemotingJob was not found. Verify the value of the Name parameter, and then try the command again. At line:1 char:1 + Get-Job $rtn + ~~~~~~~~~~~~ + CategoryInfo : ObjectNotFound: (System.Manageme...n.PSRemotingJob: String) [Get-Job], PSArgumentException + FullyQualifiedErrorId : JobWithSpecifiedNameNotFound,Microsoft.PowerShell. Commands.GetJobCommand
You can pipeline the job object to the Get-Job cmdlet and see that the job is in a stopped state. Use the Receive-Job cmdlet to receive the job information and the count property to see how many software products are included in the variable, as shown here:
PS C:\> $rtn | Get-Job Id Name PSJobTypeName State HasMoreData Location -- ---- ------------- ----- ----------- -------- 2 Job2 BackgroundJob Stopped False localhost PS C:\> $products = Receive-Job -Id 2 PS C:\> $products.count 0
In the preceding list you can see that no software packages were enumerated. This is because the Get-WmiObject command to retrieve information from the Win32_Product class did not have time to finish.
If you want to keep the data from your job so that you can use it again later, and you do not want to bother storing it in an intermediate variable, use the -keep parameter. In the command that follows, the Get-NetAdapter cmdlet is used to return network adapter information.
PS C:\> Start-Job -ScriptBlock {Get-NetAdapter} Id Name PSJobTypeName State HasMoreData Location -- ---- ------------- ----- ----------- -------- 4 Job4 BackgroundJob Running True localhost
When checking on the status of a background job, and you are monitoring a job you just created, use the -newest parameter instead of typing a job number, as it is easier to remember. This technique appears here:
PS C:\> Get-Job -Newest 1 Id Name PSJobTypeName State HasMoreData Location -- ---- ------------- ----- ----------- -------- 4 Job4 BackgroundJob Completed True localhost
Now, to retrieve the information from the job and to keep the information available, use the -keep switched parameter as illustrated here:
PS C:\> Receive-Job -Id 4 -Keep ifAlias : Ethernet InterfaceAlias : Ethernet ifIndex : 12 ifDesc : Microsoft Hyper-V Network Adapter ifName : Ethernet_7 DriverVersion : 6.2.8504.0 LinkLayerAddress : 00-15-5D-00-2D-07 MacAddress : 00-15-5D-00-2D-07 LinkSpeed : 10 Gbps MediaType : 802.3 PhysicalMediaType : Unspecified AdminStatus : Up MediaConnectionState : Connected DriverInformation : Driver Date 2006-06-21 Version 6.2.8504.0 NDIS 6.30 DriverFileName : netvsc63.sys NdisVersion : 6.30 ifOperStatus : Up RunspaceId : 9ce8f8e6-1a09-4103-a508-c60398527 <output truncated>
You can continue to work directly with the output in a normal Windows PowerShell fashion, like so:
PS C:\> Receive-Job -Id 4 -Keep | select name name ---- Ethernet PS C:\> Receive-Job -Id 4 -Keep | select transmitlinksp* TransmitLinkSpeed ----------------- 10000000000