Desired State Configuration is a great way to configure your servers quickly and keep them in compliance with those configurations to maintain consistency. As you go deeper down the rabbit hole, however, you can find that managing those configurations themselves can be a little daunting. The more you add to a configuration, that larger it gets and the harder it is to find things in there when you need to make changes.

One way to handle these large configuration files is to break them up into Composite Resources. DSC only allows one configuration per node. Composite Resources are a way for you to modularize your configurations into separate modules so you can re-use your common configurations in different places. For example, you can create a composite resource that only contains your Web-Server roles and features and another composite resource that contains just your OS lockdown settings. Then you only need to reference these composites when building your configurations without having to copy and paste everything.

For example, a sample configuration could look like this:

Configuration Sample_Configuration { Import-DscResource -Module xWebAdministration Node $AllNodes.NodeName { WindowsFeature WebServer { Name = "Web-Server" Ensure = "Present" } WindowsFeature WebWindowsAuth { Name = "Web-Windows-Auth" Ensure = "Present" } WindowsFeature WebMgmtConsole { Name = "Web-Mgmt-Console" Ensure = "Present" } WindowsFeature Web-App-Dev { Name = "Web-App-Dev" Ensure = "Present" } WindowsFeature Web-Mgmt-Tools { Name = "Web-Mgmt-Tools" Ensure = "Present" } WindowsFeature Web-Security { Name = "Web-Security" Ensure = "Present" } WindowsFeature Web-Stat-Compression { Name = "Web-Stat-Compression" Ensure = "Present" } WindowsFeature Web-WebServer { Name = "Web-WebServer" Ensure = "Present" } File LOG_Directory { Ensure = "Present" Type = "Directory" DestinationPath = "C:\Logs" } xWebsite DefaultSite { Ensure = "Present" Name = "Default Web Site" State = "Stopped" PhysicalPath = $Node.DefaultWebSitePath DependsOn = "[WindowsFeature]WebServer" } Registry SomeRegKeyChange { Ensure = "Present" # You can also set Ensure to "Absent" Key = "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\SomeRegKey" ValueName = "RegValueName" ValueType = "ExpandString" ValueData = "RegValue" } } }

Our current configurations contain a lot more settings than this. I’ll just be using this small example top make things easier.

If your web server settings are pretty consistent throughout your organization, it might make sense for you to break out the Web-Server windows features into a separate composite configuration to be re-used for other servers. You can even go a step further and break out the rest of the settings into another composite resource to keep things clean and re-usable. A configuration which uses composite resources would look something like this:

Configuration Sample_Configuration { Import-DscResource -ModuleName WebServerConfig Import-DscResource -ModuleName OtherSettings Node $AllNodes.NodeName { WebServer_WindowsFeatures WindowsFeatures { } OtherSettings GlobalSettings { } } }

The first thing you will notice is the configuration gets cleaned up considerably and it is easier to see everything that is being done at a high level without having to scroll through a long configuration. The next thing you will notice is the composite configurations need to be imported using Import-DscResource. This is very important. When you create your composite resources, they need to be placed in your Modules folder in order to create your MOF file. I’ll go into creating composite resources next.

Creating A Composite Resource

I’ll start off by saying that I am not an expert at this part. Since I started in the middle of this project I have only created a handful of custom resources and always have to go back to lookup how it was done.

First, you need to create a folder in your Modules folder. In this example, I will call name it WebServerConfig. You can use whatever naming convention you feel comfortable with. This will be the name you use when you user Import-DSCResource in your configuration.

Under the WebServerConfig folder, I need to create 2 things. First, a folder called DSCResources. This is required for all composite configurations.

Next, we need to create a module manifest in the WebServerConfig folder. The manifest needs to be named exactly the same as the folder we just created. To do this, run the following command from inside the WebServerConfig folder:

New-ModuleManifest -Path .\WebServerConfig.psd1

The WebServerConfig folder should now look like this:

Next we need to drill into the DSCResources folder. In here, we are going to create another folder and call it WebServer. The reason I didn’t give this folder the same name as the module folder we created earlier, was to show you that this is not required. In fact, you could create multiple folders under here to separate your web server settings even more if you need that granular of an option when setting up your configurations.

Next, we need to drill down into the WebServer folder. In that folder, we need to create 2 files. First, we need to create another module manifest. Second, we need to create a file with an extension of .schema.psm1. Both of these files need to be named exactly the same as the folder they are in (WebServer). Create the WebServer.schema.psm1 file first. Then, to create the module manifest, run the following command from within the WebServer folder:

New-ModuleManifest -Path .\WebServer.psd1 -RootModule .\WebServer.schema.psm1

It is important to remember to use the RootModule parameter this time. This needs to reference the .schema.psm1 file you created.

The contents of the WebServer folder should look like this:

The schema file is where you will put all your DSC configurations. In my example, this file will contain the following code:

Configuration WebServer { WindowsFeature WebServer { Name = "Web-Server" Ensure = "Present" } WindowsFeature WebWindowsAuth { Name = "Web-Windows-Auth" Ensure = "Present" } WindowsFeature WebMgmtConsole { Name = "Web-Mgmt-Console" Ensure = "Present" } WindowsFeature Web-App-Dev { Name = "Web-App-Dev" Ensure = "Present" } WindowsFeature Web-Mgmt-Tools { Name = "Web-Mgmt-Tools" Ensure = "Present" } WindowsFeature Web-Security { Name = "Web-Security" Ensure = "Present" } WindowsFeature Web-Stat-Compression { Name = "Web-Stat-Compression" Ensure = "Present" } WindowsFeature Web-WebServer { Name = "Web-WebServer" Ensure = "Present" } }

To confirm that you did everything correctly and all the files are in the right place, run the Get-DSCResource cmdlet. In this list that gets returned, you should see the composite resource you just created.

Repeat this process for any other Composite Resources you want to create. In my example, I would create another folder under my Modules folder called OtherSettings and repeat all the previous steps. My final configuration file will look like this and I will save it as SampleCompositeDSCConfig.ps1 under C:\DSC\Configs:

Configuration Sample_Configuration { Import-DscResource -ModuleName WebServerConfig Import-DscResource -ModuleName OtherSettings Node $AllNodes.NodeName { WebServer_WindowsFeatures WindowsFeatures { } OtherSettings GlobalSettings { } } }

Creating Your MOF File

Once all the files are created and your composite resources are in the Modules folder, we are ready to create our MOF file. To do this, I will create another .ps1 file in the c:\DSC\Configs folder called New-Configuration_SampleCompositeDSCConfig.ps1. You can call this whatever you want.

In this script we will need to load the sample configuration file we just created, create a new GUID (you can create one by running [GUID]::NewGuid() in the PowerShell console) to assign to the MOF file so we can place it on the Pull Server, create the actual MOF file, then create a checksum of the MOF file (This will also need to be placed on the Pull server). Here is what it will look like:

Import-Module .\SampleCompositeDSCConfig.ps1 -Force $guid = "268cc112-27c5-4130-bd81-644c64a10991" $configurationData = @{ AllNodes = @( @{ NodeName = $guid } ) } $outputPath = "./SampleCompositeDSCConfig_mof" Sample_Configuration -ConfigurationData $configurationData -OutputPath $outputPath New-DscCheckSum $outputPath\$guid.mof -Force

I like to open up this script in the PowerShell ISE so that I can step through each of the steps to make sure everything is working properly. Make sure that you CD into the directory that contains this script before running anything. Now let’s step through what this script is doing.

First, we use Import-Module to import the configuration file we created (SampleCompositeDSCConfig.ps1). Then we assign a new GUID to the $guid variable. We save a generated GUID in this script so that if we make any changes to the configurations, all we have to do is re-run this script to generate a new MOF. To generate a new GUID you can use the following command in the PowerShell console [GUID]::NewGuid() and that will generate a new random GUID for you. Then we store some configuration data in the $configurationData variable. Here we are storing the GUID as the node name. This is what the MOF file and the checksum file will be named for use on the Pull Server. The $outputPath variable is the folder where we are going to store the MOF and checksum files. Next, we run the Sample_Configuration that was loaded in the first step, passing in the configuration data and the output path. This will create a new folder in the current directory and as long as we have create all our composite configurations correctly, it will place a MOF file in that directory called {GUID}.mof. Finally, we run the New-DscCheckSum cmdlet on the MOF file we created which will create a file called {GUID}.mof.checksum.

Once we have created our MOF and CheckSum files, those will then need to be placed on the Pull Server. In the future, if any changes are made to any of the configurations, we just need to run the New-Configuration_SampleCompositeDSCConfig.ps1 script to create an updated MOF to put out onto the Pull server.

If you need more information on Pull Servers you can go check out The DSC Book from PowerShell.org. System Center Central also did a series called 100 Days of DevOps with PowerShell which I found very helpful as a good introduction to DSC and setting up a Pull Server.

Once the MOF and Checksum files are on the Pull Server, we have an administrative portal that one of our developers created using a SQL database and Visual Studio LightSwitch that allows us to add servers and assign environments and GUIDs to them. We also have a custom built PowerShell module that has an easy to remember cmdlet which Invokes a DSC build on a server after going out to the SQL database and retrieving the GUID that is assigned to that server. That saves us a lot of time.

As I mentioned earlier, this process was developed prior to my involvement but it has worked pretty well for us so far. The decision was made to modularize our configurations in a similar way to how source code is modularized to promote reuse and to align with the Infrastructure As Code mentality.

There are tools out there like Chef and Puppet which can utilize DSC so we plan on looking into those to see if they would be a good fit as well.

Drawbacks

There are a few drawbacks to using composite configurations that I have found in my experience so far. The first major drawback that we have run into, is there is no ability to use the DependsOn attribute across composite configurations. For example, if you have one composite configuration which installs the Web-Server windows feature and you have another composite configuration which handles your lockdown settings for IIS, you can’t make any of those lockdown settings depend on the Web-Server role being installed. The MOF file will get created, but the order isn’t kept intact and you could get errors if the lockdown settings try to apply before the roles are installed. To get around with this we have opted to create 2 configurations to apply to the servers. First, we have a base configurations which pretty much only installs Windows Roles and Features. After that configuration has finished, we change the GUID to a final configuration in our management too and re-run DSC to set everything else. I have heard that there may be an update for PowerShell 4 that works around this but I haven’t looked into that yet. There are also Partial Configurations that were introduced in PowerShell 5.0, but we haven’t explored the new version yet.

Another drawback that I have seen is that with composite configurations, you no longer have a single point of reference of what your configuration is doing. You need to open each configuration to find out what composites it includes then go to a different folder to open those configurations and if you nest them too much, it can get confusing and difficult to keep track of.

It took me a while to catch on to composite resources and I don’t create them too often so I always have to go back to my notes. Writing things out has really seemed to help me to understand our configurations and process a little more and I’m hoping that this will be a good reference for me in the future.

If you have any input on any of this or ideas, feel free to let me know.

Matt