A True Beginners Guide to Desired State Configuration (DSC)

In this article, I attempt to describe how to get started with Powershell Desired State Configuration for complete beginners. I'll walk through the basic parts of what is required to use DSC, and how to implement it in a small example. Read on for more.


7/7/2018 Update :

The references I'd used when I wrote this were a bit outdated. Since then, WMF has been updated to 5.0 which means a lot of the resources were updated. For the more current version of the pull server configuration for the DSCServer1 example, please see below:

You'll have to use the new xPSDesiredStateConfiguration modules new references to properly build if you're installing on vanilla Windows Server today. Ultimately the code I used to get the pull server working in my latest environment was:

First i had to create a new self-signed cert:

New-SelfSignedCertificate -CertStoreLocation 'CERT:\LocalMachine\MY' -DnsName "DSCPullCert" -OutVariable DSCCert

Then I could run the configuration:

#requires -Version 4 -Modules PSDesiredStateConfiguration
configuration DSCPullServer
    param  
    ( 
        [string[]]$NodeName = 'localhost', 
        [ValidateNotNullOrEmpty()] 
        [string] $certificateThumbPrint 
    ) 
    Import-DSCResource -ModuleName xPSDesiredStateConfiguration 
    Import-DscResource -ModuleName PSDesiredStateConfiguration
    Node $NodeName 
    { 
        WindowsFeature DSCServiceFeature 
        { 
            Ensure = 'Present' 
            Name   = 'DSC-Service'   
                      
        } 
        xDscWebService DSCPullSRV
        { 
            Ensure                  = 'Present' 
            EndpointName            = 'DSCPullSRV' 
            Port                    = 8080 
            PhysicalPath            = "$env:SystemDrive\inetpub\DSCPullSRV" 
            CertificateThumbPrint   = $certificateThumbPrint          
            ModulePath              = "$env:PROGRAMFILES\WindowsPowerShell\DscService\Modules" 
            ConfigurationPath       = "$env:PROGRAMFILES\WindowsPowerShell\DscService\Configuration"
            RegistrationKeyPath     = "$env:PROGRAMFILES\WindowsPowerShell\DscService"   
            AcceptSelfSignedCertificates = $true    
            State                   = 'Started' 
            DependsOn               = '[WindowsFeature]DSCServiceFeature'      
            UseSecurityBestPractices = $false                
        }
    }
}
DSCPullServer -certificateThumbPrint $($DSCCert).Thumbprint
Start-DscConfiguration .\DSCPullServer -Wait -Verbose


Who this article is intended for:


Admins who have a bit of knowledge about how to write scripts and knowlege of how basic roles and features are installed on windows server. They should also possess an iron will and a real interest in automation. Your patience will be tested.


What DSC is:

DSC is a technology built on powershell that allows you to configure an OS to be exactly the way you want it through the use of MOF files. It leverages WinRM and powershell remoting to copy a file and execute it.

What you will need before you start:

At least two machines or VMs running Windows Server 2012 or later, with Powershell 4.0 or later.
This article assumes that your servers are able to "talk" to each other (no firewall issues with winrm or web services). It also assumes you have a DNS in place (for name resolution)

Download the xPSDesiredStateConfiguration Module from the Powershell Gallery:
https://gallery.technet.microsoft.com/xPSDesiredStateConfiguratio-417dc71d

Once you have it downloaded, extract it to C:\Program Files\WindowsPowershell\Modules. This will let you use the import-dscresource command in your configuration files. For each new resource you'd like to reference, you'll have to download the module, and extract it to the Modules folder to use it.


Before we begin

Key words: Lets outline some key phrases that you may or may not have heard before.

Node: The target server for the configuration state
Configuration: the desired state of the node, written as a kind of function within powershell
Configuration file: a .ps1 file that defines the configuration as a kind of function.
MOF File: The compiled configuration that the Local Configuration Manager of a node can read and apply.
Resource: the primary building block that configurations are made of. Think of a DSC resource as a product add-on. A resource extends and defines what DSC is able to do on a particular node.

Potential gotchas:
When going through some of these things, there are a few things you want to check before starting.

  1. Firewalls: Either disable them for testing, or enable an exception for WinRM.
  2. Enable WinRM/PSRemoting on all Nodes.

Basic Workflow:

So in short, the process goes like this: Configuration file (a .ps1) generates a .MOF file. The MOF file is distributed to the node. Nodes configure themselves to what is stated on the MOF file. The MOF file is applied by the Local Configuration Manager, or LCM for short.



There are two ways to distribute files to the nodes. Pull and Push modes.

Pull vs Push:

The server with the DSC service can be configured in two ways. Pull and Push.
  • Nodes in push mode will not display any sort of autonomous behavior. Instead, they'll sit back and wait for the DSC server to copy the MOF file to them via WinRM.
  • Nodes configured in Push mode will have an MOF file stating that they'll need to check in with the DSC server to get their configuration.

House keeping:

To help keep things organized. I advise using a separate folder to house all of your DSC related .ps1 files. I have them in a folder at C:\Source\DSCConfigurations. This way when I run the .ps1 files, they're generated into subfolders under the DSCConfigurations directory. 

Configuration Files:

First you write a configuration file as a .ps1 This is the template that your DSC server will "enforce" on your node. The configuration file is made up of resource calls, or simply, lines of code that reference DSC resources.

The script is primarily 5 parts:

  1. Define your configuration name
  2. Define your resources that you'll need to use and import them
  3. Define your nodes
  4. Configure your resource configuration blocks for each node
  5. Compile the configuration

Here's a basic example:


Configuration WebsiteTest {

    # Import the module that contains the resources we're using.
    Import-DscResource -ModuleName xPsDesiredStateConfiguration

    # The Node statement specifies which targets this configuration will be applied to.
    Node 'localhost' {

        # The first resource block ensures that the Web-Server (IIS) feature is enabled.
        WindowsFeature WebServer {
          Ensure = "Present"
          Name = "Web-Server"
        }

        # The second resource block ensures that the website content copied to the website          root folder.
        File WebsiteContent {
          Ensure = 'Present'
          SourcePath = 'c:\test\index.htm'
          DestinationPath = 'c:\inetpub\wwwroot'
        }
    }
}
WebsiteTest


The first line defines the name of the configuration function "WebsiteTest". The next line imports the necessary modules, xPsDesiredStateConfiguration in this case. The line following that defines the targets or "nodes",and under that, how to configure the nodes with the resource blocks, calling on resources WindowsFeature and File. At the very end, the script executes the function to output the MOF file. the file name is determined by the Node name. In this case it's "localhost".

MOF Files:

Executing the script will generate a MOF file for each node in your configuration file. Simply navigate to the folder containing the .ps1 and run it. Note: whatever directory you call the script from is where the configuration directory with the .MOF file will be created. i.e. if you're in your default documents folder and you run C:\Source\DSCConfigurations\websitetestconfigdsc.ps1, the folder will still be created in your documents folder.

The output is a .MOF file in a folder by the name of the configuration within the folder you ran the script from. For example. If you ran the above script from C:\Source\DSCConfigurations, you'd have a localhost.mof file in a folder at C:\Source\DSCConfigurations\WebsiteTest. This is important to understand for the next step.

There are multiple ways to apply the MOF file to a computer.

  1. One is to simply copy the .MOF file to the target server and run the Start-DscConfiguration command against the folder containing the file. For instance, for our server we wish to have the configuration outlined above, we could copy the folder to C:\Source\Websiteconfig, and then run Start-DscConfiguration C:\Source\WebsiteConfig. Adding the -verbose parameter will give you more insight as to what's happening during the process of applying the configuration.
  2. Another way to apply a configuration is to compile your MOF with a servers actual name in the node. Instead of just saying "local host", you'll put in WebServer1. When the script gets compiled to the folder holding your configurations (C:\Source\DSCConfigurations if you're following the example given above), When you run the function within that folder, it'll generate a WebsiteTest folder with a WebServer1.mof file. Running Start-DscConfiguration C:\Source\DSCConfigurations\WebsiteTest against the folder (you cannot run this command against a specific file) will apply the .mof file to the server with the matching name.


The two scenarios given above are referred to as "push" mode. It requires less setup to begin working, but more administrative overhead if you're not running a scheduled task.



When configuring "pull" mode, there's a little more setup, but it removes a lot of the "hands on" work. Here's the simple breakdown:


  • The DSC server hosting the PSDSCPullServices web service is setup and ready to respond to incoming configuration requests or "pulls". 
  • The target servers are configured as target pull servers with a configuration stating as such.
  • The DSC has a directory in place full of mof files that are named as a guid with the .mof extension. 
  • The pull servers, when set up with the pull configuration, referenced where they could find their configuration (the DSC web address) and what GUID they're going to reference.
So at the end of the day, the node has its reference config stored on the DSC server under a specific GUID, when the node says to the DSC server "Hey, I'm looking for the config with this GUID." The server responds and says "Here, this is how you're supposed to be configured!". All over WinRM.



On the DSC server, you'll configure DSC web services and present the DSCPullService.SVC as a web directory. This can be done by running the following configuration file against your DSC server.





configuration NewPullServer
{
param
(
[string[]]$ComputerName = ‘localhost’
)

Import-DSCResource -ModuleName xPSDesiredStateConfiguration

Node $ComputerName
{
WindowsFeature DSCServiceFeature
{
Ensure = “Present”
Name = “DSC-Service”
}

xDscWebService PSDSCPullServer
{
Ensure = “Present”
EndpointName = “PSDSCPullServer”
Port = 8080
PhysicalPath = “$env:SystemDrive\inetpub\wwwroot\PSDSCPullServer”
CertificateThumbPrint = “AllowUnencryptedTraffic”
ModulePath = “$env:PROGRAMFILES\WindowsPowerShell\DscService\Modules”
ConfigurationPath = “$env:PROGRAMFILES\WindowsPowerShell\DscService\Configuration”
State = “Started”
DependsOn = “[WindowsFeature]DSCServiceFeature”
}

xDscWebService PSDSCComplianceServer
{
Ensure = “Present”
EndpointName = “PSDSCComplianceServer”
Port = 9080
PhysicalPath = “$env:SystemDrive\inetpub\wwwroot\PSDSCComplianceServer”
CertificateThumbPrint = “AllowUnencryptedTraffic”
State = “Started”
IsComplianceServer = $true
DependsOn = (“[WindowsFeature]DSCServiceFeature”,”[xDSCWebService]PSDSCPullServer”)
}
}
}

#This line actually calls the function above to create the MOF file.

NewPullServer –ComputerName DSCServer1



It's a long script, so we'll break it down to smaller pieces.


The part below just says "I'm making a new configuration, and it'll be called "NewPullServer"
configuration NewPullServer

This section is defining a parameter you can provide to the function to enter in a variable. In this case, it's the name of the node. If nothing is supplied, it'll default to localhost
param
(
[string[]]$ComputerName = ‘localhost’
)

This section is pretty plain english, we're importing a DSC resource called xPSDesiredStateConfiguration
Import-DSCResource -ModuleName xPSDesiredStateConfiguration

This section takes the input from the parameter $computername, and enters it as the node name.
Node $ComputerName

On to the actual configuration part. This part calls the WindowsFeature resource to install the DSC service on the machine. It states that the role is "DSCServiceFeature" and then uses the exact name of the service (the same as when you see it in get-windowsfeature) in the name section. For these resource blocks, you can define the 'role' name however you like.
WindowsFeature DSCServiceFeature
{
Ensure = “Present”
Name = “DSC-Service”
}

Things get a bit more involved with this bit, but at its core, its simply stating that we're going to present the C:\inetpub\wwwroot\PDDSCPullServer folder as a web directory, including the module for DSC services, setting the port to 8080, and setting the web service to "started".
xDscWebService PSDSCPullServer
{
Ensure = “Present”
EndpointName = “PSDSCPullServer”
Port = 8080
PhysicalPath = “$env:SystemDrive\inetpub\wwwroot\PSDSCPullServer”
CertificateThumbPrint = “AllowUnencryptedTraffic”
ModulePath = “$env:PROGRAMFILES\WindowsPowerShell\DscService\Modules”
ConfigurationPath = “$env:PROGRAMFILES\WindowsPowerShell\DscService\Configuration”
State = “Started”
DependsOn = “[WindowsFeature]DSCServiceFeature”
}

Next part is similar, but to advertise the compliance services.
xDscWebService PSDSCComplianceServer
{
Ensure = “Present”
EndpointName = “PSDSCComplianceServer”
Port = 9080
PhysicalPath = “$env:SystemDrive\inetpub\wwwroot\PSDSCComplianceServer”
CertificateThumbPrint = “AllowUnencryptedTraffic”
State = “Started”
IsComplianceServer = $true
DependsOn = (“[WindowsFeature]DSCServiceFeature”,”[xDSCWebService]PSDSCPullServer”)
}

After all that, we get the line that actually executes the code, generating the mof file. You can provide the name of the server that will be running your DSC pull services. In my case, it's DSCServer1.
NewPullServer –ComputerName DSCServer1

running this script as a .ps1 will generate a NewPullServer folder containing an .MOF file. Then again you can apply it to your server by running Start-DscConfiguration .\NewPullServer -wait 
-verbose.

You'll see the progress screen across the top as well as a read-out of what the script is doing.

Once that is complete, you can test by navigating to http://localhost:8080/PSDSCPullServer.svc. It should spit out a page displaying some XML like the following:
 <?xml version="1.0" encoding="utf-8" ?>
-<service xml:base="http://DSCServer1:8080/PSDSCpullserver.svc/" xmlns="http://www.w3.ort/2007/app"  xmlns:atom="http://www.w3.ort/2005/Atom"
    -<workspace>
        <atom:title>Default</atom:title>
       -<collection href="Action">
          <atom:title>Action</atom:title>
        </collection>
       -<collection href="Module">
          <atom:title>Module</atom:title>
        </collection>
      </workspace>
 </service>


Once that's up, you're ready to create a configuration for a node. It's pretty much the same process, but at the end, we'll save the .mof file with a GUID name with a .MOF extension.

Lets go ahead and create the .mof. We'll keep it simple so that we can test easily. Lets configure our fictional web server WebServer1 to have ASP.NET installed. That configuration .ps1 would look something like this:

Configuration WebServerASP
{
param ($MachineName)
Node $MachineName
{
WindowsFeature ASP
{
Ensure = “Present”
Name = “Web-Asp-Net45”
}
}
}

WebServerASP –MachineName "Webserver1"



When it runs, it'll generate the file "WebServer1.mof in a folder called WebServerASP.



Now we need to change this file into a reference that WebServer1 will ask for. we can do that with the [guid]::NewGuid() command.


So now we have our new unique identifier: c5344e47-3871-4ece-85e0-d51bbd5d917f. We'll use this string of characters to rename our WebServer1.mof above. You could copy/paste the name in windows explorer. but the easiest way to do it is to save it as a variable to be referenced later, then use the copy command, and in the same step, we can move it to where the DSCpullserver web directory will provide the configurations from.



So now we have our configuration file, in the correct directory where the web service can provide it when it's asked for. Now we need to configure our node to ask for the config. We need to set the server configuration to "Pull Mode". We do this by setting up the LCM (Local Configuration Manager), to reach out to the DSCPullServices on DSCServer1 at the correct address, and then apply the configuration id, which is the guid we created earlier. We can do this remotely by running the following on your DSC Server since you still have the guid saved as a variable.

Configuration SetPullMode
{
param([string]$guid)
Node server2.contoso.com
{
LocalConfigurationManager
{
ConfigurationMode = ‘ApplyOnly’
ConfigurationID = $guid
RefreshMode = ‘Pull’
DownloadManagerName = ‘WebDownloadManager’
DownloadManagerCustomData = @{
ServerUrl = ‘http://server1:8080/PSDSCPullServer.svc’;
         AllowUnsecureConnection = ‘true’ }
}
}
}
SetPullMode –guid $Guid 
Set-DSCLocalConfigurationManager –Computer WebServer1 
-Path ./SetPullMode –Verbose

This will reach out and configure WebServer1 to check in every so often and look for the unique id that was specified in the $guid parameter, which is to install that specific service.

And that's it! You have configured a "set and forget" configuration for WebServer1. Any time you wish to change that configuration, simply recompile the configuration to a MOF, then save it as that guid.mof file. The next time the server checks in, it'll pull that configuration and update accordingly. I hope you learned some new things while reading, I sure learned a ton putting it together.

In a later article, I'll log my experience with implementing this and replacing my current HyperVAutomationTool setup with this in place, running as a web app that will be able to generate new Hyper-V VM's. Stay tuned!

Comments

Popular posts from this blog

Creating Ubuntu Bedrock Minecraft image with Docker

Automating VM creation with DSC in Azure.

Intro to Kubernetes Series - Part 2 - Stepping it up to Kubernetes