PowerShell DSC In Vagrant

I work mostly on Apple Mac OS X. I’ve also been writing a lot of Windows automation, and that means PowerShell DSC. PSDSC doesn’t work on OS X yet, and even once it does I won’t be able to test Windows-only resources. To test my configurations, I use Vagrant boxes. It took a little fiddling to get this set up, so I’m posting the code here in case you’re in the same situation.

This assumes you already have Vagrant working. I use the VirtualBox provider and I install everything with homebrew.

Today, Vagrant doesn’t have a native PowerShell DSC provisioner. I use the shell provisioner to run my Headless PSDSC script, which compiles and runs a configuration (headless_dsc.ps1):

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\

And here’s an input file to provide the example messages (input.xml):

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

Now all you need is a Vagrantfile in the same directory:

Vagrant.configure("2") do |config|
  config.vm.box = "StefanScherer/windows_2019"
  config.vm.provision "file" do |file|
    file.source = "input.xml"
    file.destination = "input.xml"
  config.vm.provision "shell" do |shell|
    shell.path = "headless_dsc.ps1"
    shell.args = ["-InputXmlFile", "input.xml"]
    # https://github.com/hashicorp/vagrant/issues/9138#issuecomment-444408251
    shell.privileged = false

To create the box and run PSDSC:

vagrant up

If you’ve made changes and you want to recomplie and rerun the configuration:

vagrant provision

If you need to connect to the instance and run commands, check out vagrant rdp. You’ll need to install Remote Desktop from the App Store. There’s a funky bug: if Remote Desktop isn’t open, the first rdp command will open the app but won’t start a session. Just run it again and you’ll get a session window.

The provisioner uploads the script and XML file on every run even though the current directory is mounted as a synced folder on the box by default. I could have used an inline shell script to call the script directly via that sync, but sometimes I disable the sync for testing and it’s convenient for the provisioner to keep working.

Without shell.privileged = false, I got errors like this:

default: (10,8):UserId:
default: At line:72 char:1
default: + $folder.RegisterTaskDefinition($task_name, $task, 6, $username, $pass ...
default: + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
default:     + CategoryInfo          : OperationStopped: (:) [], ArgumentException
default:     + FullyQualifiedErrorId : System.ArgumentException
default: The system cannot find the file specified. (Exception from HRESULT: 0x80070002)
default: At line:74 char:1
default: + $registered_task = $folder.GetTask("\$task_name")
default: + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
default:     + CategoryInfo          : OperationStopped: (:) [], FileNotFoundException
default:     + FullyQualifiedErrorId : System.IO.FileNotFoundException
default: You cannot call a method on a null-valued expression.
default: At line:75 char:1
default: + $registered_task.Run($null) | Out-Null
default: + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
default:     + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
default:     + FullyQualifiedErrorId : InvokeMethodOnNull

The whole process froze and I had to ctrl+c twice to close it. There’s a bug. I left a link in a comment in the Vagrantfile that I recommend you keep in your code so this setting isn’t confusing later.

Happy automating!


Need more than just this article? We’re available to consult.

You might also want to check out these related articles: