HOWTO: Desired State Configuration (DSC) Overview

I found myself curious if I could get my entire lab environment to build itself from scratch in a 100% automated fashion including deploying and configuring Active Directory.

All of the cmdlets to perform these tasks already exist in PowerShell and are trivial to perform individually.  But what happens if you want to combine them together?  In order to do that, you have to be able to manage restarts and be able to continue your script where it left off.  That turns out to be a non-trivial problem for traditional PowerShell scripts.  As I was researching how I might accomplish this, I stumbled across something called “Desired State Configuration” or DSC.  I’ve heard it talked about constantly over the last year or two and all of the major PowerShell bloggers have stated that it is the concept to learn after you’ve got the basics of PowerShell down.  I’ve never had a reason to look into it though… until now.

The first thing I did was start watching the Microsoft JumpStart series on DSC.  I just assumed it would be a module’s length but to my shock and surprise, Microsoft created an entire course on just DSC and that course is 2 full days – equal to that of the JumpStart for PowerShell itself!  The next thing that jumped out at me is that at the beginning of the video series, one of the hosts and inventor of PowerShell, Jeffrey Snover said:

“DSC is not just a priority at Microsoft, it is THE priority at Microsoft.”  That may sound like hyperbole coming from most people but remember that Jeffrey Snover is now the Technical Lead Architect for Windows Server.  So even accepting he probably meant his team specifically, that’s still a big deal for those working in IT as most of our time is spent with the products he is responsible for.

With that backstory out of the way, what exactly is Desired State Configuration and why should you care?  Here is what I’ve been able to piece together so far.  DSC is a mechanism for you to define how a server or servers should be configured.  Notice I did not say how specifically to configure them.  This is the first thing that took me a while to wrap my head around.  In DSC, you never define HOW to configure a server, only WHAT you want the final product to look like.  Think of DSC more as a manager than a programmer.

The way this is accomplished is Microsoft (and others) create something called “resources” which are effectively large, complex PowerShell modules that are written by professional programmers that contain all of the logic and error handling and dependency management that is almost always missing from amateur scripts.  You then use a special subset syntax of PowerShell to declare what you want and PowerShell will go out and do it for you.

You may be asking yourself how these configurations are processed.  It turns out that starting with PowerShell 4 (Windows 2012 and Windows 8), Microsoft introduced an entirely new backend management engine into the core of Windows called the “Local Configuration Manager” or LCM.  You can think of this conceptually as similar to the engine Windows has for processing Group Policy.  Because the LCM is integrated into the very heart of Windows, it can enforce configurations in a way that other approaches cannot.  The practical consequence of this is that one of the features of DSC is that it will allow you to prevent “configuration drift”.  That’s where a server or service is initially configured and everything works, but then later on you or someone else comes around and changes something (disables a service, changes a configuration file or registry key or just about anything else) and over time leads to inconsistencies.  This is especially important when you have a fleet of servers that should be identical (ie Web Servers).

In other words, with DSC you can define a centralized, standardized configuration for your server, push it out to all your servers and now be guaranteed that the settings you defined in the configuration will stay that way.

Now I’ve quite literally only scratched the surface of DSC and have realized just how deep the rabbit hole goes.  But as you may recall from the beginning of this post, I had a specific business problem I was trying to solve which was to automate the creating of a fresh lab environment.  I don’t care about configuration drift.  But I do care about being able to complete multiple tasks with reboots in between without any manual user intervention.  Specifically I was interested in automating the following:

1) Change computer name to name that matched my naming conventions for domain controllers

2) Reboot to apply the name change

3) Configure a specific static IP address and default gateway to function in my home lab environment

4) Install Active Directory Domain Services and Management Tools

5) Create a new domain controller in a new forest

6) Reboot to complete the promotion of the server as a domain controller

7) Create a new customer AD user that I will use as my administrator account

8) Make that new user a member of Domain Admins

I wanted all of that to happen by simply having a PowerShell script on the desktop of my fully patched but otherwise generic Windows 2012 R2 template.  In other words I wanted to clone out a new VM, double click the file, walk away and come back <7 minutes later and log into a new domain as my custom admin user.  I’m pleased to report that I was entirely successful in this quest!

I’ve included the script I created below for reference. Of course the standard disclaimers apply in that I cobbled this together from lots and lots of button mashing from lots of sources and so I’m sure there are better ways to handle some of this.  But it works and I never have to manually build another lab DC again!

A quick note on requirements for this script.  While DSC was introduced in PowerShell 4, it has been massively expanded upon in PowerShell 5.  Therefore in order to leverage this script you’ll need to ensure that the “Windows Management Framework 5 Production Preview” is installed (https://www.microsoft.com/en-us/download/details.aspx?id=48729)

You will also need to download those “resources” I mentioned earlier.  (I could automate this in the script but figured I’d separate it to demonstrate a cool new feature of PowerShell 5).

PowerShell has a new component called “OneGet” which is basically a means of downloading and installing applications and modules straight from Microsoft from the command line.

In this case, with the WMF5 installed, simply type:

Install-Module xActiveDirectory, xComputerManagement, xNetworking

That’s about all there is too it.  Now something to keep in mind and this is something that frustrated me for the longest time.  Unlike traditional PowerShell scripts, you do not “run” DSC scripts in the traditional sense.  They are not directly executable.  Instead what you do is define a “configuration” and save that to a file.  You then use the PowerShell cmdet Start-DSCConfiguration and pass in the folder (not file) where your configuration resides.  It will then be applied and optionally enforced from there.

Hopefully that saves you some frustration if you decide to play with this.  If you have questions, please don’t hesitate to ask in the comments.  I don’t know a whole lot yet but there is no reason you shouldn’t be able to stand on my shoulders and learn from my mistakes.

# Script uses DSC (Desired State Configuration to rename PC, assign network details, promote as DC and make new domain admin user
# DSC; Active Directory; Domain Controller; Networking

<#
REQUIREMENTS: 

Windows PowerShell 5 via Windows Management Framework 5 Production Preview: https://www.microsoft.com/en-us/download/details.aspx?id=48729
DSC Resources: (Install-Module [modulename])
    xActiveDirectory
    xComputer Management
    xNetworking
#>

# The Local Configuration Manager (LCM) must be configured in advance to know to reboot as soon as one is required
# This cannot be set directly but rather the setting must be saved into a MOF (Management Object Format) plain text configuration file
# This file must then be read by the Set-DSCLocalConfigurationManager Command
configuration EnableRestarts
{
     LocalConfigurationManager            
        {            
            RebootNodeIfNeeded = $true
        }            
}

# We use the temp file here since we don't actually need to keep this configuration file after execution
EnableRestarts -OutputPath $Env:Temp\ | out-null
Set-DscLocalConfigurationManager -Path $Env:Temp | out-null
Remove-Item C:\Users\ADMINI~1\AppData\Local\Temp\1\localhost.meta.mof


# Request input that is required from the user
do { $ComputerName = Read-Host 'Enter new Computer Name' } while($ComputerName -eq "")
do { $DomainName = Read-Host 'Enter name of new domain/forest' } while($DomainName -eq "")
do { $DCIP = Read-Host 'Enter the IP address of the new domain controller' } while($DCIP -eq "")

# Configure all of the settings we want to apply for this configuration
$ConfigData = @{
    AllNodes = @(
        @{
            NodeName = 'localhost'
            MachineName = $ComputerName
            DomainName = $DomainName
            LabPassword = 'password'
            ADAdminUser = 'adminuser'
            IPAddress = $DCIP
            InterfaceAlias = 'Ethernet0'
            DefaultGateway = '10.0.0.1'
            SubnetMask = '24'
            AddressFamily = 'IPv4'
            DNSAddress = $DCIP, '8.8.8.8'
            PSDscAllowPlainTextPassword = $true
        }
    )
}

Configuration BuildTest01 {

    Import-DscResource -Module xActiveDirectory, xComputerManagement, xNetworking
 
    Node $AllNodes.NodeName 
    {
        LocalConfigurationManager 
        {
            ActionAfterReboot = 'ContinueConfiguration'            
            ConfigurationMode = 'ApplyOnly'            
            RebootNodeIfNeeded = $true  
        }

        # All of the resources that require a password expect a PSCredential object -- even those that only want a password
        # For lab purposes we will use the same password everywhere
        $password = ConvertTo-SecureString $Node.LabPassword -AsPlainText -Force 
        $username = "ad.vanlab.net\administrator" 
        $LabCred = New-Object System.Management.Automation.PSCredential($username,$password)

        # Wit DSC, this is literally all you need to do to change the name of a computer.  Because the LCM is set to reboot when needed above, that's even taken care of
        xComputer SetName { 
          Name = $Node.MachineName 
        }

        # This is all that is needed to configure network details
        xIPAddress SetIP {
            IPAddress = $Node.IPAddress
            InterfaceAlias = $Node.InterfaceAlias
            DefaultGateway = $Node.DefaultGateway
            SubnetMask = $Node.SubnetMask
            AddressFamily = $Node.AddressFamily
        }
       
        xDNSServerAddress SetDNS {
            Address = $Node.DNSAddress
            InterfaceAlias = $Node.InterfaceAlias
            AddressFamily = $Node.AddressFamily
        }

        # This requires that Active Directory Domain Services role be present on the machine.  If it isn't, go install it.  (All that logic is taken care of behind the scenes)
        WindowsFeature ADDSInstall {
            Ensure = 'Present'
            Name = 'AD-Domain-Services'
        }

        # Make sure the Active Directory Management tools are installed
        WindowsFeature ADDSTools            
        {             
            Ensure = "Present"             
            Name = "RSAT-ADDS"             
        }           


        # Build a domain controller.  This is all that is required.  The rest is taken care of automatically!
        xADDomain FirstDC {
            DomainName = $Node.DomainName
            DomainAdministratorCredential = $LabCred
            SafemodeAdministratorPassword = $LabCred
            DependsOn = '[xComputer]SetName', '[xIPAddress]SetIP', '[WindowsFeature]ADDSInstall'
        }    
    
        # Assign a custom admin user so we don't use the default 'administrator' account
        xADUser FirstUser
        {
            DomainAdministratorCredential = $LabCred
            DomainName = $Node.DomainName
            UserName = $Node.ADAdminUser
            Password = $LabCred
            Ensure = 'Present'
        }

        # There is no built in resource I could find to change user group membership in AD so we use the "script" resource to run a traditional command
        Script NewADAdminUser
        {
            SetScript = { Add-ADGroupMember -Identity "Domain Admins" -Members $Using:Node.ADAdminUser }
            TestScript = { $false }
            GetScript = { }
        }
        
    }
}

# We now need to build a MOF (Managed Object File) based on the configuration defined above and based on the custom configuration parameters we defined
# This will place the MOF in a folder called "BuildTest01" under the current operating folder
BuildTest01 -ConfigurationData $ConfigData


# We now enforce the configuration using the command syntax below
Start-DscConfiguration -Wait -Force -Path .\BuildTest01 -Verbose

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.