Monday, May 2, 2016

From monolithic to split DSC configurations

In the previous post I briefly introduced the PowerShell Configuration keyword which is used to describe desired states for your nodes.

Let’s have today a deeper look at its basic logic and see how Microsoft offered us a way to improve its usability in real-word environments.

1. DSC SNIPPETS

To ease our learning process, we can take advantage of a great PowerShell ISE functionality which is called Snippets. These are reusable sections of code that you can insert into your scripts, just like templates.

The default set of snippets that comes with your ISE comprehends five DSC-related snippets:


2. MONOLITHIC CONFIGURATIONS

In the simplest case, we will use the first snippet: DSC Configuration (simple)

configuration Name
{
    # One can evaluate expressions to get the node list
    # E.g: $AllNodes.Where("Role -eq Web").NodeName
    node ("Node1","Node2","Node3")
    {
        # Call Resource Provider
        # E.g: WindowsFeature, File
        WindowsFeature FriendlyName
        {
           Ensure = "Present"
           Name   = "Feature Name"
        }

        File FriendlyName
        {
            Ensure          = "Present"
            SourcePath      = $SourcePath
            DestinationPath = $DestinationPath
            Type            = "Directory"
            DependsOn       = "[WindowsFeature]FriendlyName"
        }
    }
}
As you can see the structure you see above matches the scripting rules I explained in the previous post, and contains two sections:
  • the list of nodes I am applying a configuration to (node1, node2 and node3)
  • the configuration to apply (a feature and a file in this example)
In my case I want to adapt this example to configure UAC and IEESC, so, after having installed the xSystemSecurity module (Install-Module or manually download from GitHub), I can easily modify the snippet to suit my needs:

configuration DisableUacIEEsc
{
    # Importing the required modules
    Import-DSCResource -Module xSystemSecurity -Name xUac
    Import-DSCResource -Module xSystemSecurity -Name xIEEsc

    node ("Node1","Node2","Node3")
    {
        xUAC NeverNotifyAndDisableAll
        {
           Setting = "NeverNotifyAndDisableAll"
        }
        xIEEsc DisableIEEsc
        {
            IsEnabled = $false
            UserRole = "Administrators"
        }       
    }
}

DisableUacIEEsc
The last line is the actual call to the Configuration, which generates one MOF file for each target computer in a subfolder named as the configuration itself under the current path:

E:\DSC\uacieesc.ps1
WARNING: The configuration 'DisableUacIEEsc' is loading one or more built-in resources without explicitly importing associated modules. Add Import-DscResource –ModuleName 'PSDesiredStateConfiguration' to your configuration to avoid this message.


    Directory: E:\DSC\DisableUacIEEsc


Mode       LastWriteTime         Length Name
----       -------------         ------ ----
-a----     4/28/2016  1:31 PM    6102 Node1.mof
-a----     4/28/2016  1:31 PM    6102 Node2.mof
-a----     4/28/2016  1:31 PM    6102 Node3.mof
Those MOF files are text files whose format has been standardized by the DMTF and that are passed to the Start-DscConfiguration cmdlet for execution:
Start-DscConfiguration -path .\DisableUacIEEsc -Wait -ComputerName Node1

A couple of notes here:
  • we are not passing the name of a single MOF but the name of folder that contains configuration settings files
  • the –Wait parameter is particularly useful here to follow the configuration application in real-time. In this case the appropriate registry keys are added to the target server. In the example below here’s how the registry keys for IEEsc appear after running Start-DscConfiguration.

Now that was the simplest case, which is perfect for the novice to understand how the Configuration keyword works.

3. SPLIT CONFIGURATIONS

I want to show you now how you can adopt a less monolithic approach and separate the configurations that you want to apply from the group of target computers you want to apply them to. There are a few major evident reasons for doing so, starting from the fact that we don’t want to hardcode the name of the servers in the same file used for keeping the desired configurations.
Let’s exercise a bit and convert the previous example to something more modular and agile.
A DSC Configuration like the one we built here above as a ConfigurationData parameter that accepts an optional hashtable which contains the information on the target computers:
Get-Command DisableUacIEEsc -Syntax

DisableUacIEEsc [[-InstanceName] ] [[-DependsOn] ] [[-OutputPath] ] [[-ConfigurationData] ] []
The called hashtable must contain at least the keyword AllNodes:

$MyEnvironment = 
@{
    AllNodes = @();
    NonNodeData = “”   
} 

$MyEnvironment

Name                           Value
----                           -----
AllNodes                       {}
NonNodeData
Inside the AllNodes you have to put a list of hashtables (as simple as @{ propertyname = value }) like in the following example:
$MyEnvironment = @{
    AllNodes = @(
        @{
        NodeName = "Node1"
        },
        @{
        NodeName = "Node1"
        },
        @{
        NodeName = "Node1"
        }
    )
}
Now I want to explicitly declare some of my nodes as DEV nodes, and others as PRODUCTION nodes:
$MyEnvironment = @{
    AllNodes = @(
        @{
        NodeName = "Node1"
        Role = 'PROD'
        },
        @{
        NodeName = "Node1"
        Role = 'DEV'
        },
        @{
        NodeName = "Node1"
        Role = 'DEV'
        }
    )
}
 $MyEnvironment.AllNodes

Name                           Value
----                           -----
NodeName                       Node1
Role                           PROD
NodeName                       Node1
Role                           DEV
NodeName                       Node1
Role                           DEV
When you save this file, remember to use the .psd1 extension, which is the same used for module manifests, and to remove the variable name at the beginning, otherwise you’ll get the following error:
DisableUacIEEsc : Cannot process argument transformation on parameter 'ConfigurationData'. Failed to load the PowerShell data file 'myenv.psd1' with the following error:
At E:\DSC\myenv.psd1:1 char:1
+ $MyEnvironment = @{
+ ~~~~~~~~~~~~~~~~~~~~
Assignment statements are not allowed in restricted language mode or a Data section.
At E:\DSC\myenv.psd1:1 char:1
+ $MyEnvironment = @{
+ ~~~~~~~~~~~~~~~
A variable that cannot be referenced in restricted language mode or a Data section is being referenced. Variables that can be referenced include the following: 
$PSCulture, $PSUICulture, $true, $false, and  $null.
At E:\DSC\uacieesc2.ps1:19 char:36
+ DisableUacIEEsc -ConfigurationData myenv.psd1
+                                    ~~~~~~~~~~
    + CategoryInfo          : InvalidData: (:) [DisableUacIEEsc], ParameterBindingArgumentTransformationException
    + FullyQualifiedErrorId : ParameterArgumentTransformationError,DisableUacIEEsc
Once you have defined your environment, it is time to move to rewrite the configuration definition file as follow:
configuration DisableUacIEEsc
{
    Import-DSCResource -Module xSystemSecurity -Name xUac
    Import-DSCResource -Module xSystemSecurity -Name xIEEsc
    node $AllNodes.Where{$_.Role -eq “DEV”}.NodeName
    {
        xUAC NeverNotifyAndDisableAll
        {
           Setting = "NeverNotifyAndDisableAll"
        }
        xIEEsc DisableIEEsc
        {
            IsEnabled = $false
            UserRole = "Administrators"
        }       
    }
}

DisableUacIEEsc -ConfigurationData myenv.psd1
In my case I want to disable Uac and IEEsc only on DEV servers, so I replaced the hardcoded node names with the $AllNodes automatic variable and its Where() method.

4. SUMMIN' IT UP

In the end you have two files for a split DSC configuration against one file in case of a monolithic approach:

5. SNIPPETS AGAIN

To finish with, if you like the idea (you should) of separating your environmental configuration from the configuration itself, and you are scared of so much typing, remember that two ISE snippets are available for split DSC configurations:

DSC Configuration Data:
@{
    AllNodes = @(
        @{
            NodeName = "Node1"
            Role = "WebServer"
            },
        @{
            NodeName = "Node2"
            Role = "SQLServer"
            },
        @{
            NodeName = "Node3"
            Role = "WebServer"
            }
    )
}
# Save ConfigurationData in a file with .psd1 file extension


...and DSC Configuration (using ConfigurationData):
configuration ConfigurationName
{
    # One can evaluate expressions to get the node list
    # E.g: $AllNodes.Where("Role -eq Web").NodeName
    node $AllNodes.Where{$_.Role -eq "WebServer"}.NodeName
    {
        # Call Resource Provider
        # E.g: WindowsFeature, File
        WindowsFeature FriendlyName
        {
           Ensure = "Present"
           Name   = "Feature Name"
        }

        File FriendlyName
        {
            Ensure          = "Present"
            SourcePath      = $SourcePath
            DestinationPath = $DestinationPath
            Type            = "Directory"
            DependsOn       = "[WindowsFeature]FriendlyName"
        }       
    }
}

# ConfigurationName -configurationData 

In a next posts we will move the automation slider a bit further. Stay tuned.

1 comment:

Related Posts Plugin for WordPress, Blogger...