Summary: Richard Siddaway introduces you to WMI and CIM jobs in Windows PowerShell.

Honorary Scripting Guy, Richard Siddaway, here today filling in for my good friend, The Scripting Guy. This is the second in 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:

  1. Introduction to PowerShell Jobs
  2. WMI and CIM Jobs (this post)
  3. Remote Jobs
  4. Scheduled Jobs
  5. Jobs and Workflows
  6. Job Processes
  7. Jobs in the Enterprise

Last time, you saw how to use the Windows PowerShell job cmdlets. But there is another way to start jobs. Many cmdlets have an –AsJob parameter, for instance:

Get-WmiObject [-Class] <string> [[-Property] <string[]>] [-Filter <string>] [-Amended] [-DirectRead] [-AsJob]

[-Impersonation <ImpersonationLevel>] [-Authentication <AuthenticationLevel>] [-Locale <string>]

[-EnableAllPrivileges] [-Authority <string>] [-Credential <pscredential>] [-ThrottleLimit <int>]

[-ComputerName<string[]>] [-Namespace <string>] [<CommonParameters>]

You would normally use Get-WmiObject like this:

£> Get-WmiObject -Class Win32_ComputerSystem

Domain              : WORKGROUP

Manufacturer        : Microsoft Corporation

Model               : Surface Pro 2

Name                : RSSURFACEPRO2

PrimaryOwnerName    : richard

TotalPhysicalMemory : 8506142720

The –AsJob parameter is used to turn this into a Windows PowerShell job:

Image of command output

-AsJob is a switch parameter. It doesn’t allow you to configure the job name or any of the other aspects you saw when using Start-Job. One thing to notice is the PSJobTypeName (it is WmiJob). The command is being run as a Windows PowerShell job, but it runs in a different process to Windows PowerShell jobs that are initiated with Start-Job. You’ll learn more about the way different jobs run when we look at job processes in Part 6 of this series.

If you are running a mixture of job types, for instance WMI jobs and Background jobs, you can use the job type in your searches. Unfortunately, Get-Job doesn’t have a parameter to facilitate this search, so you have to fall back on Where-Object:

Get-Job | where PSJobTypeName -eq 'WmiJob'

Get-Job | where PSJobTypeName -eq 'BackgroundJob'

Alternatively, you may want to see all of the jobs, but have them sorted by type:

Get-Job | sort PSJobTypeName

This will display the jobs by type. But ideally, I’d like them displayed in historical order within type. This requires you to add an additional property to the sort:

Get-Job | sort PSJobTypeName, Id

Thinking about it, this would be a great addition to Get-Job. You could create a proxy function for Get-Job that added two parameters:  ByType and ByTypeHistorical, which would display the data in the appropriate sort order. That would make an interesting post for some time in the future.

After you’ve started your WMI-based job, you can manage the job with the standard Windows PowerShell job cmdlets.

Image of command output

Get-Job will display the jobs that exist on your system. Receive-Job will retrieve the data from the job, and Remove-Job will delete the job for you.

The WMI cmdlets are frequently used to work with remote machines, it’s one of their great attractions. What happens when you combine the –AsJob and –ComputerName parameters? The job runs on the local machine, but it uses the –ComputerName parameter to access the remote machine. As far as Windows PowerShell is concerned, it’s just another job.

If you specify multiple computers to Get-WmiObject and run the command as a job, Windows PowerShell will run one child job per computer. There is a limit on the number of child jobs that can run simultaneously. By default 32 child jobs can be run at one time. This number can be modified by using the –ThrottleLimit parameter.

You may be tempted to increase the number of simultaneous operations to match the maximum number of machines that you want to access. This isn’t necessarily a good idea because each child job will run in its own process. You could end up consuming all of the client’s resources, which will cause a failure. I would recommend that if you decide to increase the number of simultaneous operations, you slowly ramp up the –ThrottleLimit setting while closely monitoring the resources that are being consumed on the machine.

So far, you’ve only seen Get-WmiObject use the –AsJob parameter. The other WMI cmdlets also have this parameter:

  PS C:\Scripts> Get-Help *wmi* -Parameter AsJob

Name

----

Get-WmiObject

Invoke-WmiMethod

Remove-WmiObject

Set-WmiInstance

Register-WmiEvent doesn’t have an –AsJob parameter. This makes sense because jobs don’t run in your current Windows PowerShell session—they use a separate process. You want to register the event in your current process, not in some random other process.

$proc = Get-WmiObject -Class Win32_Process -Filter "Name = 'notepad.exe'"

Remove-WmiObject -InputObject $proc  -AsJob

The object is removed immediately. There isn’t any data to be retrieved from the job.

A similar process can be used for Invoke-WmiMethod:

$proc = Get-WmiObject -Class Win32_Process -Filter "Name = 'notepad.exe'"

Invoke-WmiMethod -InputObject $proc -Name Terminate –AsJob

This time, there is data returned in the job:

PS C:\Scripts> Receive-Job -Id 14 -Keep

__GENUS          : 2

__CLASS          : __PARAMETERS

__SUPERCLASS     :

__DYNASTY        : __PARAMETERS

__RELPATH        :

__PROPERTY_COUNT : 1

__DERIVATION     : {}

__SERVER         :

__NAMESPACE      :

__PATH           :

ReturnValue      : 0

PSComputerName   :

In this case, WMI returns the standard data from a method invocation. ReturnValue is the important property. A value of zero indicates success, and any other value indicates the method call failed.

Windows PowerShell 3.0 saw the introduction of a new API for accessing WMI. One of the outcomes of this was the introduction of the CIM cmdlets:

PS C:\Scripts> Get-Command *Cim* -CommandType cmdlet | select Name

Name

----

Get-CimAssociatedInstance

Get-CimClass

Get-CimInstance

Get-CimSession

Invoke-CimMethod

New-CimInstance

New-CimSession

New-CimSessionOption

Register-CimIndicationEvent

Remove-CimInstance

Remove-CimSession

Set-CimInstance

Get-CimInstance is analogous to Get-WmiObject, and it is used in a similar manner:

Get-CimInstance -ClassName Win32_ComputerSystem

However, if you look at the syntax of Get-CimInstance, there is one glaring omission. There isn’t any –AsJob parameter:

PS C:\Scripts> Get-Command Get-CimInstance -Syntax

Get-CimInstance [-ClassName] <string> [-ComputerName <string[]>] [-KeyOnly] [-Namespace <string>]

[-OperationTimeoutSec <uint32>] [-QueryDialect <string>] [-Shallow] [-Filter <string>] [-Property <string[]>]

[<CommonParameters>]

Get-CimInstance -CimSession <CimSession[]> -Query <string> [-ResourceUri <uri>] [-Namespace <string>]

[-OperationTimeoutSec <uint32>] [-QueryDialect <string>] [-Shallow] [<CommonParameters>]

Get-CimInstance [-InputObject] <ciminstance> -CimSession <CimSession[]> [-ResourceUri <uri>] [-OperationTimeoutSec <uint32>] [<CommonParameters>]

Get-CimInstance [-ClassName] <string> -CimSession <CimSession[]> [-KeyOnly] [-Namespace <string>]

[-OperationTimeoutSec <uint32>] [-QueryDialect <string>] [-Shallow] [-Filter <string>] [-Property <string[]>]

[<CommonParameters>]

Get-CimInstance -CimSession <CimSession[]> -ResourceUri <uri> [-KeyOnly] [-Namespace <string>] [-OperationTimeoutSec <uint32>] [-Shallow] [-Filter <string>] [-Property <string[]>] [<CommonParameters>]

Get-CimInstance [-InputObject] <ciminstance> [-ResourceUri <uri>] [-ComputerName <string[]>] [-OperationTimeoutSec <uint32>] [<CommonParameters>]

Get-CimInstance -Query <string> [-ResourceUri <uri>] [-ComputerName <string[]>] [-Namespace <string>]

[-OperationTimeoutSec <uint32>] [-QueryDialect <string>] [-Shallow] [<CommonParameters>]

Get-CimInstance -ResourceUri <uri> [-ComputerName <string[]>] [-KeyOnly] [-Namespace <string>] [-OperationTimeoutSec <uint32>] [-Shallow] [-Filter <string>] [-Property <string[]>] [<CommonParameters>]

If you want to use the CIM cmdlets in a job, you need to initiate the job with Start-Job:

Start-Job  -ScriptBlock {Get-CimInstance -ClassName Win32_ComputerSystem}

The standard job cmdlets are used to manage the job, for instance:

PS C:\Scripts> Receive-Job -Id 16 -Keep | fl 

Domain              : WORKGROUP

Manufacturer        : Microsoft Corporation

Model               : Surface 2

Name                : RSSURFACE2

PrimaryOwnerName    : Richard

TotalPhysicalMemory : 2094624768

The other cmdlets, such as Invoke-CimMethod can also be run as jobs by using Start-Job.

The fact that the CIM cmdlets don’t have an –AsJob parameter isn’t a major issue. You simply need to remember to use the job cmdlets when you want to run the CIM cmdlets as jobs. However, it gets confusing when you discover the CDXML cmdlets.

CDXML (cmdlets over objects) is a way to create Windows PowerShell functionality by taking a WMI class, wrapping it in some XML, and saving it on your module path. At its simplest, a CDXML module will look like this:

<?xml version='1.0' encoding='utf-8'?>
<PowerShellMetadata xmlns='http://schemas.microsoft.com/cmdlets-over-objects/2009/11'>
  <Class ClassName='ROOT\cimv2\Win32_NetworkAdapterConfiguration'>
    <Version>1.0</Version>
    <DefaultNoun>NetworkAdapterConfiguration</DefaultNoun>

    <InstanceCmdlets>
      <GetCmdletParameters DefaultCmdletParameterSet='DefaultSet'>
             
      </GetCmdletParameters>
    </InstanceCmdlets> 
  </Class>
 
</PowerShellMetadata>

The class in use is Win32_NetworkAdapterConfiguration. Notice that the namespace and class name are used.  You also need to add a default noun. In this case, it is NetworkAdapterConfiguration.

Save the file as NetworkAdapterConfiguration.cdxml (or whatever you want to call it). You can then load it as:

Import-Module .\NetworkAdapterConfiguration.cdxml

It will auto-load if it’s on your module path. The cmdlet is then available for use:

Get-NetworkAdapterConfiguration

You can add search parameters and use the Win32_NetworkAdapterConfiguration methods to create additional cmdlets.

If you look at the syntax of your new cmdlet, you’ll see this:

£> Get-Command Get-NetworkAdapterConfiguration -Syntax 

Get-NetworkAdapterConfiguration [-CimSession <CimSession[]>] [-ThrottleLimit <int>] [-AsJob] [<CommonParameters>]

You get -CimSessions and –AsJob parameters without any work.

£>  Get-NetworkAdapterConfiguration -AsJob 

Id     Name            PSJobTypeName   State         HasMoreData     Location

--     ----            -------------   -----         -----------     --------

6      CimJob3         CimJob          Running       True            RSSURFACEPRO2

Notice the job type is now CimJob rather than the WmiJob we saw earlier. Get-Job and the other job cmdlets work as normal on these jobs.

You may be wondering why I’ve discussed CDXML because it’s a fairly exotic topic in the Windows PowerShell world. The important point is that over 60% of the Windows PowerShell functionality since Windows Server 2012 and Windows 8 is CDXML based. If you examine the C:\Windows\System32\WindowsPowerShell\v1.0\Modules subfolders, you will find many .cdxml files. Knowing that you can use these cmdlets directly in jobs will save you some work.

That’s it for today. Tomorrow you’ll learn about running jobs on remote computers. Bye for now.

~Richard

Thanks for writing this interesting series, Richard.

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