This information applies to ConfigMgr version 1710 and later.

One of the things I really love about working in IT is that you can learn new stuff all the time, and when new stuff turns into boring repetitive stuff you can apply automation and add yet another new piece of learning to your skillset.

Over the last few releases of Configuration Manager, the product team has added some new cmdlets for managing Configuration Items and Baselines, and I started to look into these when I was given the task to create a lot of very similar CIs and Baselines for our excellent dashboard, CTGlobal Insight Analytics.

In order to understand how CI’s work, we should break them down into the components they are built from. A CI is a container for one or more settings. Settings specify what we want to look at, this could be an .exe file, an AD or WMI query, a registry key and so on. For each setting we can then apply one or more rules, that specifies what value the setting should have in order to be compliant.

image

An example of such a CI could be like the following

image

This CI has two settings, one that collects the service status for a particular service on SCCM clients using a simple Powershell script, and one that references a specific .exe file.

The rules state that the service must be running, the file must exist and be a specific version.

A variation of this CI is one where the service check is left out, so it would only check for the .exe to exist and that it is at a certain version level, Another variation may be to leave out the version and only check for existence.

All CIs are built from this simple setup but with different settings and rules.

Now that we know how CIs are built lets look into the cmdlets provided with ConfigMgr 1710 that we need in order to create CI’s and Baselines.

  • Get-CMBaseline
  • Remove-CMBaseline
  • Get-CMConfigurationItem
  • Remove-CMConfigurationItem
  • New-CMConfiguration
  • Add-CMComplianceSettingFile
  • Get-CMComplianceSetting
  • New-CMComplianceRuleExistential
  • Add-CMComplianceRule
  • New-CMComplianceRuleVersion
  • Add-CMComplianceSettingScript
  • New-CMComplianceRuleValue
  • New-CMBaseline
  • Set-CMBaseline

Please note that the list of cmdlets for managing CI’s and Baselines is much longer than this list, but these are the ones we need to solve the current challenge.

Our first task is to setup some naming rules

# Generate CI Name
$CheckName = "Installation"
if ($Version) {
    $CheckName += " and Version"
    if ($ServiceName) {
        $CheckName = "Installation, Version and Service State"
    }
} else {
    if ($ServiceName) {
        $CheckName = "Installation and Service State"
    }
}
$CheckName += " Check"
$CIName = "CT $Name - $CheckName"

CheckName is the name of the CI and the Baseline that we will create and I set it based on the arguments for the script.

The next step is to remove any existing CI and/or Baseline with that name:

# Remove Existing CI if any
Get-CMBaseline -Name $CIName | Remove-CMBaseline -Force
Get-CMConfigurationItem -Name $CIName -Fast | Remove-CMConfigurationItem -Force

Please note the use of the -Fast switch to avoid loading all the lazy properties of the CI, as this makes thing go a little faster.

Next we will create the new CI:

# Create new CI
$ci = New-CMConfigurationItem -Name $CIName -CreationType WindowsOS

Note that we need to supply a type of CI, in this case it is WindowsOS (which is all CIs targeting Windows OS)

Next up is creating a Setting for the CI. In the first case, it will be a File setting by the name of “Exe File” and after creating it, we need to get a reference for it for further use:

# Create File Setting
$temp = $ci | Add-CMComplianceSettingFile -path $Path -FileName $FileName -NoRule -SettingName "Exe File" -Is64Bit:$Is64bit
$setting = $ci | Get-CMComplianceSetting -SettingName "Exe File"

Note that we pipe in the CI to the cmdlet, as I found that this is the better way to get things to work

The next step is to create the existence rule and if we added a version to the arguments we also build a rule for that:

# Create File Existance Rule
$rule = $setting | New-CMComplianceRuleExistential -RuleName "Exe File Must Exist" -Existence MustExist
$temp = $ci | Add-CMComplianceSettingRule -Rule $rule

if ($Version) {
    # Create File Version Rule
    $rule = $setting | New-CMComplianceRuleVersion -RuleName "Exe File Version" -ExpressionOperator IsEquals -ExpectedValue $Version
    $temp = $ci | Add-CMComplianceSettingRule -Rule $rule
}

Again, note the piping of the Setting and CI to the cmdlets.

If we added a service name to the arguments, we also need to setup a setting for a Powershell script to check if the service is running, so let’s do that next:

if ($ServiceName) {
    # Create PS Script to check service state
    $ServiceStatusScript = @"
    `$ServiceName = '$ServiceName'
    (Get-Service -Name `$ServiceName -ErrorAction SilentlyContinue | Select -ExpandProperty Status) -eq "Running"
"@

    #Create Script Setting
    $temp = $ci | Add-CMComplianceSettingScript -DataType Boolean -DiscoveryScriptText $ServiceStatusScript -DiscoveryScriptLanguage PowerShell -SettingName "Service State" -NoRule
    $setting = $ci | Get-CMComplianceSetting -SettingName "Service State"

    # Create Service State rule
    $rule = $setting | New-CMComplianceRuleValue -RuleName "Service is Running" -ExpressionOperator IsEquals -ExpectedValue "True"
    $temp = $ci | Add-CMComplianceSettingRule -Rule $rule
}

Note the use of the -NoRule switch when we create the setting, this makes it simpler to separate the individual components in the scripts, and also enables a simple way to add more rules to the script if you wish to do so.

Finally we must create a Baseline and add the CI to it:

# Create Baseline
$baseline = New-CMBaseline -Name $CIName 
$temp = Set-CMBaseline -Name $CIName -AddOSConfigurationItem $ci.CI_ID

Notice that the ID of the CI must be specified using the CI_ID property. Oddly enough, there is no way to add the CI object as an input. This is not a big issue, but would have been a little clearer.

All that is left now is to create a deployment of the Baseline to whatever collection you wish to target. I will not cover that in this blogpost as it is a task we all know and “love”, and of course you can also script your way out of that.

The complete script is available here: Download script

Merry X-Mas or whatever you may celebrate at this time of the year.