Using One MOF to dynamically DSC your environment

One MOF Overview

Desired State Configuration (DSC) is a powerful DevOps tool enabling you to provide a consistent, standardized configuration throughout your environment. DSC is typically node specific, requiring you to author many Management Object Format (MOF) files, or get creative with partial DSC configurations. In this post we’ll explore using both standard and custom DSC resources to break out of managing many MOF files or partial DSC configurations. One MOF in Azure can be used to dynamically configure all devices throughout your environment.

This topic covers some advanced DSC topics. If you aren’t yet familiar with the DSC authoring process I highly recommend Learning PowerShell DSC as a great starting resource.

Video Walk-through

If you prefer video format over written documentation I discuss the topic of leveraging one MOF for multiple servers in this PowerShell + DevOps Global Summit video:

All resources from the video, including code demoed and the slide deck, are available on the PSSummit-One-MOF repository.

DSC is typically Node Specific

Whether you’re using DSC Push or DSC in a pull model, you typically author a MOF for each server or device you want to configure. In DSC push, you push the corresponding MOF to its respective node, and in pull, a device will pull it’s respective MOF from the DSC pull server.

DSC Push vs PullBreaking out of a many MOF world

The result of this approach is having hundreds or potentially thousands of MOFs to create, manage, update, and maintain. By simply getting away from hostnames in the configuration, in favor of localhost, it’s possible to achieve one MOF quite easily. However, this approach requires a very generic configuration that is quite inflexible.

Breaking out of a many MOF world down to using just one MOF

Achieving one MOF

To be clear, there is no way to make a MOF smart or dynamic. However, we can empower a MOF to behave as if it were truly intelligent and dynamic by incorporating the following:

  • localhost
  • Custom DSC resources
  • Locally sourced configuration data
  • Azure Automation DSC

Progressing Towards One MOF

Lets take a look at a few DSC examples and step towards changing a traditional DSC configuration to be more flexible, so that it can be applied to any device in your environment.

Typical DSC Configuration – Fat Edition

In the below example we will generate a MOF for two servers, Server1 and Server2. This example will compile down to two MOFs, one for each respective server. Note the inefficiency of this approach. Although this configuration holds identical settings, the code is duplicated. This leaves you with a very fat configuration to maintain and update.

Typical DSC Configuration – Diet Edition

Here we take the same configuration but incorporate the use of the $Allnodes variable.  This allows us to only declare the code once, and then compile MOFs for each server in the environment. In this example, a MOF would be generated for Server1, Server2, and Server3. Note the limitations of this use case though: the configuration must be identical for every node. We also still have many MOFs to manage (one for each server).

Pushing Typical DSC Configurations

The examples provided previously would provide you with MOFs for the servers specified. They can be loaded onto a pull server, or pushed out in the following manner:

Using localhost in the configuration

Instead of specifying host names, you can instead leverage localhost. All devices share this common name and can consume the MOF generated by this configuration. This allows you not only to achieve one configuration, but also only generate one MOF. However, there are some limitations to this generic localhost one MOF approach. You can’t include dynamic variable content. Example: you couldn’t include an IP address, hostname, or other unique item into your configuration. Every device in your environment would have that (no longer) unique setting applied.

Pushing a different way with a localhost DSC configuration

Keep in mind that if you use localhost that your ability to push using the normal Start-DSCConfiguration cmdlet over a CIMSession goes away. That cmdlet expects to find the MOF file that shares the hostname you are pushing too. If it can’t find it, it won’t push. You could get around this limitation by copying the localhost MOF for ever server, and then renaming it. An alternative to that inefficient approach is simply copying the localhost MOF over to the end device and applying the DSC locally via a PSSession:

Getting creative with the impossible

Observe the below example and you’ll find several surprises. First, we’re still using localhost. Second, we’ve added a unique condition – an IP address. This seems like a bad idea, right? It is. If you compiled this down, and pushed it out, it would cause bad things to happen in your environment. (Every device would have the same IP!)

So, what do we do? Well, the ability to IP something via DSC comes in the form of the DSC community module, xNetworking (now renamed to NetworkingDsc).

Because it’s completely open source, you can download it, and edit it to do whatever you want. To make my own DSC incredibly flexible, and dynamic in nature, I’ve removed the IP and InterfaceAlias requirements from the schema. I literally just deleted them. I also deleted those from the parameters of the Get, Set, and Test in the IPAddress DSCResource. In place, I added the following code to Get, Set, and Test:

$filename = "C:\DSC\local_config.json"
Write-Verbose "Importing config file info about NICs..."
$t = Get-Content -Raw -Path "$filename" -ErrorAction Stop | ConvertFrom-Json
Write-Verbose "Config file info successfully imported. Evaluating data..."
$IPAddress = $t.'Server-Object'.PrimaryIP
$macAddress = $t.'Server-Object'.MACAddresses.Public
Write-Verbose "IP Info: $IPAddress"
Write-Verbose "MAC Address: $macAddress"
$index = Get-NetAdapter | Where-Object {$_.MacAddress -eq $macAddress} `
| Select-Object -ExpandProperty ifIndex
$InterfaceAlias = Get-NetIPAddress -InterfaceIndex $index | Select-Object -ExpandProperty InterfaceAlias

Lets digest what this is going to do. We’ve flipped from a top down model, to a bottom up model. Instead of specifying the details, like the IP, in the configuration, the customized NetworkingDsc module is sourcing that data locally from the device! Feeling lost? I demo this live in the video I’ve linked at the top of this post so you can follow along.

This pushes the responsibility of what will be applied to the individual server. In this example, in the form of a configuration file: local_config.json.

Local Configuration Data

Ok, so how do you get the configuration file onto your device? That’s on you to determine. There are a variety of ways to query, parse, and create this type of configuration information for deployment and ongoing configuration use. It can be incorporated into your deployment process, you can write a custom solution, or you can leverage another automation toolset like Chef, or Puppet.

Don’t get wrapped up in the concept of IP’ing your environment with DSC. IP assignment is a challenge in and of itself, and there are a variety of ways to do it correctly. In this post, IP assignment via DSC is as an example of something truly unique that can still be achieved with only one configuration. With NetworkingDSC customized and combined with locally sourced configuration data, it can now make intelligent decisions about how to configure a device. You can still configure unique entities, like an IP – but maintain only a single DSC configuration, and a single MOF.

Azure Automation DSC

Armed with a single localhost MOF, (smart) custom DSC modules, and a method for generating local configuration information, it’s time to engage Azure Automation DSC.

While there are a few other options out there for DSC pull capabilities (Tug, MS Server Pull Services) Azure Automation DSC is bar none the best choice.

Azure Automation DSC with One MOF DSC node configuration

Once you have an Azure Automation Account created, your single DSC configuration can be uploaded to DSC configurations and compiled to OneMOF.localhost

From anywhere in the world: Azure, AWS, Google Cloud, or on-prem – with just 443 access you can deploy, have machines build themselves, and monitor those devices in Azure Automation DSC for ongoing compliance.

The LCM of the server can be pointed to Azure Automation DSC and it’ll pull your configuration and associated DSC modules down. Again, because you control what actions those modules perform – the application of that single Azure hosted MOF can be as dynamic and as smart as you engineer it to be.

The trick about custom modules and Azure DSC

Azure Automation is incredibly sensitive about the requirements regarding uploading a module. The custom DSC module has to be zipped, and normal zipping methods simply won’t do. You’ll have to leverage the Publish-ModuleToPullServer cmdlet to zip it in a way that agrees with Azure Automation. Don’t forget to rename the module to not include the version number, as Azure Automation doesn’t like that either!

Closing Thoughts

On an initial read, this may seem like a lot to take in. I highly recommend the video linked above where you can see these concepts in action.

Engineering for a single MOF doesn’t make sense for every workplace. You may be working in an environment where every device truly is a unique snowflake. That’s fine. You’ll likely find some form of DSC approach in this write-up that suits your needs.

In environments where devices do require similar configurations, engineering for a single dynamic MOF offers many benefits. Combined with Azure Automation DSC, simply point servers to the Azure Automation Account, and they build themselves. Once completed, you get ongoing reports of their configuration health status. It will change the way you approach deployments and configuration management, for the better.

A benefit to the single MOF approach combined with Azure Automation DSC is the ability to make fleet wide changes quickly and easily. If your DSC requires an adjustment, make changes, upload, recompile, and every server associated with that configuration will apply the change. Keep in mind that this is a double-edged sword. While incredibly empowering, it can also be incredibly dangerous if you aren’t properly vetting your DSC changes prior to implementation.

Finally, a single MOF means devices throughout your environment have a like-for-like configuration. You’ll find this really impacts the way your support teams approach troubleshooting. It introduces the concept of the Macro vs the Micro. It eliminates the thinking of, maybe this adjustment or registry key change will resolve the issue. If you aren’t making that change in DSC for the fleet, why make it all?


4 Responses

  1. Great article thanks for writing (I will check out the other parts too), can you please clarify the following points for me please

    Where does the client (pulling the MOF and associated Resources) store these components once pulled down ?

    Does the client onces it has pulled down the MOF write these MOFs into the CIM database on the local client ?

  2. EKW says:

    I started with the ‘diet’ way of deploying DSC, but I’m considering going to the ‘Fat’ way. When defining an entire environment, there are a lot of $AllNodes.Where statements that as they build up, become confusing to look at.

  3. Ramprakash Kathiravan says:

    I have a number of custom modules. I want to import custom modules dynamically in the configuration file. Could you tell the possible way to do this?

  4. Bob Dillon says:

    Thanks for the great demo and concept! Is the main reason you’re editing the DSC resource because variables/objects aren’t globally scoped in DSC? So what happens in a Script Resource stays in a Script Resource (I’ve always wanted to say that..).

Leave a Reply

Your email address will not be published. Required fields are marked *