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 simple headless PSDSC script, which compiles and runs a configuration. Follow that link and save a copy as headless_dsc.ps1 and its XML file as input.xml.

Next, copy this Vagrantfile into 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 that I recommend you keep in your code so this setting isn’t confusing later.

Happy automating!


Check out these related posts: