Windows: Windows 2012 Sysprep for Vagrant readiness

Many developers like to use Vagrant from HashiCorp to standardize the workflow of virtual machines: creation, running, destroying, taking snapshots, etc..

Usually Vagrant is used for Linux hosts, but it also works with Windows as long as you prepare the template properly.

In a previous article I went over the detailed steps to create a template image for Windows 2012 server using Sysprep.  Consider this the second part in that series, where Vagrant has specific additional requirements.

Prerequisites

Go through my previous article and run the instructions in the beginning  sections:

  • Download Windows 2012 server
  • Create a base Windows instance in Virtualbox
  • Customize the Windows install

Run Sysprep

Read the section “Run Sysprep” in the previous article , but instead of using the unattend.xml provided there, use an expanded version called “unattend-vagrant.xml” that I have on github.

You will need to copy unattend-vagrant.xml and copy it to c:\windows\system32\sysprep, and make sure to rename it locally to “unattend.xml”. Then run sysprep just like shown in that section.

This expanded version has additional steps including:

  • Creating a local user named ‘vagrant’ with a password of ‘vagrant’
  • Marking the vagrant user an Administrator with a password that does not expire
  • Enables Windows remoting (WinRM) so Vagrant can remotely administer the guest OS

We enable WinRM because similar to vagrant on Linux using ssh to remotely control the guest OS, it needs a mechanism for doing the same with Windows.

You can skip this step, but if you anticipate you will want the ability to manage this host using ssh and the ‘vagrant ssh’ command, then jump down to this section at the bottom.

Create a snapshot

Just like the previous article, we will used linked clones to maximize the speed at which we can provision these Windows guest OS.

Take these steps from the VirtualBox GUI to create a parent snapshot that will serve as the basis for any number of linked clones:

  • Select “w2k12base” from the VM list
  • Click on “Snapshots” from the right of the top menu bar
  • Click on the camera icon to take a snapshot
  • In the popup dialog, use “sysprep-ready” as the name, hit OK

Create Vagrant Box

Now we have a VirtualBox, but we don’t have a Vagrant box.

We need to export this template VM as a local file named “package.box”.  Then we tell vagrant to add “package.box” as a vagrant box named “w2k12base-sysprep-ready”.

$ vagrant box list
ubuntu/trusty64 (virtualbox, 20170918.0.1)

$ vagrant package --base w2k12base
==> w2k12base: Exporting VM...
 ==> w2k12base: Compressing package to: /data/vms/vagrant/package.box

$ vagrant box add w2k12base-sysprep-ready package.box -f
==> box: Box file was not detected as metadata. Adding it directly...
 ==> box: Adding box 'w2k12base-sysprep-ready' (v0) for provider:
 box: Unpacking necessary files from: file:///data/vms/vagrant/package.box
 ==> box: Successfully added box 'w2k12base-sysprep-ready' (v0) for 'virtualbox'!

$ vagrant box list
ubuntu/trusty64 (virtualbox, 20170918.0.1)
w2k12base/sysprep-ready (virtualbox, 0)

Use Vagrant to create guest OS

The last step is to create a Vagrant file and then have Vagrant bring up the host.

$ mkdir clone1
$ cd clone1
$ vagrant init

This will create a file named ‘Vagrantfile’ which you should modify to look like below.  Here is a link to github if you want to download it.

# for linked clones
#Vagrant.require_version ">= 1.8

vmname = 'clone1'
boxname = 'w2k12base-sysprep-ready'

Vagrant.configure(2) do |config|
  config.vm.hostname = "#{vmname}"
  config.vm.box = "#{boxname}"

  # must have for Windows to specify OS type
  config.vm.guest = :windows

  # if using host only network
  #config.vm.network 'private_network', type: 'dhcp'

  # winrm | ssh
  config.vm.communicator = "winrm"
  config.winrm.username = "vagrant"
  config.winrm.password = "vagrant"
  config.ssh.insert_key = false

  # virtualbox provider
  config.vm.provider "virtualbox" do |v|
    v.name = "#{vmname}"
    v.gui = true
    # use linked clone for faster spinup
    v.linked_clone = true
    v.customize ["modifyvm", :id, "--memory","1024" ]
    v.customize ["modifyvm", :id, "--cpus","1" ]
    # dynamically set properties that can be fetched inside guestOS
    v.customize ["guestproperty", "set", :id, "myid", :id ]
    v.customize ["guestproperty", "set", :id, "myname", "#{vmname}" ]
  end

end

Finally, run ‘vagrant up’ to instantiate the Windows guest OS.  You should see the following output with the host OS communicating with the guest OS using WinRM, and a single reboot in order to accomplish the rename to ‘clone1’.

$ vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Cloning VM...
==> default: Matching MAC address for NAT networking...
==> default: Setting the name of the VM: clone1
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
 default: Adapter 1: nat
==> default: Forwarding ports...
 default: 5985 (guest) => 55985 (host) (adapter 1)
 default: 5986 (guest) => 55986 (host) (adapter 1)
 default: 22 (guest) => 2222 (host) (adapter 1)
==> default: Running 'pre-boot' VM customizations...
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
 default: WinRM address: 127.0.0.1:55985
 default: WinRM username: vagrant
 default: WinRM execution_time_limit: PT2H
 default: WinRM transport: negotiate
==> default: Machine booted and ready!
==> default: Checking for guest additions in VM...
==> default: Setting hostname...
==> default: Mounting shared folders...
 default: /vagrant => /data/vms/vagrant/clone1

When this is complete, you will see a Windows machine home screen, ready for you to press Ctrl-alt-del and login using either the ‘Administrator’ or ‘vagrant’ user.

After you login to the Windows guest OS, you can verify that the “myid” and “myname” values set in the Vagrantfile are available within the guest OS.

> cd C:\Program Files\Oracle\VirtualBox Guest Additions

> VBoxControl.exe guestproperty enumerate | findstr my

Name: myid, value: c3f14df9-0253-400c-9ef1-e87c57d300aa, timestamp: 150721058532
2166000, flags: <NULL>
Name: myname, value: clone1, timestamp: 1507210585404804000, flags: <NULL>

These properties are just examples of passing simple values from host to guest.  Vagrant has many provisioner types, in addition to systems such as Ansible, Chef, and shell, file provisioners can copy entire files and folders from host->guest.

Vagrant ssh

If you were on a Windows host OS running vagrant, you could now get a console communication to the guest OS by using ‘vagrant powershell’ because we enabled winRM.

However, this an Linux centric blog, so navigating through the guest is going to be most native by enabling ssh to the guest OS. We can have Vagrant use ssh as long as we install OpenSSH on the Windows template before Sysprep is run.

To install OpenSSH on Windows, let’s use the Powershell script provided in the Hashicorp Packer project, openssh.ps1.  Copy this file to the guest OS, and run:

> powershell -executionpolicy bypass -f openssh.ps1

Then in order to use public/private key authentication instead of passwords, copy the Vagrant public key “vagrant.pub” found on their github to the guest OS as “c:\users\vagrant\.ssh\authorized_keys”.

Every install of Vagrant comes with the default private key (found at~/.vagrant.d/insecure_private_key), and so with the private key on the client ssh side and the public key on the guest OS side, authentication can be accomplished without needing to specify a password.

We leave the Vagrant file alone because we still want the config.vm.communicator to use winrm so that it can natively change the VM name and other initialization.

After you bring up the clone based on the new template with OpenSSH, you can bring up a console from the host OS using ‘vagrant ssh’.

$ cd clone1
$ vagrant up
$ vagrant ssh
Microsoft Windows [Version 6.3.9600]
(c) 2013 Microsoft Corporation. All rights reserved.

C:\Program Files\OpenSSH\home\vagrant>exit

 

 

REFERENCES

https://www.vagrantup.com/docs/cli/package.html

https://abhishek-tiwari.com/creating-a-new-vagrant-base-box-from-an-existing-vm/

https://askubuntu.com/questions/198452/no-host-only-adapter-selected (how to add host-only network adapter)

https://forums.virtualbox.org/viewtopic.php?f=8&t=55766 (use host networking instead of NAT with port forwarding)

VBoxManage showvminfo myserver | grep 'Rule' (view port forwarding rules from host OS)

https://github.com/joefitzgerald/packer-windows (easy ssh install on Windows)

https://www.virtualbox.org/manual/ch08.html#vboxmanage-guestproperty (virtualbox guestproperty usage)

https://superuser.com/questions/846964/how-to-add-a-host-only-adapter-to-a-virtualbox-machine-via-vagrant-file-config

https://www.engineyard.com/blog/building-a-vagrant-box (openssh and insecure_private_key)

https://www.engineyard.com/blog/building-a-vagrant-box (openssh ~/.ssh/authorized_keys)

https://github.com/hashicorp/vagrant/issues/5186 problems with pub/priv key generation

http://station.clancats.com/3-vagrant-settings-you-should-check-out-to-optimize-your-vm/ (customize virtualbox vagrant settings)

https://github.com/hashicorp/vagrant/blob/9c299a2a357fcf87f356bb9d56e18a037a53d138/plugins/providers/virtualbox/config.rb (list of virtualbox provider special attributes, e.g. :id, :name)

https://www.simonholywell.com/post/2016/02/intelligent-vagrant-and-ansible-files/ (external yaml feeds vagrant file and and ansible provisioning section)

http://www.hurryupandwait.io/blog/vagrant-powershell-the-closest-thing-to-vagrant-ssh-yet-for-windows (vagrant powershell)

 

$ vagrant destroy -f 

$ vagrant box remote w2k12base-sysprep-ready -f

wget --no-check-certificate \
https://raw.githubusercontent.com/hashicorp/vagrant/master/keys/vagrant.pub

# convert vagrant private key to ppk if you want to use passwordless putty client
$ sudo apt-get install putty putty-tools
$ cd ~/.vagrant.d
$ puttygen insecure_private_key -o vagrant_private_key.ppk