Sunday, January 13, 2013

Building a Hyper-V Test Lab on Windows 8 – Part 1

This is the first part of a multi-part blog article on a set of scripts to create VMs using Microsoft’s Hyper-V on both Windows 8 and Server 2012.

Note: this page is being updated along with others in this set as I slowly get all this documented and published.


For many years, I’ve used virtual machines heavily. The articles, courses and books I write rely on VMs for their content. I’ve lost count of the number of domains I’ve built, re-built, broken, fixed over the years. 

The only downside is that I was regularly building and re-building the systems by doing creating a new VM and installing the OS from scratch. This was slow and tedious as well as somewhat error prone.

What I needed was a good, reliable system of creating sets of VMs and configuring them. The base set of services I want to be able to install and use was not that long: the OS (with AD, ADCS, DHCP, DNS, File Services), SQL, and Exchange.

Now that I have these scripts working, or nearly so, I am amazed at how much easier it is to create a set of VMs for particular purpose (e.g. write an article, create a class, build a demo for a talk). I am also finding others that have similar needs. I hope others find these scripts useful!

Building VMs Using Scripts

In the autumn of 2012, I, along with super-star MVP colleague Niklas Goude, started running PowerShell Geek Weeks in Stockholm. A lot of fun – 5 days of going as deep as we can with PowerShell. A key part of the Geek Week adventure is for the delegates to build their own environment AND to be able to take away the tools to re-create this environment at home.

I spent quite some time building some basic scripts – and encountered a lot of dead ends along the way. The first time out was a limited success – limited in that some pieces did not fit together well but a success in that it taught a lot of cool PowerShell features and showed us where we needed to do more. It also helped me to refine the real objectives the tools had to meet. Thanks goes to Mikael Nystrom for helping me with a good starting Unattend.XML!

Objectives of the VM Build module

My main objectives of these build tools were:

1. To enable someone with fairly limited PowerShell skills (or greater!) to use the the scripts simply and flexibly in the classroom and later.

2. To enable building out of a small farm of up to say half a dozen servers with minimal effort.

3. To enable extensibility, in that one could add more scripts to refine the configuration (e.g. adding a script to implement a failover DHCP).

4. To encode things like domain/forest names, user ids, passwords and IP address information to a given standard but make it not too difficult to change that.

5. To avoid the user having to use too much of the new automated setup approach.

6. To keep the use of disk space to a minimum.

The Architecture

The basic architecture of what I am trying to build is shown here:



The idea is simple – a single reference, parent, disk with a number of separate VMs based on differencing disks. This approach hits objective 6, and enables a simple solution to objective 4.

The Scripts

The key scripts here are as follows:

  1. Create-ReferenceVHDX – this script creates a parent VHDX based on an image from the Install.WIM from a Server (or client) installation DVD.
  2. Create-VM – this script creates a differencing VHDX, based on the reference VHDX, and creates an updated Unattend.xml file, stores this on the differencing drive. The script then starts the VM which then installs Windows onto the differencing disk. You run this script to create the VMs and VHDXs for DC1, Exch1, SQL1, SRV1, SRV2.
  3. Configure-DC1-1 – this script turns the first VM, DC1, into a domain controller in a new forest.
  4. Configure-DC1-2 – this script configures DC1, post installation as a DC. This includes adding and configuring DHCP and creating some users in AD.
  5. Configure –DC1-CA – this script convers DC1 into an Enterprise Root CA, and creates a web responder, complete with SSL support for the /Certsrv application.
  6. Configure-Exch1 – this script configures the Exch1 VM, including installing Exchange 2008 on the Exch1 VM.
  7. Configure SQL1-1 – this script configures the SQL server VM, including installing SQL server itself.
  8. Configure-SRV1-1 - Configure-SRV2-1, these scripts create a pair of application servers, including adding all of the RSAT tools and IIS.
  9. Utility Scripts – these include Set-DVDDriveOnAll (sets a particular DVD/ISO to be in the DVD drive of all VMs), Set-LanguageOnAll – sets the administrator logon have a particular kb layout for all vms, Set-NetFrameworkCoreOnAll (adds the base .NET onto all systems – which is needed for PowerShell V2).

These scripts are now online and you can get them from


What’s Next?

There are several next steps:

  1. I’ll publish each script both to the PowerShell scripts blog ( as well as publishing details of each scripts here. IN publishing the details here, I’ll explain what each script is doing and why I did it that way. As ever, comments are welcome – and may cause further updates to the script set!
  2. I’ll update this page with pointers and anything updated as I complete this set of posts.
  3. Package these scripts up as a downloadable module. Right now, these are just a bunch of scripts and some XML – making a proper module out of it is high on my agenda.
  4. Do a better job of centralising the variables across the scripts – things like user name, password, domain name, etc. I had in mind an XML file with all the key stuff in just one place!
  5. Take feedback and refine. Comments on these scripts are most welcome.


With the release of Server 2012 and Windows 8, building a set of Hyper-V VMs is now easier than ever – and these VMs can easily be moved from server to client and back as needs dictate. The set of scripts I’ve created have helped me create lab environments quickly and easily and may help you too.

Comments most welcome!


Unknown said...

i can't believe that this comes true! :) I hope you will finish your scripts very soon. Maybe i can help you testing something for you from the Point of view of a "normal" skilled Admin.

The best way so far was the work from the guys of with their Hydration kits using MDT.

Please go on with your work and make us happy! :-)

Craig Martin said...

Looking forward to seeing the fruit from this! Specifically looking to see how PowerShell 3.0 and WF play here.

Unknown said...

Hi i have tried using these scripts on Windows 8, but hitting a wall when running create-vm.ps1.

Hit Line breakpoint on 'D:\v3\Create-VM.ps1:18'
[DBG]: PS C:\Windows\system32>> Create-VM -name "DC1" -VmPath 'd:\V3' -ReferenceVHD 'd:\v3\Ref2012.vhdx' -Network "Internal" -UnattendXML 'd:\V3\UnAttend.xml' -Verbose -IPAddr '' -DNSSvr -VMMemory 1gb
VERBOSE: Starting Create-VM at 03/18/2013 23:22:23
VERBOSE: Creating VM: [DC1]
VERBOSE: Path to VM : [d:\V3]
VERBOSE: Creating Disk at [d:\V3\DC1.vhdx]
VERBOSE: Added VM Disk [Microsoft.Vhd.PowerShell.VirtualHardDisk], pointing to [ReferenceVHD]
New-VM : The parameter is not valid. No switch can be found by given criteria.
At D:\v3\Create-VM.ps1:45 char:7
+ $VM = New-VM –Name $name –MemoryStartupBytes $VMMemory –VHDPath $VMDisk01.path - ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (Internal:String) [New-VM], VirtualizationInvalidArgumentException
+ FullyQualifiedErrorId : InvalidParameter,Microsoft.HyperV.PowerShell.Commands.NewVMCommand

Any thoughts?

Thomas Lee said...

@Tim - the error message is less than helpful. One thing I note is that the script and target folder is on the D: drive. Not sure if that is relevant. What I've done on my laptop is to put the stuff on the E: drive, but I've created a sym link from the C: so the script uses the C: but the OS just points to the E:.

To diagnose, set a break point at the line and look at the values set for $name, $vmmemory, $vmdisk01.path, $network and $vmpath. I suspect one of these may be empty or null and if so you'll need to work your way backwards. Ping me off line and I can always do a lynk desktop sharing session and help you debug this. Sorry the scripts don't just work - they do here! :-)

Unknown said...

Hi Thomas

Thanks for your suggestion. I managed to run debug mode (new to me) and tracked down the issue to being the -SwitchName $Network param. $Network was set to "Intenal" & I had not created an Internal Connection yet. Now that I have, script now works !! We live & we learn - thanks for your help


Unknown said...

Just one more question concerning the given switch name. At the very first lines of the script "Create-VM.ps1" there is the setting of the switch variable ($Network = "Internal",)
but I've found it only once after that in the part of
"# Create a New VM
$VM = New-VM –Name $name –MemoryStartupBytes $VMMemory –VHDPath $VMDisk01.path -SwitchName $Network -Path $vmPath
Write-Verbose "VM [$name] created

In the part of the script where all the VMs are created the switch name is given in cleartext, not as a variable (string value).
(Create-VM -name "DC1" -VmPath $path -ReferenceVHD $ref -Network "Internal" -UnattendXML $una -Verbose -IPAddr '' -DNSSvr -VMMemory 1gb)

So whatever I define in the settings section of the script is not used when creating the VMs and the script crashes. For example I have named my switch to "Windows 2012 Test Lab" and therefor have set $Network = "Windows 2012 Test Lab",. The script crashes because the give switch name "Internal" doesn't exist. It only works well if you definitely have named your switch Internal.

So in the creation part at the end of the script I replaced the fixed "Internal" with $Network for every VM and it now works perfectly for me.
Did I mess up anything by that?

Thomas Lee said...


Yes, I don't handle the switch very well. In all my work, I have an Internal switch defined. I should probably fix this (and it's on the list of things to fix).

Good Catch.

Unknown said...

Maybe to go one step furth and replace the fixed variable with an input box like
$Network = Read-Host "Enter the name of the Virtual Machine Network (Press [Enter] to choose Internal): "
if ($Network -eq ""){$Network="Internal"} ; if ($Network -eq $NULL){$Network="Internal"}

Thomas Lee said...

Sven: A better way to do it would be for the script to check to see if the switch exists, and if not, the script should create it. That's how, at least, I'll be updating the scripts.

In general for this initial script, I don't do enough error checking and repair.

I'll get around to fixing these scripts and uploading updated ones as soon as I get a free week.

Din Master said...

I get an error in the Create-VM script about the path to the UnAttend.xml. It says that the file is missing.
I assume that I am supposed to get the unattend.xml from the the zipfile posted on reskit.netI.
I tried to download, but the site is not up.
Are there any alternative ways to get it/them (I understand that 2 are used)
Michael Torpegart

Unknown said...

This script doesn't work on core, because I can't execute Out Gridview... :( Please help!

Thomas Lee said...

Sorry the unattend.xml files are missing. Please ping me and I'll email them to you. I'm in the midst of updating the scripts and will ensure theupdated ones include the xcml

Thomas Lee said...

You can remove the out-gridview and just pick the particular build you want to install and set the index variable accordingly.