Config variables in PowerShell DSC can confuse

I’m in the middle of preparing a talk for an upcoming Sitecore user group on the subject of using PowerShell DSC for Sitecore installs. (I’ll post a write up of the talk after I’ve given it) And one of the things I’ve discovered is that once you get into the guts of it, using configuration variables alongside Script Resources can be a bit confusing.

So, for the benefit of my future self (I’m bound to forget this as I am taking time off from my preparation to head over to the US for this and this) I’m writing down what I’ve worked out…

Once you move away from very basic configurations, it seems to me I’ll end up with nested dictionaries of Name/Value pairs in the configuration for my DSC scripts. I found myself with something along the lines of:

@{
    AllNodes = @(
        @{
            NodeName = "My-Server"
            Role = "RoleOne, RoleTwo"
            
            RoleOneSoftware = @{
                DiskPath = "c:\SomeFolder\"
                SettingOne = "alpha"
                SettingTwo = "bravo"
            }

            RoleTwoSoftware = @{
                DiskPath = "c:\OtherFolder\"
                SettingOne = "charlie"
                SettingTwo = "delta"
            }
         }
    );
}

Contrived example – but the idea is that in order to keep the setting names sensible, you probably end up nesting things. Easy enough to declare, and better (I think) from the perspective of managing your config settings in the future.

But what happens when you try to make use of these values?

Well for most of DSC’s Resources, it’s not too bad. Inside your Node block, there’s a variable defined for you called $Node that contains the data above for the node which has been selected by the script when it’s executed. And it’s a dictionary object that works a bit like a “dynamic” variable in C# so you can just reference the properties:

Configuration ContrivedExample
{
    Node $AllNodes.where{ $_.Role.Contains("RoleOne") }.NodeName
    {
        File CreateTheRoleFolder
        {
            Type = "Directory"
            DestinationPath = "$($Node.RoleOneSoftware.SomeFolder)"
            Ensure = "Present"
        }
    }
}

The enclosing $() force PowerShell to treat what’s inside as a variable name, in order to prevent any confusion on its part. I don’t think this is always necessary – but in my head at least it’s easier to use it everywhere than occasionally be confused when a particular Resource fails because you forgot to use it when you needed to.

Things get a bit more complicated when the resource in question is a Script resource though. A side-effect of how these are implemented is that when you’re filling in the GetScript, TestScript and SetScript properties, you’re specifying strings not scripts. So you need to be very careful about how you specify variables because the usual rules of how PowerShell does variable replacement don’t quite work with your configuration data.

There are two things to remember. By default, variables you write in the normal $someName way are going to be evaluated at the point that your script code is modifying a server. That’s not the point where you run the Configuration you’re declaring here. That means you can’t reference variables declared by PowerShell DSC in this way (for example $Node) because their values are not available when the script runs. For those variables, there’s a special syntax. You have to write $using:Node instead. This causes PowerShell DSC to inject the value correctly into the output when it “compiles” your DSC script into the file which Windows uses to change the state of your computer.

The trouble is this starts to look very messy when you’re dealing with configuration variables, and I had a lot of trouble getting it to work for a while. Where I’ve ended up is that I’ve found myself declaring a lot of temporary variables, to make the code a bit clearer to me. For example:

Configuration ContrivedExample
{
    Node $AllNodes.where{ $_.Role.Contains("RoleOne") }.NodeName
    {
        Script DemonstrateVariables
        {
            GetScript = {
               # whatever code goes here
            }
            TestScript = {
               # whatever code goes here
            }
            SetScript = {
               $diskPath = $using:Node.RoleOneSoftware.DiskPath
               $settingOne = $using:Node.RoleOneSoftware.SettingOne

               $computedValue = "$diskPath\$settingOne"

               Write-Verbose "The computed value is $computedValue"
            }
        }
    }
}

It’s not pretty – but using that approach has worked for me.

Hopefully as my DSC knowledge increases I’ll work out how to make better use of this…

Advertisements

3 thoughts on “Config variables in PowerShell DSC can confuse

  1. Pingback: Development environments with PowerShell DSC – Part 1 | Jeremy Davis
  2. Pingback: Development environments with PowerShell DSC – Part 3 | Jeremy Davis
  3. haha, i really wish i read this post before i banged my head against the wall for 5 hours trying to debug why my Script resource was not working. the answer was what you wrote here: $Node and other variables are not preprocessed by the DSC compilation.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s