Question

I'm having a lot of trouble trying to get a PowerShell Desired State Configuration script working to configure an in-house application. The root of the problem is that I can't seem to pass my configuration data down into a ScriptResource (at least not with the way I'm trying to do it).

My script is supposed to create a config folder for our in-house application, and then write some settings into a file:

configuration MyApp {
    param (
        [string[]] $ComputerName = $env:ComputerName
    )
    node $ComputerName {

        File ConfigurationFolder {
            Type = "Directory"
            DestinationPath = $Node.ConfigFolder
            Ensure = "Present"
        }

        Script ConfigurationFile {
            SetScript = {
                write-verbose "running ConfigurationFile.SetScript";
                write-verbose "folder = $($Node.ConfigFolder)";
                write-verbose "filename = $($Node.ConfigFile)";
                [System.IO.File]::WriteAllText($Node.ConfigFile, "enabled=" + $Node.Enabled);
            }
            TestScript = {
                write-verbose "running ConfigurationFile.TestScript";
                write-verbose "folder = $($Node.ConfigFolder)";
                write-verbose "filename = $($Node.ConfigFile)";
                return (Test-Path $Node.ConfigFile);
            }
            GetScript = { @{Configured = (Test-Path $Node.ConfigFile)} }         
            DependsOn = "[File]ConfigurationFolder"
        }

    }
}

For reference, my configuration data looks like this:

$config = @{
    AllNodes = @(
        @{
            NodeName = "*"
            ConfigFolder = "C:\myapp\config"
            ConfigFile = "C:\myapp\config\config.txt"
        }
        @{
            NodeName = "ServerA"
            Enabled = "true"
        }
        @{
            NodeName = "ServerB"
            Enabled = "false"
        }
    )
}

And I'm applying DSC with the following:

$mof = MyApp -ConfigurationData $config;
Start-DscConfiguration MyApp –Wait –Verbose;

When I apply this configuration it happily creates the folder, but fails to do anything with the config file. Looking at the output below, it's obvious that it's because the $Node variable is null inside the scope of ConfigurationFile / TestScript, but I've got no idea how to reference it from within that block.

LCM:  [ Start  Resource ]  [[Script]ConfigurationFile]
LCM:  [ Start  Test     ]  [[Script]ConfigurationFile]
                           [[Script]ConfigurationFile] running ConfigurationFile.TestScript
                           [[Script]ConfigurationFile] node is null = True
                           [[Script]ConfigurationFile] folder =
                           [[Script]ConfigurationFile] filename =
LCM:  [ End    Test     ]  [[Script]ConfigurationFile]  in 0.4850 seconds.

I've burnt off an entire day searching online for this specific problem, but all the examples of variables, parameters and configuration data all use File and Registry resources or other non-script resources, which I've already got working in the "ConfigurationFolder" block in my script. The thing I'm stuck on is how to reference the configuration data from within a Script resource like my "ConfigurationFile".

I've drawn a complete blank so any help would be greatly appreciated. If all else fails I may have to create a separate "configuration" script per server and hard-code the values, which I really don't want to do if at all possible.

Cheers,

Mike

Was it helpful?

Solution 4

ConfigurationData only exists at the time the MOF files are compiled, not at runtime when the DSC engine applies your scripts. The SetScript, GetScript, and TestScript attributes of the Script resource are actually strings, not script blocks.

It's possible to generate those script strings (with all of the required data from your ConfigurationData already expanded), but you have to be careful to use escapes, subexpressions and quotation marks correctly.

I posted a brief example of this over on the original TechNet thread at http://social.technet.microsoft.com/Forums/en-US/2eb97d67-f1fb-4857-8840-de9c4cb9cae0/dsc-configuration-data-for-script-resources?forum=winserverpowershell

OTHER TIPS

Change this: $Node.ConfigFolder to $using:Node.ConfigFolder.

If you have a variable called $Foo and you want it to be passed to a script DSC resource, then use $using:Foo

Based on David's answer, I've written a utility function which converts my script block to a string and then performs a very naive search and replace to expand out references to the configuration data as follows.

function Format-DscScriptBlock()
{
    param(
        [parameter(Mandatory=$true)]
        [System.Collections.Hashtable] $node,
        [parameter(Mandatory=$true)]
        [System.Management.Automation.ScriptBlock] $scriptBlock
    )
    $result = $scriptBlock.ToString();
    foreach( $key in $node.Keys )
    {
        $result = $result.Replace("`$Node.$key", $node[$key]);
    }
    return $result;
}

My SetScript then becomes:

SetScript = Format-DscScriptBlock -Node $Node -ScriptBlock {
                write-verbose "running ConfigurationFile.SetScript";
                write-verbose "folder = $Node.ConfigFolder";
                write-verbose "filename = $Node.ConfigFile)";
                [System.IO.File]::WriteAllText("$Node.ConfigFile", "enabled=" + $Node.Enabled);
            }

You have to be mindful of quotes and escapes in your configuration data because Format-DscScriptBlock only performs literal substitution, but this was good enough for my purposes.

A quite elegant way to solve this problem is to work with the regular {0} placeholders. By applying the -f operator the placeholders can be replaced with their actual values.

The only downside with this method is that you cannot use the curly braces { } for anything other than placeholders (i.e. say a hashtable or a for-loop), because the -f operator requires the braces to contain an integer.

Your code then looks like this:

        SetScript = ({ 
            Set-ItemProperty "IIS:\AppPools\{0}" "managedRuntimeVersion" "v4.0"
            Set-ItemProperty "IIS:\AppPools\{0}" "managedPipelineMode" 1 # 0 = Integrated, 1 = Classic
        } -f @($ApplicationPoolName))

Also, a good way to find out if you're doing it right is by simply viewing the generated .mof file with a text editor; if you look at the generated TestScript / GetScript / SetScript members, you'll see that the code fragment really is a string. The $placeholder values should already have been replaced there.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top