An In-Depth Walkthrough of Desired State Configuration in PowerShell Windows Management Framework v4
A couple of weeks ago I blogged about my first experiences with the new Desired State Configuration (DSC) feature that is part of Windows Server 2012-R2 and Windows 8.1 Preview (and will also be available for Windows 8/2012 and Windows 7/2008-R2). I got a chance to sit down with the bits over the last week to experiment and see what DSC is capable of, and what’s it not capable of. One thing to note– this blog posting is based on the Preview bits. It’s likely that some of this will change by the time they ship, but my goal in this posting is to give you a pretty complete sense of what DSC is capable of, especially in the context of the existing configuration management technology in Windows–Group Policy. As I stated in my previous blog posting, I think DSC has tremendous promise as a configuration management system for Windows going forward. It has all of the features that we would want in a modern configuration management system– it’s fully extensible through PowerShell, it’s scalable through both push and pull delivery mechanisms and most importantly as compared to Group Policy, it’s got a relatively simple architecture with few moving parts and less hard affinity to Active Directory than GP. These are all good things.
As I’ve spent time with it (and I’ve by no means uncovered everything about DSC) I’ve discovered some cool stuff that I wish GP had, and also some things that make me scratch my head and hope they fix by release (or soon after). With all that said, let’s dive in and see how it all works.
The Architecture — Parts and Pieces of DSC
The first thing to understand about using DSC is that today, it’s essentially a PowerShell-based tool. If you are installing Server 2012-R2 Preview and looking for a DSC GUI admin tool, you won’t find it. To use DSC means to work with PowerShell, at least in part. The funny thing about DSC is that when you a create configuration, it’s not REALLY a PowerShell script. It’s just a declarative document that describes what you want to configure. But I’m getting ahead of myself a bit. Let’s look, architecturally at how all the pieces of DSC fit together. To do that, I’m going to borrow some slides that Jeffrey Snover and Kenneth Hansen presented at TechEd North America:
Push vs. Pull
The main difference between these above two slides, is that they show that DSC supports both pushing configuration to sets of nodes from a central point, as well as having those nodes pull configuration on a periodic basis, from a central point, called the Pull Server. I’ll talk about the pull server more in a bit, but I think this ability to support both push and pull deployment of configuration is extremely useful, and something that Group Policy has always lacked. Group Policy provides a pull-only model of configuration distribution, making it ill suited to environment where you absolutely need to know that configuration changes arrived at their destination at a specific time, and succeeded. Managing configuration in Server environments is a perfect example of this kind of deterministic requirement. Conversely, pull is a great mechanism for transient machines, such as machines in the cloud or mobile clients coming on and off the network. Pull distribution is also ultimately more scalable since pulls can be staggered across many thousands of machines, just as Group Policy works today. The Pull Server itself is simply a web-based endpoint that you can deploy as a feature on any Windows Server running the Windows Management Framework 4.0. It appears as a feature when you configure a server using, for example, Server Manager, under the Windows PowerShell section, as shown below:
The Pull Server is called the DSC “Service”, although it’s really just an IIS endpoint. Unfortunately, when I enabled this service on my 2012-R2 Preview server, the install didn’t quite seem to take, so more on this in later builds.
In addition to these dual modes of deployment, there are several other points of note in this architecture:
- DSC uses Windows Remote Management (aka WinRM or WS-Management) as the transport for delivering configuration to targets, so you’ll need this enabled and accessible on all targets you wish to configure via DSC. The good news here is that WS-Man is pretty WAN and firewall friendly. I was actually able to push a configuration from my domain-joined machine here on my office network, to a Windows Server instance running in Workgroup mode in Azure IaaS. Well almost. I ran into some firewall issues related to Azure that prevented this from working, but the bottom line is that it is relatively easy to configure systems across the Internet, using DSC, which is pretty powerful compared to Group Policy.
- The providers (labeled as “Imperative Providers” in the boxes on the right) are the bits that actually make things happen on the target systems. And guess what, they are just PowerShell modules (script or binary). They are thus eminently extensible. You can write providers to do just about anything. This contrasts HEAVILY with trying to extend Group Policy, which requires writing arcane C++ Client Side Extensions and accompanying MMC snap-ins to expose the options. Bleh. In the current Preview release, the following providers are, well, provided. They are also referred to as “Resources” when it comes time to reference them in your configuration documents:
- Archive Resource: Designed to unzip zip files to a location you specify. Note that the zip file itself can exist on a share somewhere–it doesn’t have to be on the target node
- Environment Resource: As the name implies, it’s designed to let you specify environment variables on the target node. In the current incarnation, there doesn’t appear to be a way to specify per-system vs. per-user environment variables, like you can in GP Preferences, but that should be easy to remedy.
- File Resource: As the name implies, this one lets you copy files from a source to the target node. You can also do the copy recursively, so you can copy a whole tree structure over if needed.
- Group Resource: This one lets you manage local group membership on target systems. Think of this as analogous to Group Policy Restricted Groups or GP Preferences Local Groups (albeit without the same flexibility currently). Frankly, I tested using this resource and it didn’t work as expected, so I can’t vouch for it’s capabilities currently, but it supports the ability to specify a members list on the local Group, as well as “MembersToInclude” and “MembersToExclude”, similar to Restricted Groups. The other implicit behavior here (and with many of the Resource providers) is that if you declare this resource as “Present” and the local group doesn’t exist, it will be created for you. Likewise if you declare the resource as “Absent”, it will be removed from the target system.
- User Resource: This resource, similar to the group resource, lets you create and modify local user accounts on target nodes. Similar to GP Preferences Local Users, you can do everything from adding local users (along with their passwords), to removing users. In addition, in a nice improvement over GP Preferences, the password for the user is not stored in any easily accessible way in the configuration document, but rather can be encrypted for secure transport to the target node.
- Package Resource: This one is pretty interesting. Essentially this is the resource you use to run software installation packages on the target node. Unlike Group Policy Software Installation, it supports all manner of package formats–if you can execute it–it will run–MSI, Setup.exe, etc. It expects you to provide a “ProductID” to uniquely identify the package. Whether this is just relevant to MSI packages or not, I don’t know yet, but I believe it keys off of that to ensure that the application is still installed, each time the target node checks (or is configured). In addition, it provides the means to run the install package from a network source, so it doesn’t have to be local to the target node.
- Process Resource: This one lets you execute arbitrary applications on demand (or presumably kill them if the “Absent” keyword is used).
- Registry Resource: This resource is your equivalent to GP Admin Templates or GP Preferences Registry policy, but only to the extent that it lets you set (or remove) arbitrary registry keys and values. This is a good thing, but one thing to note about this feature as compared to say, Admin Templates, is that there is no mechanism currently built in to ensure non-tattooing of registry setting delivered this way. This makes sense, given the differences in how DSC works vs. Group Policy. DSC is “state-based”. It describes the known good state of a system’s configuration and it’s DSC’s job to “Make it so”, as Microsoft presented it. Contrast this with Group Policy, which is really a policy-based system where, an amalgam of policy objects (GPOs) conspire to achieve some set of configuration options and, when those policies go away, those configuration items may as well. So the registry resource is far simpler than it’s GP equivalents, but also far more dependable in terms of outcomes.
- Role Resource: This lets you install any Windows Role/Feature that’s available on a given system. We’ve been wanting this in Group Policy for a longggg time, but here it is in DSC. You can specify a feature name as well as tell the configuration document that you want to include all sub-features for that feature.
- Script Resource: This one lets you execute arbitrary PowerShell script code on the target node. I view it as a nice catch-all resource to be able to do things that aren’t covered in the other providers. You must provide a Get-, Set- and Test- script when you specify this resource, just like a provider (more on this later).
- Service Resource: Let’s you manage the configuration of Windows services. Note that you cannot install services using this feature, but just like GP Preferences Services policy, you can change the startup account, startup type or even stop or start the service using this resource.
- Log Resource: The log resource is kinda neat. It lets you include logging as part of your configuration. So if you want to send log messages to the Windows event log on the target node each time you perform a configuration action, this is the resource to use. It ends up storing the events in the DSC Analytics log–more on this later.
In addition to all of these built-in resources, you have some flexibility around resource constraints. In other words, within your configuration documents, you can specify that a particular resource depends upon another resource being completed first. This ensures that if you have, for example, a process resource kicking off an .exe that was copied to the system using a file resource, that the file resource copy has completed before the process resource is executed. Very cool. This is implemented in each Resource type using the Requires keyword.
Another constraint is the targeting of configurations. In this regard DSC is A LOT different than Group Policy, in both good and bad ways. In GP, you can simply “link” a GPO to an OU and all machines and users get that policy. DSC doesn’t work quite that way. The targeting of which systems get which configurations is embedded in the configuration documents themselves. I’ll go into this when I show an example of deploying a configuration but it allows for both more flexibility in targeting than GP, but also more complexity.
Walking Through a Real-Life Configuration
So, now that I’ve yammered on about how this is supposed to work, let’s look at how it does work today (again keeping in mind that these are Preview bits I’m using). Let’s look at a sample configuration document that does two things–it copies a file to the target node and sets a registry value to turn off the Shutdown Event Tracker feature in Windows:
Ensure = "Present"
SourcePath = \\2012R2-001\DSC\testfile.ps1
DestinationPath = "C:\test\testfile.ps1"
# disables shutdown event tracker
Ensure = "Present"
Key = "HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows NT\Reliability"
ValueName = "ShutdownReasonOn"
ValueType = "Dword"
ValueData = "0"
So let’s look at what this configuration document is doing. The first thing you notice is a set of declarative headers– “Configuration“, “Node” and then a File and Registry resource reference. The “Configuration” keyword is common to all DSC configuration documents and a starting point for building the files (btw, I just used PowerShell ISE to create this .ps1 file. In the version in Server 2012-R2 Preview, there is some IntelliSense assistance you get when writing these documents). You’ll note that the configuration node has a name attached to it– LockdownTest. That’s important because that is the name of the configuration I’m going to apply to my target nodes. The next set of braces you see is the Node section. That, my friends is, in the very simplest case, the targeting capability of DSC. This document targets a server called 2012R2-002. I’ll talk about why that’s important to have that in there in a minute, but first, let’s finish going through the doc. Enclosed within the Node braces are the things I want to configure on that server I just specified. In this case I have two Resources specified–a File resource for copying a file to the target server, and a registry resource for adding a registry value that disables the Shutdown Event tracker (I got that registry location for a Group Policy ADMX file). Each resource specifies a name — ScriptCopy and ShutdownEventTracker — in my example above. And within each resource, I specify fields that are pre-defined in that resource provider (and documented here).
After I get to the end of my Configuration node, I call the configuration called LockdownTest. This makes the Configuration keyword look an awful lot like a PowerShell function, and indeed there are definite similarities.
Revenge of the MOF!
So, what’s next? I’ve saved this document as a PowerShell script called LockdownTest.ps1. The next step is an interesting one. By executing this script, PowerShell notices the Configuration keyword, and proceeds to create a MOF file out of this document, as shown in the screen shot below:
For those of you who have worked with WMI, you’re familiar with the MOF, which is a way of defining WMI classes and their properties. Essentially DSC is heavily dependent upon WMI to “Make it So” on the target node. Microsoft has added a namespace in WMI on all DSC-enabled nodes called ROOT\Microsoft\Windows\DesiredStateConfiguration. This new namespace implements a set of methods that perform the setting and getting (and fixing) operations on a target node. This set of stuff in WMI related to DSC is called the Local Configuration Manager (LCM) and you’ll hear it referred to a lot in DSC documentation.
So, I go ahead and create this MOF file by executing my configuration script. and then what happens? Well, as you’ll note in my screenshot above, a directory was created under my current directory where I ran the script, called LockdownTest. Inside that folder is a file called 2012R2-002.mof. So named because that is the target machine for this MOF file. For a given configuration, there is a 1-1 relationship between MOF files and target machines. The obvious question is, what if I have multiple configuration files targeting the same node? Well, each of those files will have a different (or should anyway) configuration name (e.g. LockdownTest2, LockdownTest3 and so on) and each will thus get a different MOF file generated for them, that can be run one after the other. And just as with Group Policy, if you have conflicts across those different MOF files, most likely “last-writer-wins” is the reigning principle. I’ll spare you the contents of the MOF file for now–you can open it in Notepad and view it just fine if you want.
Ok, so now I have my configuration document–it’s been turned into a MOF–what the heck do I do now? Make It So!
Keep in mind that all my examples so far are using Push mode (Pull mode, as I mentioned, didn’t work in my test system so I’ll refrain from explaining that here). So “making it so” involves calling a PowerShell cmdlet called Start-DSCConfiguration with a bunch of parameters. As an example, to send my LockdownTest MOF over WinRM to my target server–2012R2-002– I would type the following from the directory where I created the Powershell configuration file:
Start-DscConfiguration -ComputerName 2012R2-002 -path LockdownTest -wait -verbose
This command tells DSC to send the MOF that includes LockdownTest to my target server. The Verbose parameter is a nice one to use if you want to see all of what the Start-DSCConfiguration cmdlet is doing on the remote system, as shown here:
That’s all there is to it. If I were to go to my target node (and I did), I would find the file that I copied, and the registry value that I set, both in place. What if I want to verify that my server has the LockdownTest on it, or, in the case where someone may have mucked with that configuration–how do I ensure it gets re-applied?
There are three other cmdlets in the DSC module that provide some interesting capabilities (or at least seem to). They are called Get-DSCConfiguration, Test-DSCConfiguration and Restore-DSCConfiguration. The first cmdlet retrieves information about whether the configuration is currently in effect on the target node, the second Test- command appears to return a true or false indicating whether the configuration on the target matches the desired state as determined by the configuration documents applied to it, and the last one “fixes” the target node in the event that someone has gone around and mucked with some of the configuration settings that were delivered. These two cmdlets take, as a parameter, a CIMSession object that creates the connection to the target node you wish to interrogate. To create such an object simply use the following syntax:
$sess = New-CimSession -ComputerName 2012R2-002
Get-DscConfiguration -CimSession $sess
Restore-DscConfiguration -CimSession $sess
Test-DscConfiguration -CimSession $sess
Again, all of these cmdlets are operating in Push mode (i.e. being interactively executed against the target nodes by a user). The target node has a set of behaviors within DSC that you can configure, that control it’s behavior both when it’s operating in Pull mode as well as when it’s being operated against in Push mode. Let’s look at this now.
Configuring DSC on Target Nodes
The LCM itself, which is the “Make-it-so Engine” on the target nodes, can be configured using, what else, a Configuration document. There is a special Resource I didn’t mention above, called DesiredSatateConfigurationSettings that allows you to push changes to the default LCM behavior on target nodes. There a variety of properties on the LCM, described here, that can be modified to change LCM behavior. For example, you can set how often the LCM wakes up and tries to “fix” any configuration drift that may have been occurred on a box. This useful self-healing mechanism ensure that DSC settings are always correctly applied on a target node (or within the span of the ConfigurationRefreshFrequencyInSeconds interval). In addition, if the target node is pulling it’s configuration from a Pull Server, this is where you can configure the frequency at which it does that. Finally, a most interesting parameter here is called the ConfigurationMode. It tells us that the LCM can run in one of 3 modes–ApplyOnly, ApplyandMonitor and ApplyAndAutoCorrect. The first of these tells LCM to apply a given configuration and then don’t try to periodically fix it, to prevent drift. The second one does basically the same thing as the first but it does “monitor” drift, recording it to a log (though I’m not sure where yet). Finally the ApplyAndAutoCorrect option, which seems to be the default, applies a configuration and, on the interval I mentioned above, will fix any errant configurations to prevent any drift. You can also configure Pull mode on your target nodes using these configuration options.
So, up until now, I’ve provided only the most rudimentary examples of targeting systems within configuration files. Obviously DSC needs to be able to support more flexible targeting options than just hard coding in server names. To accommodate that, there is the concept of Environment Configuration Files. These are–what else PowerShell files (.ps1 or .psd1) that are essentially fed to a modified configuration document such as the one I posted above. Within the Environment Configuration file, you create a hashtable of hashtables that allow you to build a list of target machines with a number of different properties. I experimented with this a bit, using the ActiveDirectory module’s Get-ADComputer cmdlet to retrieve a list of computers in an OU, as shown here:
Get-ADComputer -SearchBase "OU=Servers,DC=R2test,DC=tld" -Filter *
$computers = GetComputers
$AllNodes = @(
foreach ($node in $computers)
NodeName = $node.Name;
In this example, I have a function that simply calls the Get-ADComputer cmdlet on a pre-designated OU, and returns a list of node names to the hashtable called $AllNodes. Each element in $AllNodes needs to have a key called “NodeName”. It could have other keys as well, but that is the minimum. Now, I modify my configuration script slightly to tell it to use this new Environment Configuration File, as shown here:
Ensure = "Present" # You can also set Ensure to "Absent"
SourcePath = "\\2012R2-001\DSC\testfile.ps1"
DestinationPath = "C:\test\testfile.ps1"
# disables shutdown event tracker
Ensure = "Present"
Key = "HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows NT\Reliability"
ValueName = "ShutdownReasonOn"
ValueType = "Dword"
ValueData = "0"
LockdownTest -ConfigurationData C:\dsc\ConfigEnv.ps1
You’ll notice here that what has changed is that the Node segment of the document no longer refers to a single server name, but instead references my $AllNodes hashtable (note that you can also use Where clauses on AllNode to test for key/value pairs of a certain value). At the end of the document, I call LockdownTest with the implicit -ConfigurationData parameter and point it at my Enviornment Configuration script and the rest is history. When I run this configuration script, a MOF file is created for every server found in my OU!
Obviously there is a lot of flexibility in targeting here. Over time, I suspect more and clever ways of targeting systems will arise. For example, you can imagine creating properties on the server hashtable entries in the Environment Configuration file that include the server’s OS version. You could then test for that in the Configuration document using the Where clause support (e.g. Node $AllNodes.Where(“OSVersion-eq Server2008-R2″).NodeName).
Providers — How they Work
I won’t spend a lot of time talking about Providers in this blog posting, because it could be a whole blog unto itself. As I mentioned earlier, providers (aka Resources) are fully extensible. This article describes at a high level how they are created. Providers are implemented as a set of three cmdlets (Get-, Set- and Test-) for the given resource. They could be script cmdlets, binary cmdlets, etc. If you want to see the details of the ones that Microsoft provides in the box, simply navigate to C:\Windows\System32\WindowsPowerShell\v1.0\Modules\PSDesiredStateConfiguration\PSProviders on your Preview system and you’ll see a directory for each built-in Resource. The good thing to know is that if you are using Push mode, and you’ve specified custom Resources in your configuration document, they will automatically be pushed down to the target node(s) upon configuration. Likewise if you are using a Pull Server, any providers not found on the target node will be downloaded from the Pull Server as needed. Cool.
I haven’t spent a lot of time looking into logging, except to read that there is plans for both a Operational and Analytics log for DSC related activities in the Event Viewer under Applications and Services Logs. While I do see a DSC log in Event Viewer in the preview version, I did not see any entries that indicate that it’s logging detailed set-operations for a given configuration. So more on logging later, but it does appear to be available at some level. I also think there will be some logging happening at the Pull Server.
Optimized for Server– Good for Desktop?
All of the samples around DSC show typical scenarios for configuring web servers. However, there is nothing that precludes DSC from being used on client OS’ like Windows 8 or Windows 7. The one caveat to that is, there really is no concept of per-user configuration in DSC at the moment. All configuration resources and delivery mechanisms are sort of skewed towards per-computer configuration activities. That’s not to say that one couldn’t jury rig this at logon time for a given user, but simply put, that doesn’t currently seem to be the primary use case. That said, there are enough generally favorable features in DSC to warrant its investigation for per-computer desktop configuration tasks as much as server ones. I suspect that this will only expand over time.
This is by far my longest blog posting to-date. As I mentioned at least twice, all of this is based on my experimenting with essentially beta code, so I am sure some things will change over time and I may have gotten some details wrong in my testing. That said, when I first heard about DSC I was very excited that, finally, Windows was getting a modern configuration management system. After getting some time under the covers with it, there isn’t much that changes for me in that opinion. I think it represents a big step up from Group Policy and a potentially powerful technology for other Microsoft products that do configuration gets and sets, such as System Center Configuration Manager and Windows InTune. That said, it is still very rough around the edges and it does require a modicum of PowerShell skill to take full advantage of–something that is not yet totally ubiquitous. Also, as I mentioned, Group Policy has some obvious benefits around things like per-user configuration, non-tattooing of registry values and other features, and provides those in a nice relatively easy-to-use GUI that guarantees Windows admins won’t be throwing out Group Policy right away. But, the potential is there and I fully expect that as soon as this stuff goes live, we will see a gradual, inexorable move away from GP for some configuration tasks (especially on Servers) towards DSC. And that’s a good thing!