Have you ever felt the need to have a test environment set up on your personal computer? Or perhaps to have an automated way to set up an environment in your Continuous Integration workflow? If it’s done manually, this is a tedious and I believe often error prone activity. But if it’s automated through for example my script demonstrated below, you can have your new virtual machines starting in just seconds.
I have made a PowerShell Module that contains a function that creates virtual-networks and computers in Hyper-V according to an XML-configuration. It’s idempotent enough so that it does not try to create something that is already there.
The reason that I choose Hyper-V as hypervisor is just that it’s something that is accessible for most people without any extra cost. But I believe that it should not be hard to make the module to work with another, for example Azure, if you prefer to have your environments there instead.
When developing, I prefer to have my virtual machines locally because it’s much faster. A Windows Server 2012 R2 Core instance will run with 512MB of RAM and requires about 6GB of disk space. This makes it not much of a deal to run several virtual machines even on an laptop.
In this post, I will use Windows as example since it’s the technology that I’m most familiar with. But this technique should work just as good with any other operating systems that run on Hyper-V.
Alternative Tool
Microsoft already has a similar tool called PowerShell Deployment Toolkit, primarily for deployment of System Center 2012. Instead of using it, I preferred to write my own because I wanted something more lightweight.
Setup Windows Server Templates
I prefer to work with Windows Server Core instances for two reasons. First, since they do not contain any GUI they consume less RAM and disk-space. And second, if you ever feel a need to RDP to a test server, I think it’s a sign that you have failed to fulfill Continuous Integration. Deployment of both applications and OS-configuration are done best with a tools like PowerShell DSC, Chef or Puppet. One should not have to log on to a server for deployment reasons.
To speed up creation of virtual machines one can create templates of already preconfigured instances of Windows. Then, you do not have to do the installation of the operating system manually, nor whatever other steps you normally do when creating a new virtual machine.
To create a template one should not just make a copy of an other virtual machine, but instead anonymize it first. You have to remove unique identifiers, such as for example SID and computer name. Microsoft provides a tool for this purpose called Sysprep. For details about what it removes, please see this blog post by Michael Pietroforte.
You can run Sysprep by double clicking its executive, or by invoking it through command line in the following manner.
1 | c:\windows\system32\sysprep\sysprep.exe /oobe /generalize /shutdown |
After Sysprep has run on a computer, and when first booting it up you need to fill in some details such as locale, administrator account password and license key.
Automated Installation
Would it not be nice if one could create virtual machines from templates without having to enter any details? Yeah, Microsoft has thought of that. There’s this feature called answer files, which enter the settings for you. You can use answer files to automate normal installations of Windows, but they also work together with Syspreped images. One way of how to make Windows load an answer file is to place it in the folder C:\Windows\Panther\
with the name unattend.xml.
Once the bootup configuration is complete, the installer process removes sensible data (such as password and product key) from that file by replacing them with *SENSITIVE*DATA*DELETED*
.
There’s an official tool from Microsoft for creating answer files which is named Windows System Image Manager (SIM), and it’s part of the Windows Assessment and Deployment Kit. Personally, I think that this application is a mess. It’s way to complicated to make me even want to try to use it.
The site Windows Answer File Generator comes to rescue. From there you can generate answer files for all Windows operating systems, but with the limitation that it does not offer the same flexibility as Windows SIM. For example, it can not add instructions for automatically joining a domain.
In the answer file I used preparing this post, I inserted underscore tokens in place of some fields. The function in the PowerShell Module replace them when creating virtual machines from that template.
To create the unattend.xml file on your template VHDX you can mount it with PowerShell, for example in the following way.
1 | Mount-DiskImage C:\Hyper-V\Templateswin2012r2_template.vhdx |
Alternative Way to Create Template VHDX
When creating your template, instead of installing the operating system and runing Sysprep manually you can use a tool. I have found the TechNet Scriptcenter Convert-WindowsImage.ps1 which can create sysprepped VHDX images from .iso-files.
I find this script to be too massive to use, and instead prefer to do the template myself. Creating templates is not something that I do often, so I do not have so much to gain from automating that activity.
Invoking the Hyper-V Environment Templating Function
To run the PowerShell function which creates the virtual machines you can either install the module, or just copy the .ps1-file and run it as a script.
A PowerShell Module is to some extent some functions that someone has packaged together. PowerShell monitors the folders in the environment variable PSModulePath
for modules, and automatically imports them right when they you need them.
If you choose to install it as a module you have to rename the .ps1-files to .psm1, or you can follow the instructions in my PowerShell Repo Readme under Getting started. I prefer to have my module scripts as .ps1-files when I develop them. Once I decide to install a new version of a module, I have them renamed in the install process.
To invoke the Hyper-V Environment Templating function, type New-VMFromConfig -Path C:\PathTo\MyConfig.xml
. I you like you can use the -Config
parameter instead and pass in an [xml]
object.
The XML content might look something like this:
1 | <Config> |
New virtual machines are created under the <VMs>
root attribute in a folder that matches the name of the virtual machine.
The supported VHD tags are <MoveVhd path="...">
, <CopyVhd path="...">
, <DvdDrive path="...">
, and <Vhd size="...">
. The latter will create a new disk of the specified size. All three VHD tags can have a filename attribute which if set, specifies the name of the VHD file in the virtual machine folder.
Both the <CopyVhd>
and <MoveVhd>
supports the <ReplaceContent>
tag which makes the answer file underscore token update possible. An advantage of moving a template VHD over copying it, is that it’s faster. The downside is that the template VHDs will no longer be available for future use. But if you create copies of your template VHD in advance you can overcome this. Since the VHD size of a clean install of Windows Server 2012 R2 Core is so small, space should not be an issue.
The <MoveVhd>
path attribute supports wildcard matching. It picks the first file that matches the expression.
Tricks and Tweaks
Installing Windows Server 2012 R2 AD, DNS and DHCP
Name resolution and IP address distribution in Hyper-V is poor without a dedicated DNS and DHCP within the network. If you run your virtual machines on an external network, all is fine. But if you use internal or private networks you have to host these services yourself. Your would normally also need to connect to an Active Directory.
To solve these problems all in one you can configure an instance of Windows Server 2012 R2 to act as both AD, DNS and DHCP-server.
I found a blog post written by Ivo Bruinewoud which describe an easy way to do this with PowerShell. Here is a slightly modified version:
1 | # Static IP |
The PowerShell command to join a client machine to a domain is:
1 | # Execute on the client machine |
Switching Between GUI and No GUI in Windows Server
Sometimes it will be a pain to try to configure something that you know is easy if you have a GUI, but which isn’t when you are on a Windows Server Core instance. In those cases you can use Install-WindowsFeature
to install the GUI, just to make your configuration, and then uninstall it again with Uninstall-WindowsFeature
when you’re done.
If you invoke the Get-WindowsFeature on a Core instance, you will find the GUI related features looking like this:
1 | Display Name Name Install State |
And if invoked on a full installation, they look like this:
1 | Display Name Name Install State |
If a Windows Feature is Available, or Removed and you have got access to the internet, then you can install it without having any installation media. If that is not the case, you have to insert the .iso-file with the same version of the operating system in the DVD drive and specify the Source
parameter. Follow these instructions for guidance.
Setting up PowerShell Remoting
When you deal with servers that has not got the features Server-Gui-Shell or Server-Gui-Mgmt-Infra activated, I think that it’s cumbersome to use remote desktop or the Hyper-V Virtual Machine Connection to connect to them. A more elegant solution is to use PowerShell Remoting, although it can be a bit hard to configure when not connected to a domain, such as in a workspace environment or when your try to connect to a machine from outside a domain. When connecting to a domain in an virtual isolated network from your host machine this will essentially be the case.
To get PowerShell Remoting to work on my Windows 8.1 laptop, I had to run the following commands:
1 | Enable-PSRemoting ?Force -SkipNetworkProfileCheck |
Depending on the network confirmation of your host computer, you might not need the -SkipNetworkProfileCheck
parameter.
Setting up Name Resolution to Work with Virtual Network
To make name resolution between your host and virtual machines to work, they need to be on the same subnet. Network Discovery is by default rejected by the windows firewall. You can enable it with the PowerShell command Enable-NetFirewallRule NETDIS-NB_Name-In-UDP
.
I like to have my machines answering to Ping. When you are already at it, enable the firewall rule FPS-ICMP4-ERQ-In
as well.
If you set the DNS address of the network adapter to be that of the DNS server, you can use the DNS server for name resolution. But since your host machine is outside the domain, you have to use the complete domain address. For example, frontend.classon.eu
.
Also, PowerShell Remoting will not work if the IP address of the host is on another subnet than of the virtual machines.
Future Improvement
It would be nice to be able to have virtual machines being able to automatically join a domain.
For this to be possible, I have to solve two problems. First, I have to update the answer file to also include instructions for how to join a domain. Either I have to click through the Windows SIM application, or find someone else that has managed to do it and copy some lines from their xml-file. TechNet has some sample answer files for older operating systems that seams interesting. Hopefully they will also work for Windows Server 2012 R2.
Second, I need to make a PowerShell function that can start virtual machines. When creating a new environment with network isolation there is obviously no domain controller already present. Hence there might be an error if they start simultaneously. The function can solve this by waiting for any DCs to boot up before it starts any other virtual machines.
As an alternative to using answer files for domain joining, I might consider to use PowerShell instead. What I have in mind is to wait for a DC to start up, and then invoking the domain join on the client machines through a remoting session.
I will try to address this in a blog post sometime in the future.