PowerShell DSC - how to pass configuration parameters to ScriptResources?

20,509

Solution 1

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

Solution 2

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

Solution 3

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.

Solution 4

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.

Share:
20,509

Related videos on Youtube

mclayton
Author by

mclayton

Updated on May 17, 2020

Comments

  • mclayton
    mclayton about 4 years

    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

  • BartekB
    BartekB about 9 years
    Actually, you can use curly braces, you just need to double them (so { becomes {{ and } becomes }}).
  • Falco Alexander
    Falco Alexander almost 8 years
    but the point is to put it in $() like : $($using:Node.ConfigFolder)
  • Tod Thomson
    Tod Thomson almost 8 years
    This should be the correct answer $using is provided to solve this problem for you, no need to generate the strings yourself... See msdn.microsoft.com/en-us/powershell/dsc/scriptResource for an example.
  • Vinay
    Vinay about 6 years
    Thanks, i would use this over $using approach, as it stores the whole psd object in MOF, while this one stores the required value. Definitely elegant, and as it's an array, I was able to use {0},{1} etc.