PowerShell CIM CmdLets – Working with embedded or keyless classes
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.
The Problem
One of them was to work with embedded classes. These are classes that are “embedded” into other classes. Very often they are used to store more complex properties. 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.
The Solution
I have to admit, that it took me way longer than necessary to get this solved. The final hint came from this PowerShell blog post about CIM CmdLets – Some Tips & Tricks, in particular Tip #5 “Passing ref and embedded instances”.
The “trick” here is to use the ClientOnly switch when creating the instance. This will create an In-Memory instance of this class in PowerShell only, without going to the CIM server. So to create the above SMS_Category_LocalizedProperties instance, you can use the following snippet:
1 2 3 4 5 6 7 8 |
$Properties = @{ CategoryInstanceName = "Test Category" LocaleID = 1033 } $LocalizedSettings = New-CimInstance -ClientOnly ` -Namespace root\sms\site_TST ` -ClassName SMS_Category_LocalizedProperties ` -Property $Properties |
That was almost to easy, wasn’t it? You can then use this local instance to create a new Category in SCCM
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$CategoryGuid = [System.Guid]::NewGuid().ToString() $UniqueID = "DriverCategories:$CategoryGuid" $CategoryProperties = @{ CategoryInstance_UniqueID = $UniqueID SourceSite = 'TST' CategoryTypeName = "DriverCategories" LocalizedInformation = $LocalizedSettings } New-CimInstance -Namespace root\sms\Site_TST ` -Class SMS_CategoryInstance ` -Property $CategoryProperties |
A few lessons learned:
- Don’t use the ComputerName or CimSession property when working with ClientOnly. Only use the proper Namespace and ClassName. It doesn’t matter if this namespace doesn’t exist on the local computer. You are actually more likely to run into other issues, at least that’s what happened to me and why it took me so long to figure this out to be the real solution.
- Preferably supply all properties that you need to set already when calling New-CimInstance using the Property parameter. If you want to add a property later, use Add-Member. The following will do the same as the original snippet, it’s just harder to read:
1234567891011$CimInstance = New-CimInstance -ClientOnly `-Namespace "root\sms\site_TST" `-ClassName SMS_Category_LocalizedProperties$CimInstance | Add-Member -MemberType NoteProperty `-Name "CategoryInstanceName" `-Value "Test Category"$CimInstance | Add-Member -MemberType NoteProperty `-Name "LocaleID " `-Value "1033"
Solution #2
As mostly, there isn’t only one way of doing things. Another option would be to use the New-Object CmdLet:
1 2 3 4 5 |
$Session = New-CimSession -ComputerName CM01 $LocalizedSettings= New-Object CimInstance $Session.GetClass("root\sms\site_TST", "SMS_Category_LocalizedProperties") $LocalizedSettings.CategoryInstanceName = "Test Category" $LocalizedSettings.LocaleID = 1033 |
The biggest advantage is, that you get an object with all properties that are defined in this class. I personally found this especially helpful when working with different versions of SCCM. If there e.g. is a property that is only available in newer versions, but you want to have the possibility to set this property if it’s available. Using this way, you can now check first if this property exists and only set it if available. That will make your script more resilient.
1 Response
[…] In my last post, I explained how to solve one of the stumbling blocks when working with the PowerShell CIM CmdLets. The second problem, which was both pretty nasty as it basically prevented me from using the CIM CmdLets properly for SCCM and pretty time consuming, as it was hard to find a solution, was working with lazy properties. […]