Managing the PowerShell DSC Local Configuration Manager

‌‌

The Local Configuration Manager (LCM) is an integral component of PowerShell Desired State Configuration (DSC). In a nutshell, the LCM is where the rubber meets the road (or, in DSC terms, where the configuration is finally applied to a node). And while the LCM and DSC work great, they first need to be configured to work in your environment. If your environment consists of a single machine, the task won’t be difficult; simply set a few configuration values and you're done. But most of us don't have that luxury, so we must take a different approach and focus on configuring the LCM at scale.

What exactly do I mean by at scale? In this context, I'm talking about how to apply the initial configuration and quickly change the LCM configuration for hundreds, or thousands, of nodes. In this post, we'll focus on deploying an initial LCM configuration to a few nodes and changing that configuration. While our demonstration will include just a few nodes, this technique could easily scale up to hundreds or thousands of nodes.

Creating and applying an LCM configuration

Let's first see what it takes to apply an LCM configuration to a single node. For our demonstration, we’ll focus on applying an LCM configuration to a node called SERVER1. To do this, we'll define a particular configuration using the DscLocalConfigurationManager attribute.

Note: All examples will be covered with PowerShell v5.

[DSCLocalConfigurationManager()]
configuration LCMConfig
{
    Node SERVER1
    {
        Settings
        {
            ConfigurationMode = 'ApplyOnly'
        }
    }
}

Note that I'm just ensuring the LCM is configured to only apply configurations. For an entire list of all settings that are available you can refer to Microsoft's Configuring the Local Configuration Manager article.

Now that the configuration block has been defined, I'll add a line below so that it will be executed and will save it as C:\SERVER1_LCM.ps1. Once executed, it will then create an LCMConfig folder and place a file called SERVER1.meta.mof, which represents the MOF that that SERVER1's LCM will consume to apply the configuration.

Now that the MOF file has been generated, I can apply it to SERVER1. But first, let's check SERVER1's LCM configuration to see if this configuration works. 

Get-DscLocalConfigurationManager –CimSession SERVER1

You can see that the ConfigurationMode is set to ApplyAndMonitor. If successful, when the new MOF is applied to SERVER1, this should be ApplyOnly.

Let's go ahead and apply the LCM config to SERVER1 with the Set-DscLocalConfigurationManager command.

Set-DscLocalConfigurationManager –Path C:\Users\adam\LCMConfig

You can see above that the LCM has been changed to ApplyOnly. This is the gist of applying and changing an LCM config for a node. 

Creating and applying LCM configurations for many nodes

Using the steps above to complete the task for one node let's make this scale and apply it to multiple nodes. For this demonstration, I'll just be applying an LCM configuration on a few nodes, but it's possible to add as many as you want.

To get started, we'll need to define all of the nodes that we'll be pushing a new LCM configuration to. I'll do this using ConfigurationData. This an excellent way to separate the actual node attributes from the configuration itself, and is necessary for managing many nodes. In this instance, I'm going to pull the node names from an external source; here it will be a text file and then merge these into a structure that DSC understands.

$computers = Get-Content –Path C:\Computers.txt

I'll then create the configuration data hashtable that DSC will understand when passed to the configuration.

$configData = @{
	AllNodes = @(

	)
}

Now that we have the appropriate hashtable defined, I'll create a hashtable with a NodeName key for each of the computers in the text file.

@($computers).foreach({
	$configData.AllNodes += @{NodeName = $_}
})

If you look inside of the AllNodes array, you’ll see a hashtable defined for each computer.

Building the configuration data hashtable is done. Because I'll be using the node names inside of the configuration data hashtable, we'll need to modify the LCM configuration a little bit to ensure it looks at the node names inside of the configuration data hashtable. I'll do this by replacing the static node name with $AllNodes.NodeName as shown below.

[DSCLocalConfigurationManager()]
configuration LCMConfig
{
    Node $AllNodes.NodeName
    {
        Settings
        {
            ConfigurationMode = 'ApplyOnly'
        }
    }
}

This will ensure a MOF is created for each node rather than just a single node.

I can now execute the configuration and use the –ConfigurationData parameter.

You can see above that it has created three MOF files for the three computers I originally had in the C:\Computers.txt file.

Note: Each node must have PowerShell v5 installed for this to work. This is because I've chosen to use the DscLocalConfigurationManager keyword which is only available in version 5.

I'll now deploy these LCM configurations in parallel to all of my nodes using jobs. Set-DscLocalConfigurationManager does try to apply configurations in parallel, but I've found that it sometimes gets hung up on one or more nodes and keeps them all from executing. To prevent that, I'll use PowerShell jobs.

@($computers).foreach({
	Start-Job –Name $_ -ScriptBlock {Set-DscLocalConfigurationManager –Path C:\LCMConfig –ComputerName $_ -Verbose }
})

This will immediately create jobs for each node.

Now you simply need to wait for all the jobs to complete. A while loop will accomplish this.

While (Get-Job –Name $computers | where {$_.State –ne 'Completed'}) {
	Start-Sleep –Seconds 1
	Write-Host 'Still waiting for LCM configs to finish up…'
}

This will wait until all nodes have finished. As soon as that's done, you can dive into each job and confirm success.

There are many different ways to set the LCM configuration for many nodes other than the example I chose here. You can also look into using foreach parallelization with workflows, parallel runspaces or even using Invoke-Command –AsJob. If you don't like this method, play around with other methods and use whichever one works best for you.

Contributor

Adam Bertram

is an independent consultant, technical writer, trainer and presenter. Adam specializes in consulting and evangelizing all things IT automation mainly focused around Windows PowerShell. Adam is a Microsoft Windows PowerShell MVP, 2015 powershell.org PowerShell hero and has numerous Microsoft IT pro certifications. He is a writer, trainer and presenter and authors IT pro course content for Pluralsight. He is also a regular contributor to numerous print and online publications and presents at various user groups and conferences. You can find Adam at his site listed below or on Twitter at @adbertram.