Conceptualize Desired State Configuration: Part 3


Summary: Microsoft MVP, Will Anderson, modifies a Desired State Configuration template.

Good morning. Ed Wilson, Microsoft Scripting Guy, is here. Today we have part three of Will Anderson’s awesome Desired State Configuration (DSC) series.

  Note   This is a seven-part series that includes the following posts:

The code used in this series can be downloaded from the Script Center Repository:
Conceptualize Desired State Configuration - Reference Script

Here’s Will…

We've pasted our template into our ISE instance, and now we're going to start modifying it.

Image of command

Importing DSC resources

Any DSC resource that is not part of the built-in DSC resources need to be imported into the configuration to tell the target system what tools it needs to perform the necessary tasks. Although it's not required for the built-in resources, it can be a good idea to also call them out. This not only establishes good habit patterns, but if you start downloading and using updated versions of those built-in resources, they may not necessarily have the same capabilities and attributes as the version that came with the software. Also, if you don't import the DSC resource into the configuration, you'll get an annoying warning message when you go to generate the .mof file:

Image of command output

To import a DSC resource, you need to know the module that contains it and the version. You can find this with Get-DscResource:

PS C:\Windows\system32> Get-DscResource -Name WindowsFeature

ImplementedAs Name      ModuleName      Version Properties         

------------- ----                            ----------             -------           ----------         

PowerShell  WindowsFeature   PSDesiredStateConfiguration 1.1  {Name, Credential, DependsOn, Ensure...}

If you want to know all of the resources contained in a specific module, you can use the Module parameter instead of the Name parameter:

PS C:\Windows\system32> Get-DscResource -Module PSDesiredStateConfiguration

ImplementedAs Name      ModuleName      Version Properties         

------------- ----                            ----------             -------           ----------           

Binary   File                {DestinationPath, Attributes, Checksum, Content...

PowerShell  Archive     PSDesiredStateConfiguration 1.1  {Destination, Path, Checksum, Credential...} 

PowerShell  Environment    PSDesiredStateConfiguration 1.1  {Name, DependsOn, Ensure, Path...}   

PowerShell  Group      PSDesiredStateConfiguration 1.1  {GroupName, Credential, DependsOn, Description...}

Binary   Log      PSDesiredStateConfiguration 1.1  {Message, DependsOn, PsDscRunAsCredential} 

PowerShell  Package     PSDesiredStateConfiguration 1.1  {Name, Path, ProductId, Arguments...}    

PowerShell  Registry     PSDesiredStateConfiguration 1.1  {Key, ValueName, DependsOn, Ensure...}  

PowerShell  Script     PSDesiredStateConfiguration 1.1  {GetScript, SetScript, TestScript, Credential...}

PowerShell  Service     PSDesiredStateConfiguration 1.1  {Name, BuiltInAccount, Credential, Dependencies...

PowerShell  User      PSDesiredStateConfiguration 1.1  {UserName, DependsOn, Description, Disabled...}

PowerShell  WaitForAll    PSDesiredStateConfiguration 1.1  {NodeName, ResourceName, DependsOn, PsDscRunAsC...

PowerShell  WaitForAny    PSDesiredStateConfiguration 1.1  {NodeName, ResourceName, DependsOn, PsDscRunAsC...

PowerShell  WaitForSome    PSDesiredStateConfiguration 1.1  {NodeCount, NodeName, ResourceName, DependsOn...}

PowerShell  WindowsFeature   PSDesiredStateConfiguration 1.1  {Name, Credential, DependsOn, Ensure...}  

PowerShell  WindowsOptionalFeature PSDesiredStateConfiguration 1.1  {Name, DependsOn, Ensure, LogLevel...}  

PowerShell  WindowsProcess   PSDesiredStateConfiguration 1.1  {Arguments, Path, Credential, DependsOn...}

Now that we know the module and version, we can call it in our configuration file. This call is placed before the Node block:

configuration CMDPConfig

{

 Import-DscResource -ModuleName PSDesiredStateConfiguration -ModuleVersion 1.1

 # One can evaluate expressions to get the node list

 # E.g: $AllNodes.Where("Role -eq Web").NodeName

 node ("lwincm02")

 {

You could also write it with the properties as a hash table, which works much better if you intend to use these same configurations in Azure because (as of this writing), Azure does not support the former method.

Import-DscResource -ModuleName @{ModuleName= 'PSDesiredStateConfiguration'; ModuleVersion = '1.1'}

Configuring the resource

When we call a DSC resource, we need to give it a friendly name. This should be something concise, but easily recognizable so that whoever is viewing the configuration script or configuration log files can easily identify what's being configured.

For my distribution points, I want to put my systems into a Server Core state (no GUI because we're PowerShell users and we don't need a GUI). We'll call this first configuration, RemoveUI. You can also add comments in the usual PowerShell fashion, but I save those for any time I need to make a more verbose annotation in the configuration script.

I'm also going to insert the name of the server feature we want to change on the mandatory Name parameter. If you're not sure of the name of the server feature you want to add or remove, you can always run the Get-WindowsFeature command and find it under the name field:

PS C:\Windows\system32> Get-WindowsFeature *gui*

Display Name           Name      Install State

------------                        ----      -------------

 [X] Graphical Management Tools and Infrastructure Server-Gui-Mgmt-Infra   Installed

 [X] Server Graphical Shell       Server-Gui-Shell    Installed

Now that we have the name of our feature, we'll start modifying our DSC resource:

Before we go further, it should be noted that there are common values among most of the DSC resources. They are

  • DependsOn
  • Ensure
  • PsDscRunAsCredential (introduced in DSC v2 with WMF 5.0).

DependsOn and Ensure will probably be the values that you'll use the most, but I'll quickly discuss all three of them. Let's start with Ensure:

WindowsFeature RemoveUI

  {

   Name = 'Server-Gui-Shell'

   [Credential = [PSCredential]]

   [DependsOn = [string[]]]

   [Ensure = [string]{ Absent | Present }]

   [IncludeAllSubFeature = [bool]]

   [LogPath = [string]]

   [PsDscRunAsCredential = [PSCredential]]

   [Source = [string]]

  }

Ensure has two values available to it: Present and Absent. This is essentially your on/off switch. We want to remove the UI, so we'll be setting this to Absent. This is the only thing I want to do for this feature, so we'll remove the unused parameters from when we pasted our template.

   WindowsFeature RemoveUI

  {

   Name = 'Server-Gui-Shell'

   Ensure = 'Absent'

  }

DependsOn establishes dependencies in your DSC configuration. For example, in my DP configuration script, I have a requirement to make sure that Remote Differential Compression is installed on the server because it's a required feature for the distribution point. By default, this feature is already installed on the server, but I want to make sure it's there at all times.

I also want to always make sure that this check is completed after the RemoveUI configuration. This way, I'm always maintaining a list of logical steps in my configuration. If my configuration ever goes out of compliance and I want to re-enforce it, the Local Configuration Manager will follow the logical order that I build in the configuration.

PsDscRunAsCredential, as you might have guessed, allows you to pass credentials to your configuration. For example, if you want to use a DSC resource to join a system to the domain, you can use this parameter to pass the administrative credentials that are required.

You don't have to have each step in you configuration necessarily dependent on the previous, but you definitely want to use it in situations where you might need to verify that any configurations that your change is dependent on are correctly set up.

For instance, my next configurations deal with IIS. I need to install the IIS 6 WMI Compatibility component, and for that I obviously need IIS. So it's logical to ensure that IIS exists on the system before attempting to install its dependent features.

DependsOn uses the DSC resource name inside of a pair of angle brackets, in addition to the friendly name we give the resource afterwards, as you can see in the following example. Let's paste our WindowsFeature DSC Resource template again, and configure Remote Differential Compression:

   WindowsFeature EnableRemoteDifferentialCompression

  {

   Ensure = 'Present'

   Name = 'RDC'

   DependsOn = '[WindowsFeature]RemoveUI'

  }

And now we'll continue down the line with our IIS features:

   WindowsFeature IIS

  {

   Ensure = "Present"

   Name = "Web-Server"

   DependsOn = "[WindowsFeature]EnableRemoteDifferentialCompression"

  }

  WindowsFeature IIS6WMICompatibility

  {

   Ensure = "Present"

   Name = "Web-WMI"

   DependsOn = "[WindowsFeature]IIS"

  }

At the end of the configuration block, we'll call the configuration to generate the .mof file. So your configuration should look a little something like this:

configuration CMDPConfig

{

 Import-DscResource -ModuleName @{ModuleName= 'PSDesiredStateConfiguration'; ModuleVersion = '1.1'}

 # One can evaluate expressions to get the node list

 # E.g: $AllNodes.Where("Role -eq Web").NodeName

 node ("lwincm02")

 {

  WindowsFeature RemoveUI

  {

   Name = 'Server-Gui-Shell'

   Ensure = 'Absent'

  }  

  WindowsFeature EnableRemoteDifferentialCompression

  {

   Ensure = 'Present'

   Name = 'RDC'

   DependsOn = '[WindowsFeature]RemoveUI'

  }

  WindowsFeature EnableIIS

  {

   Ensure = "Present"

   Name = "Web-Server"

   DependsOn = "[WindowsFeature]EnableRemoteDifferentialCompression"

  }

  WindowsFeature EnableIIS6WMICompatibility

  {

   Ensure = "Present"

   Name = "Web-WMI"

   DependsOn = "[WindowsFeature]EnableIIS"

  }

 }

}CMDPConfig

Now we have a very basic configuration that we can start testing. The first test is to see if we can generate a configuration .mof file. Make sure that your present working directory is set to a directory where you want to generate the .mof files. For my example, I created a Configs directory in C:\scripts, and I set my location to that path. By default, ISE puts you into the system32 directory, and we probably don't want our configurations to reside there.

So let's execute the script we created:

PS C:\Windows\system32> C:\scripts\Configs\CMDPConfig.ps1

 Directory: C:\Windows\system32\CMDPConfig

Mode    LastWriteTime   Length Name                                 

----          -------------   ------ ----                                 

-a----   1/7/2016 3:52 PM   20208 LWINCM02.mof                               

As you can see, when the script executes, it creates a directory with the name of the configuration you created. Inside, you'll find a .mof file with the host name of the system. Let's open the .mof file and examine the contents:

/*

@TargetNode='lwincm02'

@GeneratedBy=William

@GenerationDate=12/30/2015 18:55:10

@GenerationHost=LWINERD

*/

instance of MSFT_RoleResource as $MSFT_RoleResource1ref

{

ResourceID = "[WindowsFeature]RemoveUI";

 Ensure = "Absent";

 SourceInfo = "C:\\scripts\\Configs\\CMDPConfig.ps1::10::9::WindowsFeature";

 Name = "Server-Gui-Shell";

 ModuleName = "PSDesiredStateConfiguration";

ModuleVersion = "1.1";

 ConfigurationName = "CMDPConfig";

};

instance of MSFT_RoleResource as $MSFT_RoleResource2ref

{

ResourceID = "[WindowsFeature]EnableRemoteDifferentialCompression";

 Ensure = "Present";

 SourceInfo = "C:\\scripts\\Configs\\CMDPConfig.ps1::16::9::WindowsFeature";

 Name = "RDC";

 ModuleName = "PSDesiredStateConfiguration";

ModuleVersion = "1.1";

DependsOn = {

 "[WindowsFeature]RemoveUI"};

 ConfigurationName = "CMDPConfig";

};

instance of MSFT_RoleResource as $MSFT_RoleResource3ref

{

ResourceID = "[WindowsFeature]EnableIIS";

 Ensure = "Present";

 SourceInfo = "C:\\scripts\\Configs\\CMDPConfig.ps1::23::9::WindowsFeature";

 Name = "Web-Server";

 ModuleName = "PSDesiredStateConfiguration";

ModuleVersion = "1.1";

DependsOn = {

 "[WindowsFeature]EnableRemoteDifferentialCompression"};

 ConfigurationName = "CMDPConfig";

};

instance of MSFT_RoleResource as $MSFT_RoleResource4ref

{

ResourceID = "[WindowsFeature]EnableIIS6WMICompatibility";

 Ensure = "Present";

 SourceInfo = "C:\\scripts\\Configs\\CMDPConfig.ps1::30::9::WindowsFeature";

 Name = "Web-WMI";

 ModuleName = "PSDesiredStateConfiguration";

ModuleVersion = "1.1";

DependsOn = {

 "[WindowsFeature]EnableIIS"};

 ConfigurationName = "CMDPConfig";

};

instance of OMI_ConfigurationDocument

     {

 Version="2.0.0";

      MinimumCompatibleVersion = "1.0.0";

      CompatibleVersionAdditionalProperties= {"Omi_BaseResource:ConfigurationName"};

      Author="William";

      GenerationDate="12/30/2015 18:55:10";

      GenerationHost="LWINERD";

      Name="CMDPConfig";

     };

Success! Now that we've created our first .mof file, we'll be able to push the configuration to a target machine. In our next exciting episode, we'll do a test push to add the rest of our Windows features to the configuration and update the .mof file!

~Will

Thank you, Will, for another awesome post. He will be back tomorrow with Part 4 of this series.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. Also check out my Microsoft Operations Management Suite Blog. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

Comments (3)

  1. SAI says:

    Hi Will,

    If for some reason EnableRemoteDifferentialCompression is failed, will the configuration continue to execute the rest?

    Thanks

  2. William E. Anderson says:

    Hey there Sai! Thanks for the excellent question!

    If there are resources that are dependent on the failing resource, then the configuration should fail. However, if there are no dependencies on that resource, it should continue to configure the remaining resources as outlined in your configuration. The configuration
    will still return as failed because of the outstanding resource config, however.

    I say should, in part, because it depends on how the resource is designed and executes. I’ll be getting into a scenario later in this series where I show you how to design a DSC resource and give an example of how to avoid running into a situation where you
    might receive a successful configuration even if the instance isn’t correctly configured. Theoretically you can run into a scenario where you have a component configuration failure, but the returning output allows the configuration to keep on plugging. That’s
    why it’s important to test your DSC resources to make sure that they work as expected in both a true configuration, as well as a false.

    I, for the most part, like to use DependsOn in my configuration to enforce a logical order. This is because oftentimes we have a logical order of installing components and configurations for an application or service. So while one thing may not necessarily
    be related to another, the whole of the configuration is needed to complete the task. If I have a failure in my installation chain, I want it to stop at that point until the issue can be corrected. Then, after I resolve the problem, I can re-initiate the configuration
    and continue on my merry way.

  3. Cognic says:

    Thanks Will for the explanation and amazing series on DSC!!

Skip to main content