Configuring Virtual Machines Using Desired State Configuration - Part 3 - Authoring Configurations

Following my previous post on how to configure the LCM, we are now going to deep dive into configurations and how to author them.

We'll start with a very simple configuration and work our way to a more complicated one. In the first article of the series we configured a server with the Web-Server windows feature using the below configuration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Configuration WebApp001 {
    # Import the module that contains the resources we're using.
    Import-DscResource -ModuleName PsDesiredStateConfiguration

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

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

        # The second resource block ensures that the website content copied to the website root folder.
        File WebsiteContent {
            Ensure = 'Present'
            SourcePath = '\\fileserver\dsc\WebApp001\iisstart.htm'
            DestinationPath = 'c:\inetpub\wwwroot'
            Checksum = "modifiedDate"
            Force = $true
            MatchSource = $true
            DependsOn = '[WindowsFeature]WebServer'
        }
    }
}

Let's go through the main components of this configuration.

Configuration Block
In line 1, we have the "Configuration" block start. This is the block that contains our configuration and it is madatory to have. Following the configuration keyword we have the name of the configuration. You should make sure this is descriptive of the configuration to be applied since you are most probably going to need it later on. 

The first elements in a configuration are the modules that contain the resources we are going to use. We'll discuss modules in a later article on how to create your own. For now we'll be using modules provided by Microsoft or available in the Gallery.

Node Blocks
Each DSC configuration is required to have at least one "Node" block. Within the node block, we add the configuration to be applied to that specific node - the machine we are trying to configure. We can have for example configurations that apply to specific nodes/machines or to any machine using the "localhost" node name as above. 

Resource Blocks
The resource blocks are the individual settings we want to apply and their type is usually named after the entity to be configured. In the above example we are using the "WindowsFeature" resource in order to configure the Web-Server windows feature and the "File" resource to copy a file. You get the point, right?

Resource blocks are also named and their names are usefull when troubleshooting and creating dependencies. In most cases, your configuration will consist of many resources and those will most probably depend on each other. In our WebApp001 example, I've configured the "WebSiteContent" file resource to depend on the "WebServer" windows feature. This means that the file will not be copied unless the role has been installed. Pay attention to the "DependsOn" directive of the file resource, it references the resource it depends on using both its type and name. Of cource you can have a resource depend on multiple others.

Modules
The modules that we include in the configuration block provide the definitions for all those resources. If we want to configure the settings on the DNS role for example, we are going to need a module like xDNSServer. You can think of configurations as Powershell scripts that are executed on our target node and resources as functions that when executed, configure a setting. The Powershell Gallery repository contains modules with DSC resources as well and you can install them using the Install-Module -Scope AllUsers command.

The vast number of modules and the resources they contain may seem a bit intimidating but fortunatelly the majority of the tools we use to author configurations - such as Visual Studio Code and Powershell ISE, have IntelliSense capabilities that are extremely helpful. Hitting Ctrl + Space whithin a resource block shows the list of available options and their type:


The most important thing about modules is that they have to be available on the target nodes. This is one of the reasons that when we have complicated configurations we prefer using a Pull service. This server serves not only the configuration files but also the required modules to the nodes

Even though I haven't come across it many times, there's another feature of the configurations that I'd like to mention and that is parameters. Just like with Powershell functions and cmdlets, we have the ability to use parameters in order to dynamically configure the settings to apply.

The following configuration installs all the components and file required for a website:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
Configuration WebApp002 {
    # Parameters
    Param
    (
        [int]$Port = 8080
    )

    # Import the modules that contains the resources we're using.
    Import-DscResource -ModuleName PsDesiredStateConfiguration
    Import-DscResource -ModuleName xNetworking
    Import-DscResource -ModuleName xWebAdministration

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

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

        WindowsFeature WebServerManagement {...}

        xWebAppPool AppPool {...}

        # The second resource block ensures that the website content copied to the website root folder.
        File WebSiteContent {...}

        xWebSite WebSite {
            Ensure          = "Present"
            Name            = "MySite"
            PhysicalPath    = 'C:\inetpub\MySite'
            ApplicationPool = "MySiteAppPool"
            BindingInfo     = @( MSFT_xWebBindingInformation
                                 {
                                   Protocol = "HTTP"
                                   Port     = $Port
                                 }
                            )
            DependsOn       = '[xWebAppPool]AppPool','[File]WebSiteContent'
        }
        
        xFirewall AllowWebSite {
            Ensure    = 'Present'
            Name      = "Allow My Website"
            Enabled   = 'True'
            Action    = 'Allow'
            LocalPort = $Port
            Protocol  = 'TCP'
        }        
    }
}

The administrator can change the port on which the website will be listening for connnections using the "Port" parameter during the compilation of the configuration:


When the configuration is applied to the node, a new website is created with a binding on port 8081:

along with a firewall rule to allow network traffic on that port:


The configurations used in this article are available in my Github repository here.

In the upcoming articles we'll see more tips and tricks regarding configurations and how to troubleshoot issues when applying them. 

Related Articles
    Part 3 - Authoring Configurations

Popular posts from this blog

Domain Controller Machine Password Reset

Configuring a Certificate on Exchange Receive Connector

Running Multiple NGINX Ingress Controllers in AKS