Simple Headless PowerShell DSC Script

Hello!

In clouds, “headless” deployment means instances provision themselves when they start. There’s no external server infrastructure orchestrating their config, everything they need to do they do on their own. This is the most common deployment pattern I’ve seen in DevOps.

It took me some fiddling to get this pattern set up in PowerShell DSC for my situation, so I’m posting the code in case you’re running in to the same thing. It’s a single script that compiles and runs your configuration, optionally reading inputs from an XML file.

First, the code:

Param(
    [Parameter(Mandatory=$false)]
    [String[]]
    $InputXmlFile
)

if ($InputXmlFile) {
    [XML]$Inputs = Get-Content -Path $InputXmlFile
}

$NodeData = @{
    NodeName = "localhost"
    Message = "This was hardcoded in the node data."
    Inputs = $Inputs.inputs
}
$ConfigurationData = @{AllNodes = @($NodeData)}

Configuration Headless {
    Import-DscResource -ModuleName PSDesiredStateConfiguration

    Node 'localhost' {
        Log Message {
            Message = $AllNodes.Message
        }
        if ($AllNodes.Inputs) {
            Log Input {
                Message = $AllNodes.Inputs.Message
            }
        }
    }
}

Headless -ConfigurationData $ConfigurationData
Start-DscConfiguration -Wait -Force -Verbose -Path .\Headless\

A sample XML file:

<Inputs>
    <Message>This message came from the XML inputs.</Message>
</Inputs>

And the script output on a Windows Server 2019 Vagrant box:

PS C:\vagrant> .\headless_dsc.ps1 -InputXmlFile .\input.xml

    Directory: C:\vagrant\Headless

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
------       11/11/2019   8:21 AM           2488 localhost.mof
VERBOSE: Perform operation 'Invoke CimMethod' with following parameters, ''methodName' =
SendConfigurationApply,'className' = MSFT_DSCLocalConfigurationManager,'namespaceName' =
root/Microsoft/Windows/DesiredStateConfiguration'.
VERBOSE: An LCM method call arrived from computer VAGRANT with user sid S-1-5-21-1529561135-2561037041-51718299-1000.
VERBOSE: [VAGRANT]: LCM:  [ Start  Set      ]
VERBOSE: [VAGRANT]: LCM:  [ Start  Resource ]  [[Log]Message]
VERBOSE: [VAGRANT]: LCM:  [ Start  Test     ]  [[Log]Message]
VERBOSE: [VAGRANT]: LCM:  [ End    Test     ]  [[Log]Message]  in 0.0000 seconds.
VERBOSE: [VAGRANT]: LCM:  [ Start  Set      ]  [[Log]Message]
VERBOSE: [VAGRANT]:                            [[Log]Message] This was hardcoded in the node data.
VERBOSE: [VAGRANT]: LCM:  [ End    Set      ]  [[Log]Message]  in 0.0000 seconds.
VERBOSE: [VAGRANT]: LCM:  [ End    Resource ]  [[Log]Message]
VERBOSE: [VAGRANT]: LCM:  [ Start  Resource ]  [[Log]Input]
VERBOSE: [VAGRANT]: LCM:  [ Start  Test     ]  [[Log]Input]
VERBOSE: [VAGRANT]: LCM:  [ End    Test     ]  [[Log]Input]  in 0.0000 seconds.
VERBOSE: [VAGRANT]: LCM:  [ Start  Set      ]  [[Log]Input]
VERBOSE: [VAGRANT]:                            [[Log]Input] This message came from the XML inputs.
VERBOSE: [VAGRANT]: LCM:  [ End    Set      ]  [[Log]Input]  in 0.0000 seconds.
VERBOSE: [VAGRANT]: LCM:  [ End    Resource ]  [[Log]Input]
VERBOSE: [VAGRANT]: LCM:  [ End    Set      ]
VERBOSE: [VAGRANT]: LCM:  [ End    Set      ]    in  0.0940 seconds.
VERBOSE: Operation 'Invoke CimMethod' complete.
VERBOSE: Time taken for configuration job to complete is 0.372 seconds

Some notes:

  • There are several modules that implement DSC resources. I start with PSDesiredStateConfiguration and then upgrade if I need to. Check out this article if you’re getting errors defining resources or their properties.
  • This uses Log resources to demonstrate functionality and to show you how to access the ConfigurationData. You should replace those in your config.
  • You don’t have to use ConfigurationData to pass the inputs. You’ll see other patterns using regular function params, for example. I used ConfigurationData because it’s DSC’s built-in method and I use the native approach unless I have a specific reason to use something else.
  • I used XML for the inputs because PowerShell supports XML natively. It also supports JSON, but JSON makes you escape backslashes \ and since those are common path characters in Windows it makes for ugly input strings. Plus XML supports comments and JSON doesn’t and in DevOps you very often need to add comments to your input choices.
  • This is written to run directly as a PowerShell script because that’s what I needed at the time. If you’re using Azure Resource Manager Template, check out its native integration.
  • This assumes you always want verbose output. DSC is a quiet tool by default and I prefer to make it verbose. You may want to parameterize or consider using CmdletBinding.
  • This assumes you’ll only ever define one node: localhost. If you adjust that you’ll need to update the node references.

Happy configuring!

Adam

If this was helpful and you want to save time by getting “copy and paste” patterns for PowerShell and Cloud DevOps in your inbox, subscribe here.