DSC Configuration Data

One of the first ways that you can have a single configuration script produce multiple unique MOFs is to supply the script with configuration data. This is something that lives apart from the script itself, and is assigned to the configuration when the configuration is compiled. See the following basic example:

configuration MyMasterConfig {
  # configurations go here
}

$configData = @{}

MyMasterConfig -ConfigurationData $configData

We’ve created a configuration named MyMasterConfig. Separately - and this could have been in a totally different file that we dot-sourced, for example - we created a $configData hash table. Then, we run the config, and pass in the hash table using a built-in parameter. Now, the configuration data is available “inside” MyMasterConfig.

Defining Configuration Data

The top level configuration data hash table needs to contain two elements, AllNodes and NonNodeData. AllNodes is an array, and it’ll contain all of the unique node information that you need to provide. Each element within the array is a new hash table, which must at least contain a NodeName element. See the following example:

$MyData =
@{
    AllNodes =
    @(
        @{
            NodeName = 'DEMOWWW0001'
            Role     = 'WebServer'
        },
 
        @{
            NodeName = 'DEMOWWW0002'
            Role     = 'WordPress'
        },
 
        @{
            NodeName = 'DEMOWWW0003'
            Role     = 'WebServer'
        }
    );
    NonNodeData = ''  
}

Here, we’ve defined three nodes, named DEMOWWW0001, DEMOWWW0002, and DEMOWWW0003. For each, we’ve defined a “Role” property. In order to create that connection, we need to run the configuration, and use its built-in -ConfigurationData parameter to pass in $MyData.

Referencing and Using Configuration Data

Inside the configuration we’ll use three built-in variables:

  • $AllNodes automatically gets you the AllNodes@() portion of your ConfigurationData block.
  • Once you’ve filtered the $AllNodes collection, you can use $Node to refer to just the current node.
  • $ConfigurationData represents the entire block of data.

Pretend that the above three-node data block has been defined in the same script as this example:

configuration AllMyServers {
  Node $AllNodes.Where({$_.Role -eq "WebServer"}).NodeName {
  }
}

AllMyServers -ConfigurationData $MyData

The Node{} construct wants you to provide a list of node names. That’s it - just computer names, and no other data. So we’re taking $AllNodes, which contains everything. We’re using its special Where method to grab only those nodes whose Role property is set to WebServer.

Now let’s add a bit more:

configuration AllMyServers {
  import-DSCResource xWebAdministration

  Node $AllNodes.Where({$_.Role -eq "WebServer"}).NodeName {
    xWebSite TheWeb {
      Name = $Node.NodeName
      PhysicalPath = 'c:\inetpub\wwwroot'
      Ensure = 'Present'
    }
  }
}

AllMyServers -ConfigurationData $MyData

Within the Node{} construct, PowerShell will automatically run the contents once for each node you’ve given it. You can see that this is going to produce two MOF files, one named DEMOWWW0001.mof and another named DEMOWWW0003.mof, both (by default) in a folder named /AllMyServers. Each MOF will define a website, which will have the same name as the server itself. Let´´ add some more settings to the configuration.

$MyData =
@{
    AllNodes =
    @(
        @{
            NodeName = 'DEMOWWW0001'
            Role     = 'WebServer'
            Site     = 'CustomApp'
            SitePath = 'c:\inetpub\approot'
        },
 
        @{
            NodeName = 'DEMOWWW0002'
            Role     = 'WordPress'
        },
 
        @{
            NodeName = 'DEMOWWW0003'
            Role     = 'WebServer'
            Site     = 'App2'
            SitePath = 'c:\inetpub\app2root'
        }
    );
    NonNodeData = '' 
}

Notice that we’ve added Site and SitePath properties only to the nodes that have a WebServer value for Role. So now we can use those two new properties:

configuration AllMyServers {
  Node $AllNodes.Where({$_.Role -eq "WebServer"}).NodeName {
    xWebSite TheWeb {
      Name = $Node.Site
      PhysicalPath = $Node.SitePath
      Ensure = 'Present'
    }
  }
}

AllMyServers -ConfigurationData $MyData

You can see how the data block allows us to use a single configuration script to produce multiple, unique MOFs for multiple nodes.

All-Nodes Data

You can also specify properties to apply to all nodes:

$MyData =
@{
    AllNodes =
    @(
        @{  NodeName = '*'
            SystemRoot = 'c:\windows\system32'
         },
         
        @{
            NodeName = 'DEMOWWW0001'
            Role     = 'WebServer'
            Site     = 'CustomApp'
            SitePath = 'c:\inetpub\approot'
        },
 
        @{
            NodeName = 'DEMOWWW0002'
            Role     = 'WordPress'
        },
 
        @{
            NodeName = 'DEMOWWW0003'
            Role     = 'WebServer'
            Site     = 'App2'
            SitePath = 'c:\inetpub\app2root'
        }
    );
    NonNodeData = '' 
}

Now, all three nodes also have a SystemRoot property.

Using the $AllNodes Variable

The $AllNodes variable becomes a key for making those MOFs unique. It has two special functions: Where and ForEach:

 $AllNodes.Where({ $_.Site -eq 'CustomApp' }) # returns 1 node
 $AllNodes.Where({ $_.NodeName -like 'NODE*' }) # returns 1 node
 $AllNodes.Where({ $_.SystemRoot -eq 'c:\windows\system32' }) # returns 3 nodes

Imagine this configuration data block:

{
AllNodes = @(
@{
  NodeName = '*'
  FilesToCopy = @(
    @{
      SourcePath = 'C:\SampleConfig.xml'
      TargetPath = 'C:\SampleCode\SampleConfig.xml'
    },
    @{
      SourcePath = 'C:\SampleConfig2.xml'
      TargetPath = 'C:\SampleCode\SampleConfig2.xml'
    }
}

Now you might use a ForEach loop:

configuration CopyStuff {

  Node $AllNodes.NodeName {
  
    $filenumber = 1
    ForEach ($file in $Node.FilesToCopy) {
      File "File$filenumber" {
        Ensure = 'Present'
        Type = 'File'
        SourcePath = $file.SourcePath
        DestinationPath = $file.TargetPath
      }
      $filenumber++
    }
  
  }

}

Using NonNodeData

There’s a couple of important things to know about this section. First, you do not have to call the section “NonNodeData”, and second, you can have multiple NonNodeData sections, which makes a NonNodeData section a good option for role-specific settings that do not pertain to all nodes.
The snippet below shows two NonNodeData sections, one for domain controller settings (DCData) and the other for DHCP server settings (DHCPData).

$MyData = @{
  AllNodes = @( ... )
  
  DCData = @{
            DomainName = "Test.Pri"
            DomainDN = "DC=Test,DC=Pri"
            DCDatabasePath = "C:\NTDS"
            DCLogPath = "C:\NTDS"
            SysvolPath = "C:\Sysvol"
            }

  DHCPData = @{
            DHCPName = 'DHCP1'
            DHCPIPStartRange = '192.168.3.200'
            DHCPIPEndRange = '192.168.3.250'
            DHCPSubnetMask = '255.255.255.0'
            DHCPState = 'Active'
            DHCPAddressFamily = 'IPv4'
            DHCPLeaseDuration = '00:08:00'
            DHCPScopeID = '192.168.3.0'
            DHCPDnsServerIPAddress = '192.168.3.10'
            DHCPRouter = '192.168.3.1'
            } 
}

Keep in mind that you have the special $ConfigurationData variable, and $ConfigurationData. will access the “global” NonNodeData keys. Previously, we’d just set NonNodeData to an empty string (""), but in fact it can be a hash table or multiple hash tables. So now, the configuration can use $ConfigurationData.DCData.DomainName to access the domain name property, or $DHCPData.DHCPDNSServerIPAddress to access the DHCP server’s DNS IP address.