Creating Node Selection on the fly

So I am looking at setting up DSC to manage environments for multiple projects, and within my DSC Configuration I can select the nodes to apply configuration to in one of three ways…

If I want to build the entire environment for one project I can pass in a parameter $Project and use that to select the nodes for the Node{} block to create mof’s for.

Node $AllNodes.where { $_.Project -eq “$Project” }.NodeName
{

}

If I want to build all the web servers I can pass in a parameter $Role and select the nodes based on that

Node $AllNodes.where { $_.Role -eq “$Role” }.NodeName
{

}

which builds all the web-servers for all the projects, so I can also set up my Node selection like this

Node $AllNodes.where { ($_.Project -eq "$Project") -and ($_.Role -eq "$Role" ) }.NodeName { ...... } And that will build all the servers of the select role for the selected project.

It’s a bit messy.

Problem is, to do this I have a bunch of if/else statements to interrogate the parameters and then select one of the three version of the Node{} block to execute.

As I say, messy.

What I would like to try is creating that Node selection string on the fly, I could interrogate the parameters, construct the Node selection criteria, and just have a single Node {} block. That would make sense to me.

Something like this…

If ( $Project -and $Role){
$NodeSelection = “($.Project -eq “$Project”) -and ($.Role -eq “$Role” )”
}ElseIf ( $Project){
$NodeSelection = “($.Project -eq “$Project”)"
}If ( $Role){
$NodeSelection = "($
.Role -eq “$Role” )”
}

Node $AllNodes.where{$NodeSlection}.NodeName
{

}

However, no matter what I do, the "$" in the $NodeSelection either gets interpreted when the $NodeSelection string is created, evaluating to $null, or it never gets evaluated at all and stays as '$’ in the final evaluation.

This may or may not be the right way to go about what I am doing, but I’d like to see if it could be done, now that I have started.

Any suggestions?
Thanks, Simon.

Personally, I only ever have one node block in my configurations. Then I do conditionals inside of it:

node $AllNodes.NodeName
{
    if ($node.Project -eq $Project -and $node.Role -eq $Role)
    {
        # Do some resources here
    }
}

You’re writing essentially the same code, but I find it easier to read.

Dave

Yes, that is easier to read, easier to write too, and possibly the way to go, though essentially it just moves the messy bit and the duplication inside the node block instead of outside it.

My thinking was that if, say, I wanted to build just 20 of the machines in a configuration data file with 300 machines, by filtering the nodes I want before entering the node block, I only have to execute the node block 20 times instead of 300. And it keeps the actual code in the block smaller and tidier.

This way also would be more reusable as I could modify the node filtering criteria to my hearts content without ever having to touch the actual code inside the node block.

And, importantly, it annoys me. I should be able to work out how to do it and I can’t, and I don’t like that.

Maybe the saving is irrelevant, I started my programming career in assembly on Motorola 6800’s (pre 68000’s) and optimisation was the watchword for Everything.

Ah, I see what you mean. You could take a script block filter as a parameter to your configuration function, if you wanted that dynamic filtering:

configuration Whatever
{
    param (
        [ValidateNotNull()]
        [scriptblock] $Filter = { $true }
    )
    
    node $AllNodes.Where($Filter).NodeName
    {
        # Do Stuff
    }
}

# Then to call it:

Whatever -ConfigurationData $someHashTable -Filter { ($_.Project -eq "$Project") -and ($_.Role -eq "$Role" ) }

That looks like an interesting option, I’ll give it a whirl.
Thanks.

Couldn’t get that to work.
It keeps complaining that it can’t convert the string to a script block, either that or the $_ ends up as a string ‘$_’ and never gets evaluated to an object.

What does your code look like? I ran the stuff I posted, and it worked fine. Remember, you’re not passing in a string, it’s a ScriptBlock object. (Curly braces instead of quotation marks.)

Here’s what I ran to test:

configuration Whatever
{
    param (
        [ValidateNotNull()]
        [scriptblock] $Filter = { $true }
    )
    
    node $AllNodes.Where($Filter).NodeName
    {
        # Do Stuff
        Write-Verbose -Verbose $Node.NodeName
    }
}

$configData = @{
    AllNodes = @(
        for ($i = 1; $i -lt 10; $i++)
        {
            @{
                NodeName = "Node$i"
                Number = $i
            }
        }
    )
}

Whatever -ConfigurationData $configData -Filter { $_.Number % 2 -eq 0 }

Got verbose output of the even numbered node names, as you’d expect.

Dave
Sorry, been distracted by work.
My code is like this:

Configuration config_three
{
    param(
        [string] $NodeSelection
    )

    node $AllNodes.Where($NodeSelection).NodeName
    {
        Write-Host "  " $Node.NodeName " " $Node.Product " " $Node.Role

        File HaveThisDirectory
        {
            Ensure = "Present"
            DestinationPath = "C:\Temp\dud"
            Recurse = $false
            Type = "Directory"
        }
    }
} 

config_three  -NodeSelection  {$_.Role -eq "build" -and $_.Product -eq "ONE"} -ConfigurationData .\configdata.psd1  -OutputPath C:\Temp\mof\store

And I get the error:

PSDesiredStateConfiguration\Configuration : Cannot convert the "$_.Role -eq "build" -and $_.Product -eq "ONE"" value of type "System.String" to type "System.Management.Automation.ScriptBlock".

I have tried many variations on how I run this command, but no success.

I have actually taken an entirely different tack on a solution to the original problem now, one that is more likely to suit our usage.

I keep the Configuration simple, I just process every node in the configuration data.

What I do now is put the smarts in a higher level script that generates the configuration data file on the fly, so that it only contains the nodes I am interested in and I process them all.

It keeps the actual Configuration simple, I can create other Configurations for anything I want, and all the Configuration Data files are generated on the fly from a single source where I can maintain all the config data in one place.

Your parameter was still being declared as type [string], instead of [scriptblock]. (It also had no default value, but that’s fine if you want to make it mandatory.) Take a closer look at the example I posted:

param (
        [ValidateNotNull()]
        [scriptblock] $Filter = { $true }
)

Damn! I knew it would be something simple, and obvious, just missed it entirely.

I cursed the error message for giving me no clue as to what the problem was, and it was telling me the whole time!

Thanks.