Jeremy Davis
Jeremy Davis
Sitecore, C# and web development
Article printed from: https://blog.jermdavis.dev/posts/2015/development-environments-with-powershell-dsc-part-1

Development environments with PowerShell DSC – Introduction to DSC

Published 12 October 2015
Updated 25 August 2016
This is post 1 of 7 in a series titled Development environments with PowerShell DSC

I've written before about approaches to automating the install of Sitecore instances via PowerShell, but recently I've been working upgrading this process to set up entire servers. As part of this research I've been working on how to move the scripting over to PowerShell Desired State Configuration – Microsoft's framework for automating the configuration of servers. Having got to a position where the scripts are working and I can turn a plain copy of Windows Server into a functioning Sitecore box, I thought I'd shared an explanation of the tools and how it can be used for Sitecore development environments.

I'm going to break this up into a series of posts, as it's quite a big topic. This week is a bit of an introduction to DSC and my goals for it:

What is PowerShell DSC?

It's a scripting framework for automating the configuration and setup of servers. If you've ever used Chef or Salt then this is basically Microsoft's equivalent. If you haven't then DSC is an extension to PowerShell v4 and above that gives you two extra things:

  • Firstly: A domain-specific language for you to express the state you want your servers to end up in. Hence the "desired state" but of the name.
  • Secondly: A runtime based on the Windows Management infrastructure that can process your descriptions and do all the hard work of getting the server into the state you described

DSC is designed to support the DevOps concepts of automating infrastructure tasks and having executable code for describing server state which can also be seen as documentation of the state you require. It allows you to move towards the idea of having servers being disposable – since the process of building them is automated, creating and destroying them is easy. You can get to a state where you click a button in the management tools for Hyper-V (or your preferred Virtualisation framework) to spin up a new virtual machine and it automatically pulls the right configuration from a central location and configures itself to meet your requirements.

What does a basic script look like?

Here's a very basic script, which can be used to install IIS onto a machine:

Configuration EnableIIS
{
    Node “WebSvr001"
    {
        WindowsFeature WebServer
        {
            Name = "Web-Server"
            Ensure = "Present"
        }
    }
}
EnableIIS 

					

The outer block declares a Configuration. That's basically naming the DSC script. Any valid PowerShell identifier can be used here. The name is important because (under the surface) what you're declaring here is kind of like a special CommandLet – hence we need the name so we can execute it later.

The second block declares a Node. Nodes describe the name of a server (or servers) to which the script is relevant. Here it's hard coded to a machine named "WebSvr001", but commonly it's declared as an expression so that one Configuration can be applied to many servers. You can also declare multiple Nodes inside one Configuration, for example to declare a script for a cluster of servers.

The inner block describes a "resource" – which is what DSC calls the instructions for a particular bit of state you want to affect. In this case we have a WindowsFeature resource, which knows how to install a feature of Windows. Again these need a name, and the names of resources are used to allow us to specify dependencies in more complex scripts. Usually a script will have many resources, describing the set of configuration changes required. Resources also take parameters, and here we're specifying the Name of the feature to install ("Web-Server" is the feature name for IIS) and with Ensure we're saying that DSC should make sure that this feature is present. One of the benefits of DSC is that it is state-aware, so if IIS was already installed this script would do nothing. And (obviously) you could also construct a resource whose job was to remove a windows feature if you wanted.

In effect, the script describes the same process as logging on to a server, firing up the Server Manager and using its UI for Roles & Features to install IIS.

There are lots of resource types available as part of DSC. You can unzip archives, install programs, control services, create files and directories, copy files and directories, add users to Active Directory and run raw PowerShell by default. But you can also extend DSC with your own specialised resources – and there are lots available on the Internet to make use of as well.

What happens when we run the script?

The final line of the script above uses the name of our Configuration to invoke the script. So what happens when we run this? Well interestingly, it doesn't actually change the state of any servers. Two things happen:

Firstly, the PowerShell DSC framework parses your script and does a syntax check. If you've made any errors then you get an error explaining what it thinks is wrong.

Secondly, the script is translated. The Windows Management framework can't read the PowerShell DSC scripts directly. It works on a cross-platform standard for describing server state called the "Managed Object Format". So DSC translates your script into the appropriate .MOF files – one for each server you described in your Node blocks. It saves these files into a folder named for your Configuration.

These files contain basically the same data as your DSC script, just in a format that's more focused on machine readability than the original PowerShell DSC code. And since it's cross platform, it's possible to generate these files to be run on UNIX-style servers if you're working in a mixed environment. (And you can use native tools on those servers to generate .MOF files for Windows too if you wish)

Making a machine change state

Now we have our .MOF file, what do we do with it? Well DSC provides a new PowerShell CommandLet called Start-DSCConfiguration which is used to pass the .MOF files to the underlying management infrastructure and run them. In a test environment, you run something like:

Start-DSCConfiguration -Path .\EnableIIS -Verbose -Wait -Force 

					

The Path parameter tells PowerShell where to look for .MOF files to process. The Verbose flag tells the runtime to display debug information so that we can see what's happening, and the Wait flag tells it to run synchronously so we can see what's happening. Finally the Force flag tells DSC that we're pushing a specific configuration, rather than expecting it to pull down appropriate configuration from a central server.

If we run that, we see something like this:

DSC Processing

The debug output is saying that it's examined the server and discovered that IIS is already installed – so in this case it's done nothing.

Making scripts more generic

So far, however, our script has been specific to a single server. That's nice for an example, but it's not very practical for the real world. You'd end up maintaining a separate script for each of the servers you manage and that would be a pain to keep everything up to date. Ideally we'd like to be able to write scripts which describe the state needed for a particular class of server (e.g. "Sitecore Content Deployment Webserver" or "SQL Server") and then just map those configurations to the appropriate server names for any given environment. How can we express our installation requirements in a more generic way to make them reusable?

It's actually pretty simple – we can pass configuration data into our DSC Scripts, and generalise the script to process the data. DSC expects you to pass in a dictionary object containing a tree of name-value pairs that describe your config data. For example, expanding on the IIS example above, we might pass in the following configuration data:

@{
    AllNodes = @(
        @{
            NodeName = "WebSvr001"
            Role = "IIS, SQL"
            TempFolder = "c:\dsc"
            InstallPath = "C:\Program Files\My Application"
            SQL = @{
                InstallConfig = "SqlConfigurationFile.ini"
                SQLPassword = "p@55w0rd"
            }
         }
    );
}

					

It looks a bit cryptic, but it's just PowerShell's way of describing a tree of dictionary/array objects containing some data. (So it's basically PowerShell's equivalent of writing your config as JSON in a JavaScript application)

The AllNodes collection is an array of bits of data for individual Nodes in your DSC scripts. You can declare one entry in that collection per server that you want to configure. Each of these array elements needs the NodeName attribute (to give the name of the server this data is for) and the Role attribute to give the set of things which are going to be deployed to that machine. All of the rest of the data is up to you, and can be set up to meet whatever your needs are. For the purposes of an example here I've declared a couple of simple string properties for TempFolder and InstallPath and a dictionary of string properties for SQL. (I wrote a bit about accessing these nested config variables in a previous post)

You then need to modify your DSC Scripts to process the relevant bits of this data. The script for adding IIS doesn't actually need much of this information, but you might it to look like this:

Configuration EnableIIS
{
    Node $AllNodes.where{ $_.Role.Contains(“IIS") }.NodeName 
    {
        WindowsFeature WebServer
        {
            Name = "Web-Server"
            Ensure = "Present"
        }
    }
}
EnableIIS -ConfigurationData ".\configData.psd1"

					

There are two key changes here. The first is the declaration of the Node block. Instead of specifying the name of the server to apply this script to, it has changed to an expression. The code looks at all the elements in the AllNodes data we declared above, and returns the NodeName for any entry where the Role attribute contains "IIS". So our config data can describe a set of servers for databases, web servers etc. and this bit of DSC Script will only affect those servers which should have IIS installed.

The second change is that when we invoke our Configuration, we're now passing in a parameter called ConfigurationData that specifies the path to finding the config data above. This is how DSC knows where the config is when it processes your script to generate the .MOF files. Hence you can have multiple configuration files, and run a DSC script against each of them to (for example) generate .MOF files for development servers in different clusters.

As you'll see later in the series, you can also have custom parameters to pass in to Configurations, in much the same way you can pass parameters in to CommandLets you've written in script.

So what are we going to do with all of this?

Install all the things! My goal over the next few posts is to write up how we can use the basic concepts above to construct DSC scripts to take a Windows server from its basic post-install state to running Sitecore 8.x for development. I plan to cover the following aspects of that:

  • The configuration data required for the various scripts
  • Basic configuration of the server with Windows features and settings
  • Installing SQL Server
  • Installing MongoDB
  • Installing Sitecore
  • Configuring Sitecore with Packages
  • Using PowerShell Extensions remoting

I'm not sure how many posts this will end up with, but I'll go through the assorted bits of script, and explain how they work for each of the key steps described above.

↑ Back to top