Provisioning Vagrant Windows Environments with PowerShell Desired State Configuration

In this article, I’ll describe an approach to provisioning using Powershell Desired State Configuration (DSC) in Vagrant.  I’ll deploy a static website to IIS on Windows Server 2012 to showcase this approach.  At the end of the article, I’ve included the final Vagrantfile as well as a DSC Configuration script, but would recommend to read through my steps in order to get a sense on why it was done as such.

On another note, I’m currently employing this setup to develop and test custom DSC modules.  Using Vagrant for this purpose has saved me quite a bit of time and given me and fellow developers a nice development workflow.


Create Windows Server 2012 Vagrant Box

The focus of this article is on provisioning.  However I’m including a summary of steps taken to create a WS2012 Virtual Box using Packer (For information on manually creating Vagrant Windows Boxes see my previous post)

  1. Clone packer-windows Git Repository and copy your WS2012 ISO to “iso” folder in packer-windows (If you’re not using Git, you can just download the latest release of packer-windows)
  2. Update each builder in packer-windows\windows_2012_r2.json with a relative path to your iso and your respective checksum (I used the SHA1 from MSDN)
    "iso_url": "./iso/en_windows_server_2012_r2_essentials_with_update_x64_dvd_4119207.iso", 
    "iso_checksum_type": "sha1", 
    "iso_checksum": "316A2...",
  3. Optional Config:
    1. Disabled Windows Update in packer-windows\answer_files\2012_r2\Autounattend.xml
    2. If not using the Windows Evaluation ISO, Add your product key to packer-windows\answer_files\2012_r2\Autounattend.xml. 
    3. If installing a different Windows Server 2012 other than Standard or Standard Core, you may want to set headless to true in the json file and use the UI to finish the installation (In my case I did this with WS2012 R2 Essentials)
    4. Remove the VMWare builder in the json file if you’re just interested in the Virtual Box VM (or vice versa)
  4. Run command:
    packer build windows_2012_r2.json
  5. Add newly created box to vagrant:
    vagrant box add ws2012e_r2_base

Setup WMF 5.0 and PowerShellGet

In order to use the new Package Manager “PowerShellGet” we need to install the latest WMF 5.0 Preview and configure a PowerShellGet repository.  For the purpose of the exercise, we’ll configure the current central repository called “PowerShell Gallery”.  These steps assume you have followed the steps in the previous section or you have a windows vagrant box with WinRM communications already configured and working.

  1. Create a new Vagrant project based on your WS2012 vagrant box and enable WinRM
    C:\VMs\>mkdir WMF5VM
    C:\VMs\>cd WMF5VM
    C:\VMs\WMF5VM\>vagrant init ws2012e_r2_base
  2. Edit generated Vagrantfile: Set the communicator to winrm and if using VirtualBox you can give it a name to make repackaging a bit easier.
    config.vm.communicator = "winrm"
    config.vm.provider "virtualbox" do |v| = "ws2012evm"
  3. Bring up the VM and RDP into it
    C:\VMs\WMF5VM\>vagrant up
    C:\VMs\WMF5VM\>vagrant rdp
  4. Install WMF 5.0 November Preview on the Guest VM via RDP or the GUI
  5. Configure the guest VM to be part of the same Windows Domain as the host or add your host computer as a trusted host on WinRM or use a wildcard (please note using wildcard is not recommended for security reasons):
    PS C:\>winrm set winrm/config/client @{TrustedHosts="*"}
  6. Install NuGet (See Getting Started with the PowerShell Gallery).
    PS C:\>Get-PackageProvider -Name NuGet -ForceBootstrap
  7. Trust the Microsoft PowerShell Gallery
    PS C:\>Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
  8. Shutdown VM, repackage as a new vagrant box, and add it to the index
    vagrant package –base <your vm as listed in virtual box> –output <location of your new box file>

    C:\VMs\WMF5VM\>vagrant halt
    C:\VMs\WMF5VM\>vagrant package --base ws2012evm --output C:\boxes\
    C:\VMs\WMF5VM\>vagrant box add ws2012e_r2_wmf5 C:\boxes\

Create a new DSC/WebSite Project and Initialize Vagrant

  1. Create a new directory for the vagrant project and initialize vagrant:
    C:\VMs\>mkdir DSCVM
    C:\VMs\>cd DSCVM
    C:\VMs\DSCVM\>vagrant init ws2012e_r2_wmf5
  2. Create folder structure for storing your website files and DSC config/mof/custom module files.  The structure below is a good starting point, but is only meant to be an example.  You may want to do things a bit differently based on your needs.
    ├── Vagrantfile
    ├── DSC
    │   ├── Config
    │   │   ├── <DSC Configuration Files>.ps1
    │   ├── MOF
    ├── MySite
    │   │   ├── index.asp
  3. Create a new DSC configuration file
    1. Add parameters to the top of the file which may need to be passed from the Vagrantfile.  Parameters I’d recommend to start with:
      param (
          [string]$nodeName = "localhost",
          [string]$mofFolder = "C:\tmp\MOF\"
    2. We’re “pushing” the DSC configuration, therefore I suggest to add code to clean up your config MOF folder on every provision
      if (Test-Path($mofFolder)) {
          Remove-Item $mofFolder -Recurse -Force
      New-Item -ItemType directory -Path $mofFolder | Out-Null
      Set-Location $mofFolder | Out-Null
    3. Create a simple DSC Configuration section.  I would recommend to start with simple/out-of-box DSC modules to test your workflow first.
      Configuration MySite {
          param (
          Node $NodeName
              WindowsFeature IIS
                  Ensure = "Present"
                  Name = "Web-Server"
    4. Add a function call to the DSC Configuration function to generate the MOF file
      MySite -NodeName $nodeName
    5. Save as MySiteConfig.ps1 in the DSC/Config folder created earlier
  4. Update Vagrantfile
    1. Configure winrm as the communications mechanism in the Vagrantfile and also forward any ports necessary to test your configuration.  In my case I forwarded the ports RDP (3389) and IIS (80, 443)
      config.vm.communicator = "winrm" "forwarded_port", host: 33389, guest: 3389 "forwarded_port", host: 8080, guest: 80 "forwarded_port", host: 4443, guest: 443
    2. Create a shell provision script that will call your DSC configuration script and generate the MOF file
      config.vm.provision "shell" do |s|
          s.path = "DSC/Config/MySiteConfig.ps1"
          s.args = ["localhost", "C:\\vagrant\\DSC\\MOF"]
    3. Add an inline-shell provision script that starts the DSC configuration based on the newly created MOF file
      config.vm.provision "shell" do |s|
          s.inline = "Start-DSCConfiguration -Path C:\\vagrant\\DSC\\MOF\\MySite\\ -Force -Wait -Verbose"

      Note: Highly recommend using -Verbose when calling Start-DSCConfiguration as you can get a lot of great information, in case things don’t go as expected.

  5. Vagrant up and test the configuration worked by launching RDP and verifying the default IIS website is up and running by using a browser on the host machine
    C:\VMs\DSCVM\>vagrant up --provision
    C:\VMs\DSCVM\>vagrant rdp

    After your VM is running, go to http://localhost:8080/. You should see the default IIS page.

Using Third-Party DSC Modules

The true power of DSC comes with the large amount of DSC modules that are being published and shared everyday by Microsoft and the PowerShell community.  Many of these modules are being uploaded to the central repository: PowerShell Gallery.  In this section, we’ll leverage the package manager PowerShellGet to install the xWebAdministration DSC module, that allows us to manage IIS Web Sites, App Pools, etc.

  1. Install xWebAdministration module.  We’ll create another provisioner script to take care of any DSC module dependencies for us (similar to how Berkshelf works with Chef, although we’ll just create a simple script to do it).
    In the Vagrantfile define the script prior to Vagrant.configure

    $dscModDepScript = <<SCRIPT
        Install-Module -Name xWebAdministration -Version

    Note: Get-DscResource is a nice way to confirm you’ve loaded all the necessary modules.  It’ll print all modules loaded on the Vagrant output screen. You only have to call it once.
    Call the script (prior to any DSC provisioner)

    config.vm.provision "shell" do |s|
        s.inline = $dscModDepScript
  2. Import the DSC module.  Now that we have a DSC module installed, we can start using their resources in our DSC configuration.
    Import it prior to the Node script block

    Import-DscResource -Module xWebAdministration
    Node $NodeName {...}

    Use any DSC resource from that module. In my case I’ll use the xWebsite DSC Resource.  In addition to this resource I’ll use the File DSC resource to copy any files/folders in the MySite folder to the default IIS website.

    File WebProject {
      Ensure = "Present"
      SourcePath = "C:\vagrant\MySite\"
      DestinationPath = "C:\inetpub\wwwroot"
      Recurse = $true
      Type = "Directory"
    xWebsite DefaultSite { 
      Ensure = "Present" 
      Name = "Default Web Site" 
      State = "Started" 
      PhysicalPath = "C:\inetpub\wwwroot" 
      DependsOn = @("[WindowsFeature]IIS", "[File]WebProject")

    Note: I’ve hard-coded the SourcePath above.  Ideally you’d pass this as an argument to your configuration, which I’ve done in the final script at the end of the article.

  3. Develop an index.html or copy your favorite static html site to the MySite folder
    <head><title>DSC Site</title></head>
    <body><h1>Hello DSC!</h1></body>
  4. Run vagrant provision again (or vagrant up if your vagrant VM is down/destroyed)
  5. Test your new site (If using my sample, you should see Hello DSC! by launching a browser on your host machine and going to http://localhost:8080)

Food for thought and final files

  • As I stated earlier, I used this setup to develop/test custom DSC modules.  The dscModDepScript in the Vagrantfile below shows how to install your custom DSC modules (Get-DscResource would take care of loading them as well).
  • The DSC  configuration file might need a bit of tinkering if you were to use it with something like Microsoft Release Management (this file is optimized to work with Vagrant)
  • The latest version of Vagrant addressed some issues around passing arguments to PowerShell scripts as well as RDP.  Highly recommend that you install the latest version, as the sample Vagrantfile would not work.  In addition I’ve used all 3 types of shell provisioning (inline, inline+variable, path) and passed arguments to it (as an example of how this works with Vagrant)


DSC Config File: DSC/Config/MySiteConfig.ps1