I have been looking into Chef off an on for the past few months as a possible solution for automating our server builds and configurations. We are currently using Desired State Configuration, but we are finding the tooling around DSC to be lacking. Creating configurations, managing configurations, monitoring successes and failures and auditing all have their own limitations. Chef is an automation tool that allows you to set up policies for configuring servers using “recipes” written in the Ruby language. So, I have heard people ask “What should we use? Chef or DSC?” The great thing is, the answer is both. Chef and DSC are pretty similar in what they do but Chef adds some of the features that we find lacking in the current state of DSC; Interface for managing configurations, reporting of failures and auditing when configurations need to be enforced because a change was made.

Coming from a Windows Server world, Chef seemed a little foreign to me. The principles are pretty much the same. You have configurations that you create using a scripting language. In that scripting language, you use “resources” that take care of validating an existing configuration and making changes if needed without you needing to know how those changes are made. I had a steeper learning curve when it came to learning how to use Chef’s resources than anything else. I have never used Ruby before but it’s similar to any other language once you have the basic syntax down.

The first question that we had when we started exploring Chef was, Do we need to re-write our existing configurations in Ruby for Chef? The quick answer is, no. Chef has a couple different solutions for deploying DSC configurations depending on what version of PowerShell you are running on your server. I’m going to go over some of the information and issues I ran into while exploring these options while trying to port our DSC configurations over to Chef.

As I mentioned before, just like DSC, Chef has the concept of “resources” that are used to configure settings on a server. Chef has 2 resources that can be used to configure a server with DSC. They are dsc_script and dsc_resource. dsc_resource is only available to use if you have at least the PowerShell 5 Preview installed on the server. Anything before PowerShell 5 needs to use DSC_Script.

dsc_script

For PowerShell 4 and earlier, the dsc_script resource can be used in a Chef Recipe to embed a block of DSC configuration code to enforce a configuration. If you wanted to ensure that the Web-Server role was installed you would put the following into your recipe:

dsc_script 'IISConfig' do code <<-SETUPIIS WindowsFeature WebServer { Name = "Web-Server" Ensure = "Present" } SETUPIIS end

You just need to make sure that all your DSC code is contained within the dsc_script code block. In this case, I called that code block SETUPIIS.

If your DSC configuration requires any particular modules or custom resources, DSC will pull all those files down from the pull server and put them in the Modules directory. If you plan to use Chef, you will need to ensure that those files get copied to the server before the dsc_script resource runs. There are a few different ways you can do this. I chose to put the custom resources that were needed into my cookbook so that I could copy those files out to the server and put them in the correct folder. Carbon is a PowerShell module that can performs many functions for configuring computer settings and we have used it in the past. In order to enable our configuration to use these funtions, I copied the entire Carbon module folder into my cookbook so that there is a directory called Carbon in the Files/default directory. Then it’s easy to use the Remote_Directory Chef resource to copy those files to the server before starting the DSC configuration.

remote_directory "C:\\Program Files\\WindowsPowerShell\\Modules\\Carbon" do source "Carbon" end

Since I copied the Carbon module folder to my Files directory within my cookbook, for the source, I only need to specify “Carbon” as the source. Chef will then copy all the files under that directory to the path I specify. Note that because of the way Ruby handles backslashes ( \ ), you will need to escape each of the backslashes in your path with an additional backslash. Otherwise the step will fail.

Next, you will need to import the module or resource that you need to reference in your DSC configuration. In DSC, you would do this with the Import-DscResource cmdlet, specifying the module you want to import. In Chef, this process is very similar. The dsc_script resource has a property called imports which behaves like Import-DscResource. After adding the imports property to my recipe, it should look something like this:

DSC_Script 'IISConfig' do code <<-SETUPIIS WindowsFeature WebServer { Name = "Web-Server" Ensure = "Present" } SETUPIIS end

Once you have your recipe configured, you can upload your cookbook to the Chef Server. This is where things are a little different than using straight DSC. With DSC you would need to compile your configuration to create a MOF file which you would Push out to your node or put onto your Pull Server. With dsc_script, the MOF file gets generated on the server at runtime during a Chef run and applied to the Local Configuration Manager (LCM). You can have multiple dsc_script blocks in your recipes but they are all compiled separately during the run. This means that the last dsc_script block that gets applied to the node is the last one that the LCM keeps as it’s current configuration. This shouldn’t be a problem because during a Chef run, each dsc_script block get’s re-evaluated every time. This however can impact performance if you have too many dsc_script blocks in your policy.

Composite Resources

In our DSC implementation, we use Composite Resources so that we can modularize our configurations for easy re-use. I talked about Composite Resources in a previous post. I wasn’t sure if Chef would allow me to handle these Composite Resources so I started playing around with me recipe a little. I was happy to find out that Chef would indeed allow me to reference my composites inside the dsc_script block the same way I would inside any other DSC configuration. You just need to treat them the same way you would treat a PowerShell module that is needed by your configuration. First, you need to make sure that the composite resource folders are copied to the server’s PowerShell Modules folder. To do this, I just copied all my composites to my cookbook’s files/default folder just like I did with the Carbon module earlier. Then I need to use the remote_directory resource from Chef to copy those folders down from the Chef server and into the server’s modules folder. Once you have the files copied over to the server, you can import your composite resource into your dsc_script resource just like you would with a module and call that resource from within your code block.

remote_directory "C:\\Program Files\\WindowsPowerShell\\Modules\\Carbon" do source "Carbon" end remote_directory "C:\\Program Files\\WindowsPowerShell\\Modules\\WebServerConfig" do source "WebServerConfig" end dsc_script 'IISConfig' do imports "WebServerConfig" code <<-SETUPIIS WebServer_WindowsFeatures WindowsFeatures { } SETUPIIS end

In the recipe above, I am copying my module and composite configuration folders out the server, importing the composite resource WebServerConfig, then I call the Webserver_WindowsFeatures configuration from my composite resource.

LCM Configuration

Both Chef and DSC will try to apply their latest configurations to the server so in order to avoid any conflicts, you will need to update your LCM ConfigurationMode to ApplyOnly. This way, DSC will not try to auto-correct any changed settings. We’ll leave that up to Chef to handle. To do this, we will use the powershell_script Chef resource to run some code that will update the LCM on the local server.

powershell_script "Configure LCM for dsc_resource" do code <<-EOH [DscLocalConfigurationManager()] Configuration ConfigLCM { Node "localhost" { Settings { ConfigurationMode = "ApplyOnly" RebootNodeIfNeeded = $false } } } ConfigLCM -OutputPath "#{Chef::Config[:file_cache_path]}\\DSC_LCM" Set-DscLocalConfigurationManager -Path "#{Chef::Config[:file_cache_path]}\\DSC_LCM" EOH only_if '(Get-DscLocalConfigurationManager).ConfigurationMode -notlike "ApplyOnly"' end

This code should either be added to your recipe before your dsc_script configurations or to a different recipe which gets called before the recipe containing your dsc_script code. What it is doing is updating the ConfigurationMode property of the LCM to ApplyOnly so that the LCM will not continually evaluate the latest configuration and try to fix any changes it finds. You will also want to set RebootNodeIfNeeded to False. This will ensure that reboots don’t occur in the middle of a Chef run, causing it to fail. The only_if statement towards the end will run a check to see if the ConfigurationMode is set to anything other than ApplyOnly and will not run the script if it has already been set.

The dsc_script resource has a many more properties available to it so I encourage you to become familiar with dsc_script documentation on Chef’s website. I haven’t tried it myself, but there are properties that allow you to pass in configuration data or point to a PowerShell configuration file instead of embedding your DSC code in the recipe.

The idea behind using the dsc_script resource for us, is to use it as a jumping point to Chef. This way we don’t have to re-do all of the work we have already put into our DSC configurations and we get to slowly move our environments over to Chef without throwing too many changes into the process. The servers we have will still be using the same configurations as they were before, they will just have them delivered by Chef instead of a Pull Server. Once everything has been brought over and tested, we can then concentrate on writing our configurations with the next resource I will be talking about, dsc_resource.

dsc_resource

As I mentioned before, dsc_resource is only available if you are running the latest version of PowerShell 5 or later. The main difference you will notice with dsc_resource, is instead of embedding DSC code inside a code block which Chef just runs, you are using a native Chef resource to check and enforce each of your DSC settings. This looks a lot cleaner and will run a lot faster. Each dsc_script block needs to be compiled individually and run against the server. That can add up with the more dsc_script blocks you have in your recipe. dsc_resource gets around that by calling the Local Configuration Manager (LCM) directly using the new features available in PowerShell 5.

The next advantage to using dsc_resouce, is that the code looks very similar to DSC code. For example, the code for ensuring the Web-Server and Application-Server Windows features would look like this:

WindowsFeature WebServer { Name = "Web-Server" Ensure = "Present" } WindowsFeature ApplicationServer { Name = "Application-Server" Ensure = "Present" }

That same code in Chef would look like this:

dsc_resource 'Install IIS' do resource :windowsfeature property :ensure, 'Present' property :name, 'Web-Server' end dsc_resource 'Install ApplicationServer' do resource :windowsfeature property :ensure, 'Present' property :name, 'Application-Server' end

See the similarities? Inside the dsc_resource, you specify which DSC resource you are using (It can be a default resource or a custom resource), then specify each of the values for the properties you need (ensure, name, etc…). Then you’re done.

Just like before, you will also need to change a few settings in the LCM. The dsc_resource resource won’t work unless you have the RefreshMode set to Disabled. This is a new option in PowerShell 5. In addition, you will also want to set the ConfigurationMode and RebootIfNeeded properties.

powershell_script "Configure LCM for dsc_resource" do code <<-EOH [DscLocalConfigurationManager()] Configuration ConfigLCM { Node "localhost" { Settings { ConfigurationMode = "ApplyOnly" RebootNodeIfNeeded = $false RefreshMode = 'Disabled' } } } ConfigLCM -OutputPath "#{Chef::Config[:file_cache_path]}\\DSC_LCM" Set-DscLocalConfigurationManager -Path "#{Chef::Config[:file_cache_path]}\\DSC_LCM" EOH only_if '(Get-DscLocalConfigurationManager).RefreshMode -notlike "Disabled"' end

What I like about this approach is that Chef will run everything in the order it is specified in the recipe or run list. We run into issues with our composite resources because you cannot put a dependency on a resource contained in another composite resource. For example, if you install IIS in one composite resource and set your AppPoolDefaults in another composite resource. There isn’t a way to make your AppPoolDefaults depend on IIS being installed. With Chef, we can explicitly specify the order of our configurations even if we use multiple recipes.

Again, I recommend that you make yourself familiar with the dsc_resource documentation on the Chef website and check out the examples.

I would also recommend that you watch a presentation that Steve Murawski did at the 2015 PowerShell Summit called Chef and DSC .

I hope this helps clarify some things if you are someone else who is exploring Chef from the Windows world. Using Chef can seem a little daunting at first but they seem to really be embracing the Windows world and are working hard to get us all up to speed with configuration management. Good luck and let me know if you have any feedback or questions on this post.

Matt