DSC Design

There are two important things to consider when it comes to designing your DSC implementation:

There is no “one approach” that is right for everyone - you need to choose an approach that meets your needs.

and

You need to understand the options thoroughly before deciding.

This chapter will help you understand what DSC is doing, how it does it, and what your options are for designing your DSC approach.

Local Configuration Manager (LCM)

One of the main components is the Local Configuration Manager, or LCM. The LCM is a component that runs on each node that is managed with DSC. The LCM reads configuration documents and checks if the node is in compliance and potentially fixes the node so that it is in compliance again.

Managed Object Format (MOF)

Another main component in DSC is the Managed Object Format (MOF) file. This is a plain-text file that contains the configuration specifications. MOF is an industry-standard format developed by the Distributed Management Task Force (DMTF), an industry group that includes Microsoft and other vendors. DSC resources provide the instructions for how the LCM checks the node’s current configuration.

The MOF contains a description of what the node should look like, and the DSC resources know how to check the configuration and, if necessary, fix it. The abilities with DSC are limited only by the DSC resources that are available. Anything there is a resource for, can be managed via DSC. And, because DSC resources are just PowerShell scripts, you can always create a DSC resource for whatever you might need.

The CIM Connection

It’s good to know that under the hood DSC is based almost entirely on the Common Information Model (CIM). For example, when you run a command to send a configuration MOF to a node, that connectivity happens over a CIM session.

Uniqueness in MOF files

There is an important technical detail about MOF files that you need to know before we go any further. Take a look at this example of a PowerShell DSC configuration script:

WindowsFeature WebServer {
	Name = 'Web-Server'
	Ensure = 'Present'
}

This is a resource configuration. It specifies the configuration for one specific item. The WindowsFeature part is the name of the resource that willbe installed on the node. Following that is the name for this configuration setting. In this example the resource is named WebServer.

Always try to create unique names because in the final compiled MIF file all names should be unique - in the final MOF, no other configuration setting can use the same name.

Inside the configuration setting are its properties, including Name and Ensure. These properties and their values will be passed to the DSC resource by the LCM, and tell the resource what to do.

Because Name was designed to be the key for the WindowsFeature resource, it must be unique for all instances of the WindowsFeature resource within the final MOF. This restriction is designed to stop you from doing things like this:

WindowsFeature DnsServer1 {
	Name = 'DNS'
	Ensure = 'Present'
}
WindowsFeature DnsServer2 {
	Name = 'DNS'
	Ensure = 'Absent'
}

This kind of configuration can throw the LCM into an infinite loop of installing and uninstalling, DSC won’t allow it and it will detect the conflict upon trying to create a MOF from a configuration with these settings, an error will be returned, and a MOF will not be created.

One MOF, Many Nodes

One MOF can simply be applied to multiple nodes. Provided the MOF doesn’t try to configure any must-be-unique information, like computer name or IP address, this is totally legal. This works well in situations where you want multiple nodes configured identically, such as a group of web servers that are all part of the same load-balanced set.

Configuration Data Blocks

When you run a configuration script, you can pass in a configuration data block. This is a data structure that lists the nodes you want to produce MOF files for, and can provide unique, per-node information. In your configuration script, you can then write logic - such as If constructs - that change the output of the MOF file for each node, based upon that configuration data.

For example, you might create a configuration data block that, in part, looks like this:

$ConfData = 
@{
    AllNodes = 
    @(
        @{
            NodeName = "DEMOWWW0001";
            Role     = "WebServer"
        },
        @{
            NodeName = "DEMOWDB0001";
            Role     = "SQLServer"
        }
		)
}

Then, in the configuration script, you might do something like this:

if ($node.role -eq "WebServer") {
	WindowsFeature InstallIIS {
		Ensure = 'Present'
		Name = 'Web-Server'
	}
}

The MOF files for each of those two nodes would therefore be different. Configuration data blocks can be defined in the same physical file as a configuration script, but more often the data is stored in a separate file, and then loaded and passed to the configuration script when that script is run.

While this technique does enable a single configuration script to handle a wide variety of situations, it also becomes possible for that configuration script to become incredibly complex - and potentially harder to maintain.

Dot Sourcing

One way to make a complex configuration script easier to maintain is to break it into multiple files. See this example:

configuration Database {
	param(
		[string[]]$ComputerName
	)
	node $ComputerName {
		. ./base-configuration.ps1
		. ./security-configuration.ps1
		. ./sql-2017-configuration.ps1
	}
}

When run, this configuration script would dot-source three different PowerShell scripts. Those scripts would simply contain configuration settings. This technique can also make use of configuration data blocks, enabling you to add per-node logic to each of the dor-sourced scripts.