Blog Post

Core Infrastructure and Security Blog
3 MIN READ

Exploring Configuration Manager Automation Fundamentals – WMI

ms-foxworks's avatar
ms-foxworks
Icon for Microsoft rankMicrosoft
Jun 26, 2023

Hello everyone!

 

Welcome back to our blog series on automation and API capabilities within Microsoft Configuration Manager. In our previous post, we delved into the SMS Provider, the WMI interface that enables interaction with an MECM site. In this installment, we will take a closer look at Windows Management Instrumentation (WMI) and its significance in MECM.

 

Recap - The SMS Provider is our WMI-Interface to interact with a MECM-Site. With the Installation of an MECM-Client we also have WMI-Namespaces to gather information or automate Tasks:

 

 

Building-Blocks

 

Before we dive into the details of WMI and its usage, let's familiarize ourselves with its building blocks.

The following Illustration gives a High-Level-Overview:

 

 

In WMI, we navigate through namespaces, such as the SMS Provider ROOT\SMS\Site_[SiteCode]. Within these namespaces, we encounter classes, which represent the objects we interact with and sometimes act as type definitions. As an example, let's consider the SMS_Site class. Within a class, we find instances, such as SMS_Site.SiteCode="LAB," which provide properties holding specific values. In our illustration, the InstallDir property holds the value D:\Roles\ConfigMgr. Additionally, classes may offer methods, which can be executed at either the class level or instance level. For instance, the ImportMachineEntry method.

 

Lazy Properties / Static Methods

 

In Microsoft Configuration Manager, certain WMI classes utilize lazy properties for improved performance. Lazy properties display only the necessary information, and you can retrieve the entire object using a Get-Method or an implemented provider method. When using the WMI Explorer, you can identify classes employing lazy properties by a specific column indicator.

 

 

In our example, we explore the SMS_SiteControlFile class, which contains five properties when displayed,

 

 

 

but the instance definition specifies six properties, with SCFData being a lazy property.

 

 

Static methods, on the other hand, apply to WMI classes and not specific instances.

 

 

Understanding this concept is crucial, especially for working with the Administration Service. Let's consider an example using the SMS_SiteControlFile class mentioned earlier.

 

We utilize a native WMI call in PowerShell, defining a variable to store the information, specifying the namespace (root\sms\site_fox) and the target class (SMS_SiteControlFile). Then, we employ the WMI built-in method GetInstances().

 

 

The result mirrors what we saw in the WMI Explorer, with SCFData being empty because it is a lazy property.

 

In this case, we utilize the static method GetCurrentVersion to retrieve SCFData by providing the SiteCode parameter.

 

 

Associations between WMI Classes

 

In Microsoft Configuration Manager, certain WMI class properties may refer to different WMI classes. For instance, when dealing with machine variables in the context of a task sequence deployment, we require two WMI classes: SMS_MachineSettings and SMS_MachineVariable.

 

SMS_MachineSettings

 

The SMS_MachineSettings class includes instances for records with assigned variables, and its primary key is the ResourceID. This class does not provide a method, so we must create a new instance for new objects.

 

 

By using the Get-Member cmdlet or WBEMTest.exe, we can examine the type of the property, which reveals the WMI class it references.

 

 

SMS_MachineVariable

 

On the other hand, the SMS_MachineVariable class does not have instances or a method to create a variable. However, we need to create an instance to obtain the specific type for the SMS Provider.

 

 

Once we create the instance in SMS_MachineVariable, we can set the properties we desire.

 

 

 

Depending on the scenario, we can add or replace machine variables using the object created with SMS_MachineVariable and the MachineVariable property of the SMS_MachineSettings class for a specific ResourceID.

 

 

Conclusion:

 

In this blog post, we have covered important fundamentals of WMI and explored specific aspects within the context of Microsoft Configuration Manager. Familiarizing yourself with WMI is crucial since it serves as the interface used with the SMS Provider. In our next blog, we will delve into Configuration Manager PowerShell cmdlets, which heavily leverage WMI. Stay tuned for more insights!

 

Disclaimer
The sample scripts are not supported under any Microsoft standard support program or service. The sample scripts are provided AS IS without warranty of any kind. Microsoft further disclaims all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. The entire risk arising out of the use or performance of the sample scripts and documentation remains with you. In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the scripts be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the sample scripts or documentation, even if Microsoft has been advised of the possibility of such damages.

Published Jun 26, 2023
Version 1.0

5 Comments

  • ToddMote's avatar
    ToddMote
    Brass Contributor

    ms-foxworks Thanks!  Since i posted yesterday i have been able to get some code working, even the foreach-object -parallel.  here is what i've got:

    $objComputers = Invoke-Sqlcmd -ServerInstance $Siteserver -Query "select resourceid,Netbios_Name0,ADAttributefromDiscovery10,ADAttributefromDiscovery20 from v_R_System" -Database CM_DB 
    $objSMSMachineSettingsClass = Get-CimClass -ComputerName $Siteserver -Namespace "root\sms\site_$SiteCode" -ClassName "sms_machinesettings"
    $objSMSMachineSettings = Get-CIMInstance -computername $Siteserver -namespace "root\sms\site_$SiteCode" -class $objSMSMachineSettingsClass.CimClassName
    $varclass = Get-CimClass -ComputerName $Siteserver -Namespace "root\sms\site_$SiteCode" -ClassName "sms_machinevariable"
    $objComputers | ForEach-Object -ThrottleLimit 10 -Parallel {
        try {
            $objComputer = $_
            $objMachineSettings = $USING:objSMSMachineSettings | Where-Object { $_.ResourceID -eq $objComputer.ResourceID }
            $objMachineSettingsValues = $objMachineSettings | Get-CIMInstance
            foreach ($attr in ('ADAttributefromDiscovery10', 'ADAttributefromDiscovery20')) {
                $var = ($objMachineSettingsValues.MachineVariables | Where-Object { $_.name -eq $attr })
                If ( !$var -or ($objcomputer.$attr -ne $var.value)) {
                    $newcompvar = New-CimInstance -CimClass $using:varclass -ClientOnly
                    $newcompvar.Name = $attr
                    $newcompvar.Value = $objcomputer.$attr
                    $newcompvar.IsMasked = $false
                    $objMachineSettingsValues.MachineVariables = $objMachineSettingsValues.MachineVariables + $newcompvar
                }
            }
            Set-CimInstance -CimInstance $objMachineSettingsValues | out-null
        }
        catch {
            $_
        }
    }


    i found i can get the classes and get all of the machine settings, then the class for the machine variable and build variables from that.  and even set them.  we have about 17,500 clients and if i put 'top 100' in the sql query at the top it parallelizes it and works through all 100 in about 28 seconds, which is pretty good.  using the cmdlet to set them one at a time is about 3 secs per endpoint.  i thought WMI would be faster so that's how i started down this rabbit hole.

     

    as you say SMS_MachineVariable is only a type reference and it messed me up for a good bit because with WMI you can make a variable with the typecast on the path and it just makes it.  the trouble i had was that when i tried to do that with CIM it would make the instance in WMI on the site server the first time and then complain that it already existed in subsequent invocations, and all i needed was the type on a variable locally.  i found the -clientonly switch and that unlocked the whole thing for me yesterday.

     

    the parallelization though makes it hard to know how far it gets.  i think i get to some limit though because at just shy of running for an hour and a half, i don't know how many it's worked through and it starts failing with errors like unable to find the property 'name' on 

    $objMachineSettingsValues when it had worked on untold hundreds or thousands for the last 90 minutes.  so i don't think it's the code, but some other limit.  memory maybe?  i dunno.  i also don't know if this is even right, but it works and it writes those AD attributes we get from discovery into machine variables correctly.
     
    i will try the adminservice to see if i can get it to complete that way, but i have a sense it will be slower.
     
    speed is a thing for this because it will have to run regularly to get the AD attributes into variables on anything that moves in AD or new endpoints.  we use the data in task sequences to let the endpoints know what department they belong to.
     
    i'm close, i feel like, but not quite all the way through.
  • Hi ToddMote! Thanks for the Feedback. In short, I am sorry to say, parallel is not working for this API. It was never build or designed for that and little bit older than the invention of PowerShell.

    Btw you can use foreach -parallel with Windows PS 5.1 - but it must be in a Workflow and here we have some restrictions:
    about Foreach-Parallel - PowerShell | Microsoft Learn
    You cannot use the WMI 1.0 Method like in this Blog because of this restrictions.
    The Microsoft Configuration Manager CmdLets supports PowerShell 7 - but you sense it - not in a workflow parallel Code-Block. The reason you need to import the Module - which is not allowed in a workflow. You can overcome it if you provide in the workflow an InlineScript - but the InlineScript runs in an isolation mode - so the rest of the code cannot use it.

    Usually you would use Get/New/Set-CIMInstance. But with this approach you will face two different Issues. As mentioned in the Blog the SMS_MachineVariable-Class is only a Type-Reference, we just want such an Object to use it later for the SMS_MachineSettings-Class for one Instance. But if you use something like that:

     

    $MachineVar = New-CimInstance -ComputerName CM01 -Namespace root\sms\site_fox -ClassName SMS_MachineVariable -Property @{Name="TESTVAR";Value="1234";IsMasked=$false}
    

     

    It will use the Put() Method and you will have one Instance - where usually none exist, where you will face exceptions with the next runtime.
    But let us continue:

     

    $Resource = Get-CimInstance -ComputerName CM01 -Namespace root\sms\site_fox -ClassName SMS_MachineSettings -Filter "ResourceID='16777330'"
    Set-CimInstance -InputObject $Resource -Property @{MachineVariables=$MachineVar}

     

    If you call this you face a Cast-Exception. The reason with the CIM-Call you get for this Object is a Type of Microsoft.Management.Infrastructure.CimInstance - but the Property MachineVariables need a Type of Microsoft.Management.Infrastructure.Native.InstanceHandle

    I see only two Options for you in PowerShell 7 - you use the Configuration Manager Module but without parallel. Then you do not need to care about the CIM-Instances. 

     

    Import-Module ConfigurationManager
    SL XYZ:
    
    New-CMDeviceVariable -DeviceName WS01 -VariableName TEST -VariableValue 1234 -IsMask $false

     

     

    Or use the Administration Service and the Rest-API. But this will also not run in parallel.
    Exploring Configuration Manager Automation Fundamentals – Administration Service - Microsoft Community Hub

     

    $CMAdminService = Connect-CMAdminService -CMProviderFQDN CM01.FOXWORKS.INTERNAL
    
    # Add a Variable to the Device
    
    $VarOSType = 'WindowsOS'
    $VarOSTypeValue = 'Windows 11'
    
    $uri = 'wmi/SMS_MachineSettings'
    $ReqBody= @{
                 LocaleID = 1033;
                 ResourceID = $($NewDevice.ResourceID);
                 SourceSite = 'FOX';
                 MachineVariables = @( 
                                        @{
                                            IsMasked = 'False';
                                            Name = $VarOSType;
                                            Value = $VarOSTypeValue;
                                         }
                                    )
                }
    
    $NewVar = Invoke-CMAdminServicePost -odata $CMAdminService -query $uri -body $ReqBody

     

     
    Not the answer what you hoped - but maybe a workaround for your Code-Project.

  • ToddMote's avatar
    ToddMote
    Brass Contributor

    Great post.  I'm looking for a CIM version for the machine variables section.  I need to parallelize so i can hit every device quickly, but all the WMI cmdlets don't work in PS7, only the CIM ones do, and foreach-object -parallel isn't available in Windows PS.  there aren't methods for objects that get-ciminstance, and nor is there a put method.  How do you do get variables, write variables and save variables with the CIM cmdlets?

  • Hi Piotr Lapkowski

    On the Client or a Server with CMClient installed the main Namespaces are root\ccm - recursive and root\cimv2\sms.
    On the SiteServer it is root\sms - recursive
    On a DistributionPoints we have also one additional Namespace root\sccmdp 

    You can find more information here about the WMI-Namespaces:
    WMI namespaces and classes for reports - Configuration Manager | Microsoft Learn
    For WMI-Classes and Methods you will find information here:
    Configuration Manager API reference | Microsoft Learn

    The association between Classes is not that documented - it will be necessary to look at the WMI-Classes and Props in that case when you start your Code-Project.

    But here is another example - at the moment we have an open Bug regarding the CmdLet  Set-CMDriverBootImage. The Problem instead of applying one specific Driver it is adding Drivers of a Package because of a wrong Query-Expression.

    So the workaround was a native WMI-Call. 

    # Get the DriverDeatils we need

    $DriverInfo = Get-WmiObject -Namespace root\sms\site_fox -Query "select CI_ID, ContentSourcePath from SMS_Driver where CI_ID = '16784127'"

     

    # Build a Reference Instance to apply Props later to the Instance

    $DriverObj = [WMIClass]'root\sms\site_fox:SMS_Driver_Details'

    $newDriverObj = $DriverObj.CreateInstance()

    $newDriverObj.ID = $DriverInfo.CI_ID

    $newDriverObj.SourcePath = $DriverInfo.ContentSourcePath

     

    # Get the BootImage WMI-Object - Load Lazy Properties extend the existing DriverReference-Array

    $objWMI = [WMI]'root\sms\site_fox:SMS_BootImagePackage.PackageID="FOX00005"'

    $objWMI.Get()

    $objWMI.ReferencedDrivers += $newDriverObj

    $objWMI.Put()


    Hope this helps.

  • Piotr Lapkowski's avatar
    Piotr Lapkowski
    Copper Contributor

    Hello

    Thank You for Your very interesting post!

    I have two questions:

     

    1. How to find deep information about WMI namespaces created by Configuration Manager? 

    2. How to find association between WMI Classes? Please example.