Chapter 3. DSC Configuration Files

 

"Success is not final, failure is not fatal: it is the courage to continue that counts."

 
 --Winston S. Churchill

Throughout the first two chapters, we covered the foundations of configuration management and how DSC works using them. We delved into the innards of DSC, covering the inner architecture and how the various pieces work together to maintain the state of your environment. By this point, you should have a solid understanding of the what and the why; now, let us start understanding the how. We will address the core interface or API you will use with DSC, the DSC configuration script.

In this chapter, we will cover the following topics:

  • Defining a DSC configuration script file
  • Defining a DSC configuration data file
  • Creating reusable DSC configurations
  • Debugging and troubleshooting configuration script files

Defining a DSC configuration script file

Now that we're past the abstract concepts and the explanatory architecture, we can start to address how to use DSC in real-world situations. Thus far in the book, we have been referring to the DSC configuration script files without really getting into the details of what that is. There isn't a specific file or file extension called a DSC configuration script file, and this term isn't really an official term for it. It has, however, become the de facto term people use to describe the file that we will create by the end of this chapter, and it's the term we will use in this book.

You should be familiar with the general format of a DSC configuration block as we have covered a few examples so far. Here is a simple one we will use as we move through the chapter. We will expand it to perform more advanced configurations and update it to allow multiple target nodes and use external configuration data:

Configuration InstallExampleSoftware  
{
    Node "localhost"
    {
        WindowsFeature DotNet
        {
            Ensure = 'Present'
            Name   = 'NET-Framework-45-Core'
        }
    }
}

While the preceding example is a DSC Configuration block, it is not a DSC configuration script file. A DSC Configuration block is the set of instructions used to compile the information to an MOF File. This file is a PowerShell script file (a file with the .ps1 or .psm1 extension) that is required to have at least one Configuration block defined inside it. Inside the Configuration block are the DSC Resource import declarations and zero or more Node declarations. Inside the Node declaration is your DSC code, where you define what state is expected on each node. So, a Configuration block has the actual code, and the configuration script file is your mechanism for making MOF files.

The DSC configuration script files are allowed to have PowerShell code defined before or after the DSC Configuration block, meaning that a DSC configuration script could be inside a larger script or in a standalone script. We will see examples later on of using DSC configuration script files with and without additional code, and the pros and cons of both approaches. To keep things simple, for the rest of the book we will use the term DSC configuration script file to mean both a Configuration block and a configuration script file.

Authoring DSC configuration files

In previous chapters, we stated that you can author configuration files in any text editor you want and how important that is for both cross-platform use and tooling choice freedom. In practice, your choice of text editor largely depends on your preferences and what interactive experience you want as you type. The best thing about authoring files for DSC is that you can pick and choose whichever text editor you want to use at any point in time in your workflow. You don't have to stick with one or the other as you progress in your use of DSC.

When you are first starting out with DSC, or really any new technology, you will benefit from tools that provide you with immediate feedback on whether you are correct or not in the code you are writing. So far, the PowerShell ISE provides you with the best learning experience, as its best features provide that immediate feedback. It has syntax highlighting for DSC language features and Cmdlets, as well as for other PowerShell language features. The ISE intelliSense autocompletes all DSC Cmdlets and language features, and additionally provides inline help tooltips for DSC Resource parameter sets. These features are the key to your first forays into authoring configuration files, as they provide immediate usefulness without having to look back at the documentation outside the editor. The ISE parser will automatically evaluate your script in real time for syntax errors and missing parameters. This is invaluable as a first line of defense against bugs and common errors when first starting out, and we will explore more of this in the last section of this chapter, as debugging DSC on target nodes is an involved process and not always easy.

Since we are writing text files, the list of third-party editors is huge and potentially includes anything out there. We are going to mention one in particular here as its features merit its mention all by itself. Sublime Text has advanced textual editing that includes multiple selections, configurable keyboard shortcuts, powerful file and function search and replacement, and a command pallet that allows execution of any feature of Sublime Text without the menu navigation. For a better the explanation and more examples, go to its website and try it out.

What do I use? Most of the time the author uses Sublime Text and falls back on PowerShell ISE if things go wrong.

DSC automatic variables

When crafting your DSC configuration script and DSC configuration data files, there are a few variables you will use that are predefined and special. These variables are reserved in the sense that DSC will populate the variables with values you provide from your configuration data files at runtime. They provide special functions or features that you can use inside your configuration scripts.

Seeing what these variables look like will be helpful while reading these descriptions, so we will show for each variable an example to refer to as we move through them individually. We will see this example again in the Defining a DSC configuration data file section.

AllNodes

The first variable to cover is the $AllNodes variable. $AllNodes is an array of hashtables that contains the information for all target nodes. The hashtable can contain any set of information you need, as long as each hashtable has a key called NodeName:

@{
  AllNodes = @(
    @{
      NodeName = "server1"
    },
    @{
      NodeName = "server2"
    }
  );
}

DSC requires NodeName because each hashtable represents a target node. This allows us to specify information for a specific node on a granular level. Imagine a set of servers that has to have a file placed in a different path depending on some aspect of the server, so each server has a different path. In each hashtable in the $AllNodes array, we can specify a property that has the file path and it will be applied to the correct server. More on this in a moment.

The $AllNodes array can be filtered to select only the desired target nodes thanks to the .Where() and .ForEach() methods added in PowerShell v4. You can get as fancy as you want using normal PowerShell filtering syntax to select which nodes are configured at both a global and a granular level. For example, you could have an entry in each target node hashtable in the $AllNodes array that defines a "role" for each server and filter on that. Only servers with the web role will have IIS installed on them.

If each hashtable entry in the $AllNodes array is specific to a target node, what happens if you have a piece of information that can vary between servers, but you want a default value applied to every server? If you specify a hashtable with a value of * for the NodeName key, then all the values in this hashtable act as defaults for all target nodes. This allows us to specify a default value, but override it for a particular target node at will. For example, say you need to ensure a specific version of a piece of a piece of software is installed on all the target nodes, but one server needs to stay locked at a specific version for backwards compatibility.

When used properly, the $AllNodes array allows for a powerful level of control and flexibility in your deployments.

Node

The next variable to cover is the $Node variable. $Node refers to the specific target node being worked on. Think of the $Node variable as an iteration on the values of the $AllNodes array, so anything inside this variable applies to one node at a time. This doesn't mean that execution of the contents of the $Node variable happens serially across target nodes; it is more a mental model to keep in mind when looking at the configuration scripts.

The $Node variable is a single hashtable that has all the values you provided for that target node in your $AllNodes array. For example, we will use the following piece of code pulled from an example we'll use later on to show how to use ConfigurationData inside our DSC configuration scripts:

Package InstallExampleSoftware  
{
  Ensure    = "Present"
  Name      = $Node.ExampleSoftware.Name
  ProductId = $Node.ExampleSoftware.ProductId
  Path      = $Node.ExampleSoftware.Path
}

This uses the data we define in ConfigurationData that we pass to the DSC Configuration block. Using the preceding example, this is how we would define it:

@{
  AllNodes = @(
    @{
      NodeName = "server1"
      ExampleSoftware = @{
        Name       = "ExampleSoftware"
        ProductId  = "{b652663b-867c-4d93-bc14-8ecb0b61cfb0}"
        SourcePath = "c:packages	hesoftware.msi"
      }
    },
    @{
      NodeName = "server2"
    }
  );
}

This is the granular level we were just talking about the $AllNodes section. This hashtable is freeform and can contain any information you want. It can contain nested hashtables or typed information. This is the place where you can fully describe the state of each server as you want it to be. Anything is up for grabs to include, but be careful, as there are some rules and syntax you must know when trying to place information here. We will cover this in the Defining a DSC configuration data file section later in this chapter.

ConfigurationData

The $ConfigurationData variable is a hashtable that accepts any data you want. Why would we need another variable that can accept any data? The main difference between this variable and the $Node variable is that the $ConfigurationData variable is global. It does not contain target-node-specific information, but information that is applicable to the environment as a whole.

Why is this needed? Looking back at our explanation of the $AllNodes variable, we decided that if a server had IIS installed on it based on the Role property, it had defined inside each target node hashtable. This was something that was applicable per server and changed depending on which server you were examining. There are some things (settings, values, applications, and so on) that are applicable on all servers no matter what and that you want applied on all servers. An example of this is an application that must be present on all servers and have the same configuration set in its configuration file. Set the values in the ConfigurationData hashtable once, and it will be available to each target node you run this on, instead of having to specify the values in each target node hash in the $AllNodes array.

To show how this is specified, we'll steal another example from later on in this chapter, but reduced just to show the use of ConfigurationData:

# data file
@{
  AllNodes = @(
    @{
      NodeName = "server1"
    },
    @{
      NodeName = "server2"
    }
  );
  NonNodeData = @{
    ConfigFileContents = (Get-Content "Config.xml")
  }
}
# end of data file

# beginning of the example configuration
Configuration MyConfiguration
{
  Node $AllNodes.NodeName
  {
    File ConfigFile
    {
      DestinationPath = "c:foo.txt"
      Contents        = $ConfigurationData.NonNodeData.ConfigFileContents
    }
  }
}
# ending of the example configuration

Note

The preceding example will not work if you copy and paste it and try to run it, as we have not covered how to pass configuration data to a configuration block yet; this is for example only.

In our data file, we store the contents of config.xml in a variable called ConfigFileContents and use that to create a file on each target node. Since this is something that is created on all our servers, we only have to specify it once in the ConfigurationData section.

In practice, this is for data that does not change much and is generally static. This variable is not used as much, as the power of the inheritance and granularity of the $AllNodes array allows a lot of flexibility while reducing the amount of repeated information.

DSC Resource import declarations

One last area we need to cover before we get into the syntax of DSC configuration script files is DSC Resource imports. When we write our DSC Configuration blocks, DSC needs to know what external DSC Resources are used in order to validate parameters, ensure the syntax is correct, and record version information about the DSC Resources being used.

We use the Import-DscResource command to tell DSC that the specified DSC resources must be loaded in order to parse this configuration file. These DSC Resources must be present on the system in one of the paths listed in the PowerShell module path variable $env:PSModulePath, but they aren't executed when you compile the configuration script to MOF. They are inspected for the parameters needed by each DSC Resource so that the DSC configuration script can be validated and then compiled to MOF.

Import-DscResource

Import-DscResource is a dynamic keyword, not a Cmdlet, contrary to what the verb-noun format suggests. Being a dynamic function, it behaves slightly differently than you'd expect. Its parameters are not positional, so you have to specify them in order for it to work. It does not exist outside the Configuration block, so you can't call it from the PowerShell cmd line in isolation. This makes discovery using the normal PowerShell tools hard; you can't run Get-Help or Get-Member on it, so you will have to rely on PowerShell documentation. There are only two parameters for Import-DSCResource: Name and ModuleName.

The parameter Name accepts the ClassName of the DSC Resource, or its full name. This generally is the name of the DSC Resource that is listed when running Get-DSCResource. You can specify one or more values to this parameter. It is preferred to use ModuleName, because when Name is provided, DSC has to iterate over every DSC Resource in the module path and search for the resource specified. This is a costly operation that has to happen for each name specified. It will also load the first matching resource, meaning that if there are different versions, the first one wins and it will miss the other versions. Another option is to use both ModuleName and Name when you want to specify that only one DSC Resource is being used in a specific module. While this may not speed up resolution, it may be clearer to someone coming back to your DSC configuration script as to what exactly is being used.

The parameter ModuleName accepts the name of the module containing the DSC Resources to be used. This parameter also accepts one or more string values of names to search for, but it also accepts hashtables. The hashtable can specify both the name of the module and the version of the module to find. This ensures that the correct version is used for your deployment. The flexibility and power of this parameter is coupled with the fact that it is more efficient than the Name parameter at finding and parsing the DSC Resource specified in the module path.

The DSC script file syntax

Now that we have covered how to author the configuration files and special variables, and how to import DSC Resources, we can cover the syntax of using these elements inside DSC configurations.

In the Defining a DSC configuration script file section, we stated that you can have more than one Configuration block in a given DSC configuration script file. In the field, you will want to have only one DSC Configuration block per file, and little to no extra code inside the file besides the DSC Configuration block. This is a best practice for many reasons, but first and foremost, it's to support the main purpose of any CM product: to reduce points of change or variance.

If we have multiple DSC Configuration blocks inside a configuration file, we have multiple points of change that need to be accounted for. If there are multiple Configuration blocks in a file, from a readability standpoint it's hard to know at a glance what is going on in the whole file. It's difficult to determine if the multiple Configuration blocks have a relationship or if they just happen to be grouped together in the same file. Since there is no tooling to show us without compiling a MOF, it's our responsibility to know.

Note

You may decide to take on the added complexity of having multiple DSC Configuration blocks in separate scripts and conditionally pushing them to target nodes using Start-DscConfiguration. If you don't know what this approach is yet, don't worry - we will talk more about pushing DSC configurations in Chapter 5, Pushing DSC Configurations. In a constrained environment where you have complete control and know your environment well, this may work for you as it keeps things organized. It requires a great deal of control and discipline to keep everything straight. Take care to ensure that you do not need to apply two of your DSC configurations at once; if both are applied they may cancel each other out and cause unintended breakage. You can have DSC partial configurations, which can express multiple configuration blocks for a single target node, but this is only available in PowerShell v5 and is currently being finished. It is an advanced deployment technique that is beyond the scope of this book. A more appropriate feature to use is the DSC composite resource, which we will cover later in this chapter.

You're probably wondering how one file could possibly describe the entirety of a configuration state for thousands of target nodes. The short answer is the flexible and powerful filtering we can do inside the $AllNodes array and the declarative and flexible DSC configuration data files. We can categorize target nodes and decide what state they should have at runtime, and in doing so, keep the separation of the data (the parts that change) from the configuration (the steps to set the state that do not change). We will cover the filtering and, other mechanisms available to us inside the DSC configuration script file here, and we will address what we can do with configuration data in the upcoming Defining a DSC configuration data file section.

Earlier in this chapter, we showed an example DSC Configuration block and said that we would use it to go into further detail about how it is constructed. Now is the time to do so. We are going to start with the following DSC Configuration block that ensures that a specific version of the .NET Framework is installed on the target host, and by the end of the chapter we will modify it to install a MSI package as well. This shouldn't look too strange to you at this point, because we have shown several examples so far. Don't worry if it does, as we will go through each part now in detail:

Configuration InstallExampleSoftware
{  
  Node $AllNodes.Where({$_.Roles -contains 'FooBar'}).NodeName  
  {
    WindowsFeature DotNet
    {
      Ensure = 'Present'
      Name   = 'NET-Framework-45-Core'
    }

    File ConfigFile
    {
      DestinationPath = "c:foo.txt"
      Contents        = $ConfigurationData.NonNodeData.ConfigFileContents
    }

    Package   InstallExampleSoftware  
    {
      Ensure    = "Present"
      Name      = $Node.ExampleSoftware.Name
      ProductId = $Node.ExampleSoftware.ProductId
      Path      = $Node.ExampleSoftware.Path
      DependsOn = @('[WindowsFeature]DotNet')
    }
  }
}

This is the general format or anatomy of the Configuration block you will use in your production scripts. While you may or may not use the indenting or brace format of the preceding example, the important thing is that you and your team agree on a code formatting style and stick with it. It will improve the readability of your code and increase the effectiveness of code review and historical examination of changes in the code. The following conventions are used to increase readability in this book and to ensure all important parts are clear:

  • Opening and closing braces are put on the next line for each function declaration
  • The statements within the braces are indented, and the closing brace is put on the same indentation level as the header of the function on a line of its own
  • Parameter statements have their equal signs aligned to the longest parameter name
  • Normal PowerShell coding style guidelines for any inline code written inside these blocks are followed

If you are not used to it, this may seem like a lot of ceremony without function. You will want to keep a tight coding style or format to ensure the readability of the script in the months to come. You don't want to come back to the script months later and wonder what it does because of poor formatting or organization. Indenting, new lines, and spacing all seem trivial and inconsequential and best left to programmers to bicker about, but they are the key to ensuring that your script is maintainable and will adapt to future modifications. Do not underestimate the value of well-formatted script when you have to read it months later to find out what is going on, with your boss looking over your shoulder.

The Configuration keyword

A DSC Configuration block starts with declaring the Configuration keyword and a name for the configuration block. It follows PowerShell function name declaration rules, but otherwise there are no requirements for what to name it.

Choose the name of the DSC Configuration block carefully as it will not only be how you refer to what configuration is applied, but, by default, it will also be the name of the output folder into which the MOF files are placed after compilation. You can change the name and path by using the OutputPath parameter. Technically, it does not have to be unique as the additional metadata written to the MOF file on compilation helps identify what exactly is deployed to target nodes, but it helps to be as descriptive as possible.

The DSC Configuration block can be treated like a PowerShell function declaration in that it accepts parameter statements and contains code. This is important to note, as it means that you can control how the DSC Configuration block behaves both by the parameter statements defined and the defining custom parameters.

There are two built-in parameters that are always present in any Configuration block you define: the ConfigurationData and the OutputPath parameters. We just mentioned the OutputPath parameter. The ConfigurationData parameter allows you to specify the environmental data to your function, which we covered in the DSC automatic variables section. You can define custom parameters by adding a param statement to your Configuration block:

Configuration InstallExampleSoftware
{
  param(
    $computer,
    $ExampleSoftwareName,
    $ExampleSoftwareProductId,
    $ExampleSoftwarePath,
    $ConfigFileContents
  )

  Node $computer
  {
    WindowsFeature DotNet
    {
      Ensure = 'Present'
      Name   = 'NET-Framework-45-Core'
    }

    File ConfigFile
    {
      DestinationPath = "c:foo.txt"
      Contents        = $ConfigFileContents
    }

    Package   InstallExampleSoftware  
    {
      Ensure    = "Present"
      Name      = $ExampleSoftwareName
      ProductId = $ExampleSoftwareProductId
      Path      = $ExampleSoftwarePath
      DependsOn = @('[WindowsFeature]DotNet')
    }
  }
}

You would execute the preceding Configuration block like so:

InstallExampleSoftware -computer "foo1" -ExampleSoftwareName   'FooSoftware' -ExampleSoftwareProductId '4909a5d0-b3c3-4a98-be90-a0dcee7c4eef' -ExampleSoftwarePath 'e:sourcefoosoftware.msi' –ConfigFileContents (Get-Content "Config.xml")

For the most part, you will likely prefer not to use custom parameter statements and instead rely on the more powerful $AllNodes or $Node variables, as they provide superior flexibility and do not require you to provide them inline. We will cover examples that prove this statement in the Defining a DSC configuration data file section later in this chapter.

The Node keyword

The Node keyword is at first glance confusing because we already said there is a special variable called $Node earlier in the chapter. It is further confusing because in our previous example, we have both the Node keyword and the $Node variable in use. It's not so confusing, however, when you know that one is a PowerShell keyword and the other is a reserved variable.

Remember, the $Node variable is used to grab specific configuration data for the target node. As shown in the example, you use it like any other PowerShell object, accessing properties and values using normal syntax. The Node keyword selects the nodes using the filter statement we provide and builds the $Node variable for each target node. In the example, we provide this filter:

Node $AllNodes.Where({$_.Roles -contains 'FooBar'}).NodeName

There's a couple of things going on here; let's take them one by one. First is the Node keyword, which takes an array of strings that represent the names of the target nodes. We provide the array of names by using a PowerShell filter called Where() that accepts a script block. Inside that script block, we evaluate each item in the $AllNodes array and inspect whether it matches our filter. In this case, we are looking for all items that have a role called Foobar, and getting the value for the key NodeName.

This is the power we were talking about earlier when we explained how DSC puts all the configuration state information for one target node inside one file. We can put all the information we need inside the configuration data files, and inside the DSC Configuration block, we can inspect and filter on it and decide what gets applied when and where. You will notice that the Configuration block doesn't need to change when our target node list changes. It only needs to change when our example property role changes, which makes sense because a change of that importance would require a change in our deployment methodology. Even then, that's not too big a change, because it's either an additive or a subtractive change; we either add a new node block for a new role or remove a node block for a role we don't need any more. The Role property is an example here; it can be anything you need.

Before we move on, we should go back to that Node keyword statement again. This statement can be as complex or as simple as you need. You can get away with just passing the $AllNodes array as it is:

    Node $AllNodes.NodeName

Or, you can just have a specific hostname:

    Node "server1.company.com"

It's up to you how complex or simple you want it to be. A general rule is to keep it as simple as possible to increase readability and decrease the chance of bugs in your filtering logic. If you notice that your filter statement is more than a line or two, you might want to look at splitting your declaration into two node blocks. It is a good indication that there is enough difference there to warrant the break.

DSC Resource statements

Inside the Node keyword block are the actual statements that will determine the configuration state of your target nodes. This is where you'll use the DSC DSL and DSC Resources to describe the different operations, settings, software, and other aspects of your nodes. This is the last and most important step in the syntax of DSC configuration script files, as this is the part that is parsed by DSC and compiled to MOF using DSC Resources. There are a lot of moving parts here, but not much to explain in general, as this will be very implementation-specific. It would not be much use to you to regurgitate help file documentation for each DSC Resource, but we will cover some best practices here.

It is very easy to hardcode, or write down the exact text, of configuration data next to the DSC Resource parameters that are using it. You must identify these hardcoded values and extract them to configuration data variables ($AllNodes, $Node, or $ConfigurationData). We covered the separation of environmental data and structural data in Chapter 2, DSC Architecture in the Configuration data section, and here is where it comes into play. We want to take all the environmental data out of our DSC configuration script file and inside the Node blocks is where it most often crops up. Fortunately, DSC makes this easy to extract using the DSC special variables. We will go more into the format of specifying the configuration data in the Defining a DSC configuration data file section of this chapter.

A common mistake is to put PowerShell code inside the Node block that performs some kind of decision logic for the target node. You might need to parse a string, figure out a version number, or resolve a path, and so on. There are many different things you might try to accomplish inside the Node block, because it's so easy to drop down to PowerShell and get it done. You must resist this temptation, because adding logic that needs to be executed on the target node here breaks the utility and power of the DSC engine. DSC allows the idempotent configuration of a target node, no matter how many times it is run on the target node. The logic put into the DSC Configuration block or Node block is not part of the DSC Resources, and so it is not executed on the target node, but instead on the computer you compiled the MOF on. Remember, the DSC Resource is the part that does the actual work of determining and applying the state, and the code that you put in the Node block is never seen by the DSC Resources. When you go to run DSC on the target node again, your code will not be re-evaluated.

For example, you write some code to determine what version of a piece of software to install on a target node based on a file on a remote server. When you compile your DSC configuration to MOF, that value is stored as it is in the MOF file. When applied to the target node, that value is used. If run again, that value does not change, because the MOF was not re-generated.

There are some cases where code can be placed inside a Node block and not be a problem. Code or conditional logic inside this area that makes decisions based on your deployment processes can be very useful and powerful here; for example, code that determines whether a software package is installed on test servers or production servers, or code that determines which set of servers are web servers or database servers, or both. These are examples of environmental choices whose data is stored in your ConfigurationData files, but the decision to use it lies in your DSC configuration script.

If you are not supposed to put any code inside the Node or Configuration blocks, where should it be put? If you have code in these places, it is a clear sign that you should be creating a DSC Resource that handles this logic or decision making. The DSC Resource is the best place to understand how to apply this logic in an idempotent manner. In the next chapter, we will cover how to create DSC Resources, so stay tuned.

The script file example

Now that we have covered the different parts of a DSC Configuration block syntax, we can finally create a DSC configuration script. As we said earlier, we are going to modify it to install an MSI package, as well as to ensure the .NET 4.5 Framework is installed. We are going to use the Package DSC Resource to accomplish this. We are also going to change things up (like the real world often does) and make this a bit more complicated by ensuring our target nodes all have a state file by using the File DSC Resource and an environment variable that points to the location of the state file using the Environment DSC Resource. Before we start writing our DSC configuration script file, let's examine each resource and determine the information needed to use it. The Package DSC Resource accepts the Name, ProductId, and Path to the MSI as parameters, along with some others we won't need to use in our example. This information is likely to change frequently as the software is updated or the source location is changed, so it makes sense to take advantage of the DSC special variables to keep this information out of our DSC configuration script file.

Note

If you are looking at the ProductId parameter and scratching your head at what it is, you aren't be the first one. The ProductId refers to the ProductCode of the MSI you are installing. Why the PowerShell team used ProductId and not the actual term ProductCode is unknown, but there is probably a good reason somewhere. You can obtain the ProductCode by querying the MSI itself using PowerShell, opening the MSI in a MSI reader program like Orca, or by installing the MSI on a test system and inspecting the registry for the value. Included in the book's source code is some PowerShell code to do this.

The File DSC Resource requires that at least DestinationPath be specified, but since we are trying to create a file, we need to specify File for Type. The Environment DSC Resource requires Name and Value to operate, so we fill those in with the name of the variable we want and the location of the state file.

The File and Environment DSC Resources will be applied to all our servers, but we will only install the MSI package to those with the FooBar role. This means that we will split these DSC Resource statements into two Node blocks so we can filter appropriately.

The last thing to discuss is the parts that make this a script file. We are going to be compiling this DSC Configuration block into MOF files frequently, every time an environment variable changes. We won't want to do the steps we did in Chapter 2, DSC Architecture over and over again on the command line. The purpose of both PowerShell and DSC is to automate, after all! So, we will put our DSC Configuration block inside a PowerShell script file and wrap the invocations of the configuration block. PowerShell script files accept parameter statements that give you the ability to specify custom parameters, which allows us to specify things like the path in which to put the MOF files and environmental data.

So, we will begin our script with a set of parameters that represent our OutputPath and ConfigData. Then, we will specify our DSC Configuration block, and then end the script with the Configuration block invocation. We will continue to build on this script as we move along in the chapter, and the final version of it is included in the book's source code:

# beginning of script
[CmdletBinding()]
param(
  [Parameter()]$OutputPath = [IO.Path]::Combine($PSScriptRoot, 'InstallExampleSoftware'),
  [Parameter()]$ConfigData
)

Configuration InstallExampleSoftware
{
  Node $AllNodes.NodeName
  {

    WindowsFeature DotNet
    {
      Ensure = 'Present'
      Name   = 'NET-Framework-45-Core'
    }
  }

  Node $AllNodes.Where({$_.Roles -contains 'FooBar'}).NodeName
  {
    Environment AppEnvVariable
    {
      Ensure = 'Present'
      Name   = 'ConfigFileLocation'
      Value  = $Node.ExampleSoftware.ConfigFile
    }

    File ConfigFile
    {
      DestinationPath = $Node.ExampleSoftware.ConfigFile
      Contents = $ConfigurationData.NonNodeData.ConfigFileContents
    }

    Package  InstallExampleSoftware
    {
      Ensure    = 'Present'
      Name      = $Node.ExampleSoftware.Name
      ProductId = $Node.ExampleSoftware.ProductId
      Path      = $Node.ExampleSoftware.SourcePath 
      DependsOn = @('[WindowsFeature]DotNet')
    }
  }
}

InstallExampleSoftware  -OutputPath $OutputPath -ConfigurationData $ConfigData
# script end

All the elements we planned to include are present. We declared our Configuration block, set our DSC Resource imports, declared our Node filter statements, and listed the DSC Resource blocks we needed. In the Package DSC Resource, we specified the configuration data, so we kept a clear separation between environment data and our structural data.

We have a production-quality script that is maintainable and separated from environmental data, but if you tried to run it as it is, it would fail. We don't know how to pass configuration data to the file yet! We are going to address making and using configuration data now.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset