Easily Create a Hyper-V Windows Server 2016 AD & Nano Server Lab


One of the PowerShell Modules I’ve been working on for the last year is called LabBuilder.The goal of this module is:

To automatically build a multiple machine Hyper-V Lab environment from an XML configuration file and other optional installation scripts.

What this essentially does is allow you to easily build Lab environments using a specification file. All you need to do is provide the Hyper-V environment and the Operating System disk ISO files that will be used to build the lab. This is great for getting a Lab environment spun up for testing or training purposes.

Note: Building a new Lab can take a little while, depending on the number of VM’s in the Lab as well as the number of different Operating Systems used. For example, a Lab with 10 VMs could take an hour or two to spin up, depending on your hardware.

The LabBuilder module comes with a set of sample Labs that you can build “as is” or modify for your own purpose. There are samples for simple one or two machine Labs as well as more complex scenarios such as failover clusters and two tier PKI environments. Plus, if you’re feeling adventurous you can easily create your own LabBuilder configurations from scratch or by modifying an existing LabBuilder configuration.

In this article I’ll show how to use a configuration sample that will build a lab containing the following servers:

  • 1 x Windows Server 2016 RTM Domain Controller (with DNS)
  • 1 x Windows Server 2016 RTM DHCP Server
  • 1 x Windows Server 2016 RTM Certificate Authority Server
  • 1 x Windows Server 2016 RTM Edge Node (Routing and Remote Access server)
  • 8 x Windows Server 2016 RTM Nano Servers (not yet automatically Domain Joined – but I’m working on it).

This is a great environment for experimenting with both Windows Server 2016 as well as Nano Server.

So, lets get started.


To follow along with this guide your Lab host (the machine that will host your Lab) will need to have the following:

Be running Windows Server 2012 R2, Windows Server 2016 or Windows 10

I strongly recommend using Windows 10 Anniversary Edition.

If you are using Windows Server 2012 R2 you will need to install WMF 5.0 or above. Although WMF 4.0 should work, I haven’t tested it.

Have enough RAM, Disk and CPU available for your Lab

Running a lot of VMs at once can be fairly taxing on your hardware. For most Sample Lab I’d recommend at least a quad core CPU, 16 GB RAM and a fast SSD with at least 10 GB per VM free (although for Nano Server VMs only 800MB is required).

The amount of disk used is minimized by using differencing disks, but Labs can still get pretty big.

Hyper-V Enabled

If you’re using Windows 10, see this guide.

If you’re using Windows Server 2012 R2 or Windows Server 2016, you probably already know how to do this, so I won’t cover this here.

Copies of any Windows install media that is used by the Lab

In our case this is just a copy of the Windows Server 2016 Evaluation ISO. You can download this ISO from here for free.

You can use non-evaluation ISOs instead if you have access to them, but at the time of writing this the Windows Server 2016 non-evaluation ISO wasn’t yet available on my MSDN subscription.

An Internet Connection

Most Labs use DSC to configure each VM once it has been provisioned, so the ability to download any required DSC Resources from the PowerShell Gallery is required. Some sample Labs also download MSI packages and other installers that will be deployed to the Lab Virtual Machines during installation – for example RSAT is often installed onto Windows 10 Lab machines automatically.

The Process

Step 1 – Install the Module

The first thing you’ll need to do is install the LabBuilder Module. Execute this PowerShell command at an Administrator PowerShell prompt:

Install-Module -Name LabBuilder


Note: If you have an older version of LabBuilder installed, I’d recommend you update it to at least because this was the version I was using to write this guide.

Step 2 – Create the ISOs and VHDs Folders

Most labs are built using Windows Install media contained in ISO files. These are converted to VHD files that are then used by one or more Labs. We need a location to store these files.

By default all sample Labs expect these folders to be D:\ISOs and D:\VHDs. If you don’t have a D: Drive on your computer, you’ll need to adjust the LabBuilder configuration file in Step 4.

Execute the following PowerShell commands at an Administrator PowerShell prompt:

New-Item -Path 'd:\ISOs' -ItemType Directory
New-Item -Path 'd:\VHDs' -ItemType Directory


Step 3 – Create a Folder to Contain the Lab

When building a Lab with LabBuilder it will create all VMs, VHDs and other related files in a single folder.

For all sample LabBuilder configurations, this folder defaults to a folder in C:\vm. For the sample Lab we’re building in this guide it will install the Lab into c:\vm\NANOTEST.COM. This can be changed by editing the configuration in Step 4.

Note: Make sure you have enough space on your chosen drive to store the Lab. 10GB per VM is a good rough guide to the amount of space required (although it usually works out as a lot less because of the use of differencing disks).

Execute the following PowerShell commands at an Administrator PowerShell prompt:

New-Item -Path 'c:\VM' -ItemType Directory

Step 4 – Customize the Sample Lab file

We’re going to build the Lab using the sample Lab found in the samples folder in the LabBuilder module folder. The sample we’re using is called Sample_WS2016_NanoDomain.xml. I’d suggest editing this file in an editor like Notepad++.

If you changed the paths in Step 2 or Step 3 then you’ll need to change the paths shown in this screenshot:


You may also change other items in the Settings section, but be aware that some changes (such as changing the domain name) will also need to be changed elsewhere in the file.

If you already have an External Switch configured in Hyper-V that you’d like to use for this Lab to communicate externally, then you should set the name of the switch here:


If you don’t already have an External Switch defined in Hyper-V then one called General Purpose External will be created for you. It will use the first Network Adapter (physical or team) that is not already assigned to an External Switch. You can control this behavior in the LabBuilder configuration file but it is beyond the scope of this guide.

Save the Sample_WS2016_NanoDomain.xml once you’ve finished changing it.

Step 5 – Copy the Windows Media ISOs

Now that the ISOs folder is ready, you will need to copy the Windows Install media ISO files into it. In this case we need to copy in the ISO for Windows Server 2016 (an evaluation copy can be downloaded from here).

The ISO file must be name:


If it is named anything else then you will either need to rename it or go back to Step 4 and adjust the sample Lab configuration file.


Step 6 – Build the Lab

We’re now ready to build the lab from the sample configuration.

Execute the following PowerShell commands at an Administrator PowerShell prompt:

$ConfigPath = Join-Path `
-Path (Split-Path -Path (Get-Module -Name LabBuilder -ListAvailable).Path -Parent) `
-ChildPath 'Samples\Sample_WS2016_NanoDomain.xml'
Install-Lab -ConfigPath $ConfigPath -Verbose

This will begin the task of building out your Lab. The commands just determine the location of your LabBuilder sample file and then call the Install-Lab cmdlet. I could have specified the path to the sample file manually, and you can if you prefer.


So sit back and grab a tea or coffee (or beer), because this will take a little while.

Note: The individual virtual machines are configured using PowerShell DSC after they are first started up. This means that it might actually take some time for things like domain joins and other post configuration tasks to complete. So if you find a Lab VM hasn’t yet joined the domain, it is most likely that the DSC configuration is still being applied.

Using the Lab

Once you’ve built the Lab, you can log into the VMs like any other Hyper-V VM. Just double click the Virtual Machine and enter your login details:


For the sample Lab the Domain Administrator account password is configured as P@ssword!1. This is set in the Lab Sample configuration and you can change it if you like.

Note: Nano Server is not designed to have an interactive GUI. You interact with Nano Server via PowerShell Remoting. You’ll want to have a basic knowledge of PowerShell and PowerShell Remoting before attempting to administer Nano Servers.

Shutting Down the Lab

Once the Lab has been completely built, you can shut it down with the Stop-Lab command. You need to pass the path to the Lab Configuration file to shut it down:

$ConfigPath = Join-Path `
-Path (Split-Path -Path (Get-Module -Name LabBuilder -ListAvailable).Path -Parent) `
-ChildPath 'Samples\Sample_WS2016_NanoDomain.xml'
Stop-Lab -ConfigPath $ConfigPath -Verbose

The Virtual Machines in the Lab will be shut down in an order defined in the Lab Configuration file. This will ensure that the VMs are shut down in the correct order (e.g. shut down the domain controllers last).

Starting the Lab Up

If you need to start up a previously created Lab, use the Start-Lab command. You will again need to provide the path to the Lab Configuration file of the Lab you want to shut down:

$ConfigPath = Join-Path `
-Path (Split-Path -Path (Get-Module -Name LabBuilder -ListAvailable).Path -Parent) `
-ChildPath 'Samples\Sample_WS2016_NanoDomain.xml'
Start-Lab -ConfigPath $ConfigPath -Verbose

The Virtual Machines in the Lab will be started up in an order defined in the Lab Configuration file. This will ensure that the VMs are started up in the correct order.

Uninstalling the Lab

If you want to completely remove a Lab, use the Uninstall-Lab command. You will again need to provide the path to the Lab Configuration file of the Lab you want to unisntall:

$ConfigPath = Join-Path `
-Path (Split-Path -Path (Get-Module -Name LabBuilder -ListAvailable).Path -Parent) `
-ChildPath 'Samples\Sample_WS2016_NanoDomain.xml'
Uninstall-Lab -ConfigPath $ConfigPath -Verbose

Note: You will be asked to confirm the removals.

Wrapping Up

This article has hopefully given you a basic understanding of how to use LabBuilder to stand up a Hyper-V Lab in relatively short order and without a lot of commands and clicks. This project is still in Beta and so there may be bugs as well as some incomplete features. If you want to raise an issue with this project (or even submit a PR), head on over to the GitHub repository.


Comparing Objects using JSON in PowerShell for Pester Tests

Recently I spent the good part of a weekend putting together Pester Tests (click here if you aren’t familiar with Pester) for my LabBuilder PowerShell module- a module to build a set of Virtual Machines based on an XML configuration file. In the module I have several cmdlets that take an XML configuration file (sample below) and return an array of hash tables as well as some hash table properties containing other arrays – basically a fairly complex object structure.

A Pester Test config file for the LabBuilder module

A Pester Test config file for the LabBuilder module

In the Pester Tests for these cmdlets I wanted to ensure the object that was returned exactly matched what I expected. So in the Pester Test I programmatically created an object that matched what the Pester Test should expect the output of the cmdlets would be:

$ExpectedSwtiches = @( 
  @{ name="General Purpose External"; type="External"; vlan=$null; adapters=[System.Collections.Hashtable[]]@(
    @{ name="Cluster"; macaddress="00155D010701" },
    @{ name="Management"; macaddress="00155D010702" },
    @{ name="SMB"; macaddress="00155D010703" },
    @{ name="LM"; macaddress="00155D010704" }
  @{ name="Pester Test Private Vlan"; type="Private"; vlan="2"; adapters=@() },
  @{ name="Pester Test Private"; type="Private"; vlan=$null; adapters=@() },
  @{ name="Pester Test Internal Vlan"; type="Internal"; vlan="3"; adapters=@() },
  @{ name="Pester Test Internal"; type="Internal"; vlan=$null; adapters=@() }

What I needed to do was try and make sure the objects were the same. At first I tried to use the Compare-Object cmdlet – this actually wasn’t useful in this situation as it doesn’t do any sort of deep property comparison. What was needed was to serialize the objects and then perform a simple string comparison. The ConvertTo-JSON cmdlet seemed to be just what was needed. I also decided to use the [String]::Compare() method instead of using the PowerShell -eq operator because the -eq operator seems to have issues with Unicode strings.

The Pester test that I first tried was:

Context "Valid configuration is passed" {
  $Switches = Get-LabSwitches -Config $Config
  It "Returns Switches Object that matches Expected Object" {
    [String]::Compare(($Switches | ConvertTo-Json),($ExpectedSwtiches | ConvertTo-Json)) | Should Be 0

This initially seemed to work, but if I changed any of the object properties below the root level (e.g. the adapter name property) the comparison still reported the objects were the same when they weren’t. After reading the documentation it states that the ConvertTo-JSON cmdlet provides a Depth property that defaults to 2 – which limits the depth that an object structure would be converted to. In my case the object was actually 4 levels deep. So I needed to add a Depth parameter to the ConvertTo-JSON calls:

[String]::Compare(($Switches | ConvertTo-Json -Depth 4),($ExpectedSwtiches | ConvertTo-Json -Depth 4)) | Should Be 0

This then did pretty much exactly what I wanted. However, I also needed the comparison to be case-insensitive, so I added a boolean parameter to the [String]::Compare static call:

[String]::Compare(($Switches | ConvertTo-Json),($ExpectedSwtiches | ConvertTo-Json),$true) | Should Be 0

The end result was an deep object comparison between a reference object and the object the cmdlet being tested returned. It is by no means perfect as if the properties or contents of any arrays in the object are out of order the comparison will report that there are differences, but because we control the format of these objects this shouldn’t be a problem and should enable some very test strict cmdlet tests.

How the the Final Pester Test in Visual Studio 2015 (with POSH tools)

How the the Final Pester Test in Visual Studio 2015 (with POSH tools)

Edit: after writing a number of Pester tests using the approach I realized it could be simplified slightly by replacing the generation of the comparison object with the actual JSON output produced by the reference object embedded inline in a variable. For example:

Performing the object comparison using JSON in a variable in the test.

Performing the object comparison using JSON in a variable in the test.

The JSON can be generated manually by hand (before writing the function itself) to stick to the Test Driven Design methodology or it can be generated from the object the function being tested created (once it it working correctly) and then written to a file using:

Set-Content -Path "$($ENV:Temp)\Switches.json" -Value ($Switches | ConvertTo-Json -Depth 4)

The $switches  variable contains the actual object that is produced by the  working command being tested.

A Word of Caution about CRLF

I have noticed that when opening the JSON file in something like Notepad++ and copying the JSON to the clipboard (to paste into my Pester test) that an additional CRLF appears at the bottom. You need to ensure you don’t include this at the bottom of your variable too – otherwise the comparison will fail and the objects will appear to be different (when they aren’t).

This is what the end of the JSON variable definition should look like:

Good JSON CRLF Formatting

And this is what it should not look like (the arrow indicates the location of the extra CRLF that should be removed):

Good JSON CRLF formatting

Note: I could have used the Export-CliXML and Import-CliXML CmdLets instead to perform the object serialization and comparison, but these cmdlets write the content to disk and also generate much larger strings which would take much longer to compare and ending up with a more complicated test.

Well, hopefully someone else will find this useful!