April 3rd, 2015

Machine Factories with Windows Part 1: Local Development with Vagrant, Packer and DSC

Automating Windows development environments with Packer, Vagrant and PowerShell DSC.

— Matthew Fellows —

The merits of continuous delivery (CD) in this highly disruptive and innovative age is unquestionable; disrupt, or be disrupted. For several years now, the Linux world has dominated in this landscape and continues to press forward at the speed of thought (read: Docker), however doing this is now quite achievable on Windows.

In a recent YOW! Nights tour of Australia Warner Godfrey, a fellow DiUS colleague, and Terrence Benade an Agile Architect at SEEK and I shared our experiences in continuously delivering applications on Windows platforms at SEEK, and whilst we have shared the video and our slides, there is only so much detail you can pass on in these forms.

In this 3 part series, I will take you through practical examples of my component of the talk – Machine Factories – with real source code, so that you can take these concepts and practices with you to your next CD project.

  1. Creating a local Vagrant box for development
  2. Creating Build and Production Servers
  3. Tying it all together: Deployment to AWS with Terraform

Update: 2016/02/19

Packer merged the plugins from Packer Windows Plugins project meaning Windows gets first class support. Huzzah! Relevant code has been updated in the demo repo.

Getting Started

Before we start, you will need a few things to complete the tutorial:

  1. Git – so you can download the code and play along
  2. Vagrant – Development automation tool.
  3. VirtualBox: a virtualisation platform to run development machine images.
  4. If you are on Windows, you will need to install Cygwin and ensure openssh and rsync (3.1.x) have been installed and are on the path.
  5. Packer: used to build and provision machine images (ISOs, Virtualbox, AWS AMIs, Docker containers, Vagrant boxes etc.)
  6. Packer Windows Plugins: specialised plugins to make Packer play nice with Windows. (see update)
  7. A set of AWS Credentials for your account (Consider using credulous to manage your keys).
  8. A subnet and optionally, a VPC, to load the application into.

If you are on Windows, this gist will install Vagrant, Virtualbox and the Packer setup you need.

The Application

Avoiding the token “Hello World”, we’re going to build a basic URL shortening service with a twist and deploy it to IIS. Yes, we could build it on Mono as a self-hosted application and deploy it to Linux on a < 20mb Docker container – but that would spoil all of the fun – there is a heap of utility in applying this to applications that may not be candidates for this treatment.


Part 1: Setting up a local development environment with Vagrant

I’m a big fan of the 12 Factor App concept of environment parity. Repeatable, isolated development environments ensure code that works locally works the same in Prod, and it saves us the daily battle of ensuring our neighbours setup matches our own.

This is where Vagrant comes in, it is a tool to “Create and configure lightweight, reproducible, and portable development environments”. We will use Vagrant to run our local development environment and provision it with PowerShell DSC – a configuration management tool for Windows. The end result will be a repeatable, isolated development runtime (a Virtualbox virtual machine) running Windows that will be configured from the same recipe we configure all of our other environments.

Let’s get started. To begin, clone the accompanying tutorial so you can follow along.

1. Creating a Vagrant Machine with Packer

Doing things by hand is great, if you don’t have a life, so from the outset we are going to automate everything we can – including the setup of Vagrant. For this, we use a tool called Packer, a tool to create machine images from a single source configuration. As Packer expects to provision via SSH, we will use a number of community built Windows plugins to provision via native WinRM.

Packer uses a simple JSON DSL, and we will use the following to create our Vagrant box in two steps.

In the first step, we shall create a VirtualBox Windows 2012r2 image with a Vagrant user and the guest additions installed and then pop that aside for later use. This is good practice when you are iterating on a new machine, as booting and installing the OS can be quite time consuming and it’s likely you will make some mistakes in your machine provisioning. In the second step, we take the Base image as an input and then provision it to create a development machine image.

Here is our Packer file, you can see the two builders corresponding to the two images we were just referring to – basebox-vbox and devbox-vbox:

You should be able to get a rough idea of what we are doing by reading through this file: We are installing Chocolatey and 7zip, running a number of provisioning scripts (installing chocolatey packages, rsync, etc.) and then compressing the disk to make the resultant Vagrant box as small as possible, and then finally we generate a Vagrant box from the image. Take this opportunity to read through the scripts that are being run in order to get a sense for what’s happening.

Let’s create the box, shall we?

Now would be a good time to get some coffee – the ISO download will probably take a while!

If all goes well, you should have a file in the working directory with the name “machinefactory-api-virtualbox-1.0.0.box”. All we need to do now is add it to Vagrant’s local box registry and then we can use it!

2. Configuring Windows with PowerShell DSC

Now that we have a Vagrant box with all of the software we need on it, we need to configure it for our application to run. This is where DSC comes in: we can use it to easily, and declaratively set the desired state of our server including Windows Features, Firewall rules and Web Applications. The out-of-the-box resources are lacking, so we are pulling in the feature-rich SEEK modules to do some of the heavy lifting.

There are two main bits here – our Configuration file (MyWebsite.ps1) and a custom DSC Module for our Application:

Again, DSC is fairly readable when kept simple. You can see that we are opening the firewall for port 80, ensuring IIS and Application Development features are installed and then configures an AppPool & Website for our site to run under, including a host entry.

We will see this in action when we run Vagrant shortly, but the great thing here is that we can run DSC as many times as we like without breaking things, meaning iterating on a new configuration is easy.

3. Running Vagrant

Now all we need is a Vagrant configuration file to coordinate spinning up our VirtualBox image & running our DSC scripts:

Here we create a private network on the IP, running on hostname urlsvc.dev and we use rsync to share the current working directory into c:\vagrant on the Windows guest machine. We need to use rsync here as IIS does not work with VirtualBox shared folders. We then use the vagrant-dsc plugin to run our DSC scripts from Vagrant. This is really neat, because when we want to change our DSC configuration we can simple modify the files and then issue a vagrant provision –provision-with dsc to re-run the DSC provisioner only.

Now all we need to do is run vagrant:

This will import our new development machine image, provision it with DSC and sync the working directory into the guest – this means you can use Visual Studio, Xamarin or whatever to author your code and it will run remotely in your VM. We have also installed the Visual Studio remote debugger and opened up port 4018 so you can debug remotely. Nice.

You should now be able to navigate to http://urlsvc.dev from your host or guest machine, and hit our URL shortening service!

NOTE: we need to run vagrant rsync-auto to continually sync the working directory. This is only required when rsync is used as the shared folder type.

Wrapping up

Using Packer, Vagrant and DSC we have managed to automate our entire development environment and have laid the foundations for the rest of our CD pipeline. Packer has bootstrapped a Windows machine from scratch and turned it into a Vagrant VirtualBox image that can be re-used by others. We used Vagrant to run this image, and configure our development runtime with DSC and can now develop in a clean, isolated and repeatable environment.

In the next installment, we will use Packer to build our Production and Build servers in Amazon from the same source configuration.

  • Simon

    Great post Matthew. I was wondering if you ran into any issues pointing your IIS website to the rsync’ed directory? I can’t get a test ASP.NET site to work when it points to the rsync’ed webroot. The error is pasted below. If I copy the webroot so it’s outside of the folder that is rsync’ing then it works fine.

    Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))

    Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

    Exception Details: System.UnauthorizedAccessException: Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))

    ASP.NET is not authorized to access the requested resource. Consider granting access rights to the resource to the ASP.NET request identity. ASP.NET has a base process identity (typically {MACHINE}ASPNET on IIS 5 or Network Service on IIS 6 and IIS 7, and the configured application pool identity on IIS 7.5) that is used if the application is not impersonating. If the application is impersonating via , the identity will be the anonymous user (typically IUSR_MACHINENAME) or the authenticated request user.

    To grant ASP.NET access to a file, right-click the file in File Explorer, choose “Properties” and select the Security tab. Click “Add” to add the appropriate user or group. Highlight the ASP.NET account, and check the boxes for the desired access.

    • Simon

      I’ve tried permissions, app pool identity etc

      • http://www.onegeek.com.au Matt Fellows

        Hmm that is weird – this is part of the problem rsync was meant to fix in the first place! Are you running the VM from a Windows host or a Linux/Mac host? It’s possible that the underlying rsync is not copying the correct file permissions. I recall having to run a Powershell script previously to correct the parent folders permission bits on boot, but that was a long time ago.

        • http://www.onegeek.com.au Matt Fellows

          Also, I would check to ensure the version of rsync on the guest and host machines match up. Semantic versioning may not be your fried here either I’m afraid – I had a micro version change break this once.

          • Simon

            Hey Matt,

            Thanks so much for your swift response. Your post was obviously a long time ago so I really appreciate you trying to help out!

            Just to let you know, I found a workaround. I logged on to the guest, opened advanced security settings for the web root, enabled inheritance and replaced permissions of all child objects (using the checkbox).

            My only question is, how do I configure rsync to set this by default?

          • Simon

            Hi Matt, I ended up putting this on hold for the time being and switched to using SMB. I couldn’t find a way with the rsync__args to set the permissions to inherit from the parent folder. If I come back to this and find a solution I’ll post it here.

          • http://www.onegeek.com.au Matt Fellows

            Sorry for the delay Simon. You have found the right place to tweak the settings, however I do recall a particular scenario where this was insufficient (I couldn’t dredge up any old code to prove it though!). If you are running a Windows host, SMB should work nicely. How is the performance?

          • Simon

            Another day, another update ;) We’re continuing down the path with using SMB shares but on the sidelines I was curious to get rsync working. I did find the following syntax seemed to work by preserving the file permissions on the guest so that they were inherited from the parent folder (C Drive).

            config.vm.synced_folder “.”, “/vagrant”, type: “rsync”, owner: “vagrant”, rsync__args: [“–verbose”, “-r”, “-l”, “-t”, “–no-p”, “-g”, “-o”, “-D”, “–delete”, “-z”, “–copy-links”]

            However, as soon as I run vagrant rysnc-auto, those permissions were hosed. It’s as if rsync-auto ignores any of the args set in the Vagrantfile. I’m going to park this for now as the SMB solution seems to be working fine so far and performance hasn’t been a problem. Only time will tell if this is a robust solution though. Thanks for your support and for your inspiration again.

          • http://www.onegeek.com.au Matt Fellows

            Thanks for having this public discussion, hopefully we as a community will get to the bottom of this! In the next few weeks I should have the opportunity to look again at this and I will post any findings I have into the repo and this article. Glad to hear you are moving forward though!

  • Alexey Auslender

    Hey there ,I am getting “rsync” could not be found on your PATH. Make sure that rsync
    is properly installed on your system and available on the PATH while trying vagrant up,I am trying to create your sample on windows 8 Pro.
    I have tried to install rsync using Cygwin but it didn’t help,placing rsync files in to the working directory didn’t help either.
    Please help!

  • David Bosley

    Great article – I’m trying to get the local dev box step running:

    packer build -only basebox-vbox vagrant.json

    It all goes well up to a point where a Network Discovery prompt opens during the Windows 2012 setup. That causes everything to stop and eventually timeout. Any ideas on how to add that to the answer file or other work around?


    • http://www.onegeek.com.au Matt Fellows

      I’ve never seen that before. This line (https://github.com/mefellows/windows-machine-factory-tutorial/blob/master/machine-factory/answer_files/2012_r2/Autounattend.xml#L275) should auto-select this for us, assuming you still have it. If you manually disregard the notice things do things progress? I suppose you could automate the networking in the Autounattend.xml (e.g. https://technet.microsoft.com/en-us/library/ff715739.aspx). Also, take a look at the keys in https://technet.microsoft.com/en-us/library/ff716016.aspx. Hopefully this gets you back on track!

      • David Bosley

        It does choose the home network it seems, but it pops up asking about finding stuff on the network. The Packer command waits for it but eventually times out whether I answer the question either way or not. Haven’t found anything yet on this – but I’m new to creating images so I’ll keep plugging on it for a bit. I’m using the exact vagrant.json file – I even recopied it from your answer and tried it again to be sure. There obviously has to be something different about my network, software versions or how I installed the software but haven’t found it yet.


        • http://www.onegeek.com.au Matt Fellows

          Ahh, I’m pretty sure that’s not the issue, but I can see why you might think that. Can I suggest you run Packer with verbose logging enabled and review the output (pop into a Gist or somewhere online for me)? The good news is it’s likely that it’s an authentication problem or something simple.

          • David Bosley

            Hey Matt – sure appreciate the time to point me in the right direction. Here is the packer log with PACKER_LOG=1: https://gist.github.com/davidbosleyhp/3702b5a656ae64bf0e2f

          • http://www.onegeek.com.au Matt Fellows

            This tells me that at it is almost certainly a WinRM or user/pass problem (https://gist.github.com/davidbosleyhp/3702b5a656ae64bf0e2f#file-packerlog-txt-L228-L233). You can see the 401 response code, which tells me WinRM is up (the method in which Packer communicates to the machine) but can’t auth. Did you change any of the passwords from the default in my repo? (“[email protected]″)

          • David Bosley

            Aha you nailed it. The vagrant.json file in github has a hardcoded path for the ISO to a local directory which of course I don’t have. So I had replaced the entire vagrant.json file with the code from the vagrant.json gist above. But there are several problems with that gist. The password value, as you pointed out, is incorrect. In addition, even with the correct password it does not complete without errors. Once I went back to the vagrant.json file from the git repository and then just replaced the iso_url and iso_checksum elements with the ones above it completed.

            As a side note I did notice that that same Device Discovery dialog popped up in the virtual machine, but it was completely benign. The build completed fine.

            Thanks again for the help!

          • http://www.onegeek.com.au Matt Fellows

            So glad to hear – great news!