CIM vs. WMI CmdLets – The top reasons I changed over

Some time ago I moved most of my WMI related work from the “deprecated” PowerShell WMI CmdLets (Get-WMIObject, Invoke-WMIMethod, etc.) over to the “new” CIM CmdLets (Get-CimInstance, Invoke-CimMethod, etc …).
It wasn’t really because they are “deprecated”. Microsoft might treat them this way, but they will for sure be around for quite some time. It’s like VBScript, it still has it’s purpose and it will stick there. You can find a very good Introduction to CIM CmdLets on the Windows PowerShell Blog. For the typical user (so you and me 😉 ) there are often only small differences and for most of the stuff you typically do, they are kind of interchangeable.
What leads to the question, why should one use the new CIM CmdLets?
My personal top three benefits that actually made me use the CIM CmdLets are:
Nr. 3 – The CIM session
On default all CIM CmdLets support the ComputerName parameter that allows you to connect to a remote computer to execute the commands. Pretty much the same as the WMI CmdLets. However they all also have a CimSession parameter, that takes a Session object.
The CimSession allows you to use different credentials and even different authentication methods:
1 2 3 4 5 6 |
$Cred = Get-Credential $Session = New-CimSession -ComputerName CM01 -Credential $Cred $BasicSession = New-CimSession -ComputerName CM01 -Credential $Cred -Authentication Basic Get-CimInstance -CimSession $Session -ClassName Win32_OperatingSystem |
On default, the CIM CmdLets use WS-Man (Web Services – Management) as protocol when connecting to a remote computer. On Windows based computers, that happens via WinRM (Windows Remote Management). However, as this is an industrie standard, the CIM CmdLets aren’t restricted to Windows at all. They can be used for basically any device or OS that supports WS-Man. Please have a look on Managing Linux via OMI, where Bartek Bielawski manages a CentOS box via PowerShell.
In addition, if e.g. WinRM is not enabled on the box, the CIM CmdLets can still fall back to DCOM (the protocol used by the WMI CmdLets)
1 |
New-CimSession –ComputerName CM01 –SessionOption (New-CimSessionOption –Protocol DCOM) |
E.g. a small snippet that would allow you to connect via WS-MAN on default and fall back to DCOM automatically
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$SessionParams = @{} if ($PSBoundParameters['Credential']) {$SessionParams.Credential = $Credential} $SessionParams.ComputerName = $ComputerName $WSMan = Test-WSMan -ComputerName $ComputerName -ErrorAction SilentlyContinue if (($WSMan -ne $null) -and ($WSMan.ProductVersion -match 'Stack: ([3-9]|[1-9][0-9]+)\.[0-9]+')) { $Session = New-CimSession @SessionParams } if ($Session -eq $null) { $SessionParams.SessionOption = (New-CimSessionOption -Protocol Dcom) $Session = New-CimSession @SessionParams } |
Please check the “Get-CMSession” function from my ConfigMgr Module for the complete code of above snippet.
Nr. 2 – Parallel execution – “Fan out”
In my daily job, I often have to work with computers located around the globe. I need to collect information or execute some tasks on multiple of them, preferably in the shortest time possible.
Using the WMI CmdLets you can pass in an array of computer names. However those are processed sequentially. If they are now on a WAN connection, this just sums up. To be able to reduce the total time, you have to implement your own methods to execute it in parallel using e.g. Invoke-Async, Background Jobs, Windows Powershell Workflow with Foreach -Parallel, or similar options. In opposite working with multiple computers using the CIM CmdLets simply works like a charm and is pretty fast.
I did my own very basic speed comparison testing using 5 computers located at 5 different remote locations.
1 2 3 4 5 6 7 8 9 10 |
(1..5) | foreach { $MeasureWMI += ,(Measure-Command {$WMIServers = Get-WmiObject -ComputerName $Servers -Class Win32_ComputerSystem}) $MeasureCIM += ,(Measure-Command {$CIMServers = Get-CimInstance -ComputerName $Servers -ClassName Win32_ComputerSystem}) } $AverageWMI = ($MeasureWMI | Measure-Object -Average -Property TotalSeconds).Average $AverageCIM = ($MeasureCIM | Measure-Object -Average -Property TotalSeconds).Average Write-Host "WMI: $AverageWMI seconds." Write-Host "CIM: $AverageCIM seconds." |
I left out the definition for $Servers as that’s just an array of above mentioned 5 remote computers.
And the results speak for themselve
1 2 |
WMI: 35.40861808 seconds. CIM: 2.51654976 seconds. |
I’ve seen some other speed comparisons that often come to the conclusion, that the WMI CmdLets would be as fast or even faster than the CIM CmdLets. But mostly they were either running hundreds of iterations to the same, sometimes even the local computer. Or they had put it into a custom for-each loop which would then basically disable the built-in capabilities of the CIM CmdLets in terms of doing parallel work.
In case you need to work with Sessions, this works the exact same way as using plain computer names
1 2 3 |
$AllSessions = New-CimSession -ComputerName $Servers -Credential $Cred Get-CimInstance -CimSession $AllSessions -ClassName Win32_ComputerSystem |
or pipe the computer names into it an the CIM CmdLets will use the appropriate session object automatically
1 2 3 |
$AllSessions = New-CimSession -ComputerName $Servers -Credential $Cred $Servers = Get-CimInstance -ClassName Win32_ComputerSystem |
Do your own testing, but for me the speed and simplicity of using the CIM CmdLets in a distributed environment was convincing.
Nr. 1 – Invoke methods with named parameters
Really? That’s your Number 1 reason?
Yes!!! 😉
When working with SCCM, a lot of the things that you need to do involve calling WMI methods. Let’s have a look on a method call that I described already in this post.
Calling WMI methods using VBScript was (and still is) a pain:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
strComputer = "." Set objWMIService = GetObject("winmgmts:" _ & "{impersonationLevel=impersonate}!\\" & strComputer _ & "\root\sms\Site_TST") Set objClientOperation= objWMIService.Get("SMS_ClientOperation") ' Obtain an InParameters object Set objInParam = objClientOperation.Methods_("InitiateClientOperation").inParameters.SpawnInstance_() ' Add the input parameters. objInParam.Properties_.item("Type") = 1 objInParam.Properties_.item("TargetCollectionID") = "SMS00001 objInParam.Properties_.item("TargetResourceIDs") = Array(123456,234567) objInParam.Properties_.item("RandomizationWindow") = NULL Set objOutParams = objClientOperation.ExecMethod_("InitiateClientOperation", objInParam) |
It gets a whole lot easier using Invoke-WMIMethod:
1 2 3 4 |
Invoke-WMIMethod -Class SMS_ClientOperation ` -Name "InitiateClientOperation" ` -ArgumentList @($null, "SMS00001", @(123456,234567), 1) ` -Namespace root\sms\site_XYZ |
As you can see while it’s definitely easier than VBScript, it still has the drawback, that you need to supply the method parameters in a certain order. And if you think that this would be the order as specified in the documentation, you might be wrong. The documentation for the InitiateClientOperation method specifies the arguments in the following order:
- Type
- TargetCollectionID
- RandomizationWindow
- TargetResourceIDs
However, the method actually expects the arguments in the following order:
- RandomizationWindow
- TargetCollectionID
- TargetResourceIDs
- Type
What? Yes!!!
As you can imagine, this makes it pretty cumbersome working with methods sometimes.
The second option would be to get a list of parameters first and then pass them as an object.
1 2 3 4 5 6 7 8 9 |
$WMIConnection = [WMICLASS]"\\.\root\sms\Site_TST:SMS_ClientOperation" $ClientOperation= $WMIConnection.psbase.GetMethodParameters("InitiateClientOperation") $ClientOperation.Type = 1 $ClientOperation.TargetCollectionID = "SMS00001" $ClientOperation.TargetResourceIDs = @(123456,234567) $ClientOperation.RandomizationWindow = $null $WMIConnection.psbase.InvokeMethod("InitiateClientOperation",$ClientOperation,$Null) |
This is working as well. Now let’s have a look on how Invoke-CimMethod handles the same call:
1 2 3 4 5 6 7 8 9 10 |
$Args= @{ Type=1 TargetCollectionID="SMS00001" TargetResourceIDs=@(123456,234567) RandomizationWindow=$null } Invoke-CimMethod -ClassName SMS_ClientOperation ` -MethodName "InitiateClientOperation" ` -Arguments $Args ` -Namespace root\sms\site_TST |
I pesonally prefer that way of handling methods and passing in a hashtable that contains the name and values for the method parameters.
For sure there are other interesting aspects as well like the easy serialization/deserialization of objects via Export-CliXML and Import-CliXML, listing of classes using Get-CimClass, the automated conversion of e.g. the WMI DateTime format into the DateTime format used within .Net and PowerShell and a bunch of others.
Drawbacks
There are two issues, that you will run into, when working with the CIM CmdLets epsecially in SCCM. Sooner or later.
1. Working with embedded classes without key property
SCCM is often using embedded classes. For example the SMS_CategoryInstance class that represents a Category in SCCM, is storing the localized category name(s) in an embedded class called SMS_Category_LocalizedProperties.
If you now try to create a new instance of this class using
1 2 3 4 5 6 7 |
$Properties = @{ CategoryInstanceName = "Test Category" LocaleID = 1033 } New-CimInstance -Namespace root\sms\site_TST ` -ClassName SMS_Category_LocalizedProperties ` -Property $Properties |
you will end up with an error 0x80041089. According to the WMI Error constants, that’s WBEM_E_NO_KEY or better “User attempted to put in an instance with no defined key.” Looking at the definition of SMS_Category_LocalizedProperties again, it gets obvious, that this class doesn’t even have a key property. And now you are stuck. You can’t create an instance of this embedded class as it doesn’t have a key property. But New-CimInstance requires you to use one. And you can’t create a new Category, as LocalizedProperties (which takes an array of SMS_Category_LocalizedProperties) is a mandatory property and can’t be null.
Doh.
2. Working with classes that contain lazy properties
A second thing, that is pretty unique to but also regular within SCCM, is the use of so called lazy properties. As some of the SCCM objects contain pretty large properties or properties that might take quite some time to generate or process, those properties are marked as lazy and will not be loaded on default. E.g. if you are iterating through a list of those objects, all lazy properties will be empty/null.
Using the WMI CmdLets, one has to execute an explicit Get on the WMI object to also load the lazy properties. And this is a necessity if you want to change a value! As if you don’t load the content of the lazy properties and then save the object, those properties will suddenly all be null! You wouldn’t be the first one who accidentally corrupts a ConfigMgr object by simply changing a value 😉
With the CIM CmdLets, this problem isn’t as serious, as the Set-CimInstance CmdLet allows you to supply a list of key-value pairs that you would like to set. So you are no longer getting the object, update the values and then save the whole object. Rather get the object and explicitly set the properties that need to be changed. So no “sideeffect” on the lazy properties. But as there isn’t any Get method on the CimInstance object itself, you simply can’t read the lazy properties. Which might not be a problem, until you need to know the value of any of them.
For sure there are workarounds for those two issues. As this post is already way to large, I’ll give you the solution in a separate post.
Update:
https://maikkoster.com/powershell-cim-cmdlets-working-with-embedded-or-keyless-classes/
https://maikkoster.com/powershell-cim-cmdlets-working-with-lazy-properties/
However I’m very interested in your personal experience with the Cim CmdLets.
2 Responses
[…] In my last post, I explained a bit, why I changed over to the “new” PowerShell CIM CmdLets. However as mentioned, there were a few painpoints that I struggled with. […]
[…] one of my last posts I mentioned that for me the CIM CmdLets have been faster than the WMI CmdLets. I referred mainly to […]