Lab Automation

In my day job I often take customers through demos of our products, we have some pre-canned demos available to use as well as some demo environments but there are some gaps and we have to built our own or find other methods of showing off the products. Three products I talk about often are Azure Backup, Azure Migrate and Azure Site Recovery. I've pulled together a few environments for each over the past year and then ripped them down again, which takes alot of my time. But over the last few weeks I've been studying for exams and exploring alot of things different services which got me thinking about trying to automate the building of these labs in some way.

Disclaimer: This method does require a bit of time pre automation and might not be the most efficient deployment, but this method works for my needs so please feel free to take my learnings and do it in a better way.

What do I need

As I said I demo three products time and time again, Azure Backup, Azure Migrate and Azure Site Recovery. Each kind product have similar requirements to operate, the Backup product needs something to backup (servers), the Migrate product needs something to discover and assess (servers) and Site Recovery needs something replicate (servers).

So I need servers . In the past I've tried to use my (aging) HP Microserver at home as a lab environment, either running VMware or Hyper-V but isn't a sustainable method for a variety of reasons so my new solution is to take advantage of nested virtualization on Azure!

Nested virtualization has been supported on Azure since around July 2017, and currently all v3 virtual machines support nested virtualization.

Disclaimer: Using the nested virtualization only allows you to demonstrate Hyper-V functionality or mimic an "on-premises" environment, you will need to find another way to demo a VMware environment.

I'm going to base this blog on what I really want/need to demonstrate Azure Backup server, but you can use the same principles for other lab environment needs etc.

So for my Azure Backup lab I need:

  • An Active Directory server
  • A server to backup
  • A server to deploy Azure Backup server on

Obviously you can expand on that but environment but my main requirement of this lab is to showcase Azure Backup server and show some functionality working.


For those of you who have set up a lab with a AD server, and another couple of servers you will know the work involved in that. It can be very time consuming deploying servers, patching them, installing the necessary roles/features etc. It can suck up hours or days of your time, which is okay when you have the luxury of time but often you get asked to demo something at short notice so you want to be able to spin up your lab environment fairly quickly.

In order to make my lab repeatable I'm going to use Azure Resource Manager (ARM) templates and PowerShell scripts ad my deployment method.

To make it repeatable all in I need the following:

  • An ARM template to deploy my host machine
  • PowerShell scripts to deploy my configuration
  • An Azure Subscription to host my host machine on
  • An Azure storage account to store my scripts etc
  • Pre-built VMs

So let's talk about those components in more detail....

Pre-Built VMs

This is where you put the work in to make this repeatable. You need to build the VMs (in my case, a AD server, File Server and vanilla image server) with the required configuration you need to replicate a "on-premise" environment at least once. So find a host server, deploy Hyper-V and build your servers. This will take some time, I spent the best part of a day doing this. I built three servers with Windows Server 2016 on them, patched them, then set up Active Directory on one of the servers, deployed a File Server configuration on another and left the third just running the Operating System ready for Azure Backup Server to be deployed.

I then exported these VMs and placed them in a ZIP file that is stored in an Azure Storage blob, ready for use.

This is time consuming and my servers are in an offline state so if I spin them up in say 3 months time they will be out of date in relation to patches but they are still in a usable state for a demo environment.

ARM Template

My ARM template does a few things:

  • Deploys a Windows Server 2016 server
  • Deploys a Virtual Network
  • Deploys a network interface attached to my server
  • Deploys a public IP address to my server and sets a DNS name for my server
  • Installs the Hyper-V role on my server
  • Fetches my pre-built VMs, and then deploys them within Hyper-V

Below is a visualisation of what the template does:

Hyper V config

A copy of the ARM template can be found here.

Variable declaration

I'm going to break some of it down for you. At the start of this template there is a variables section that is specifies the name of various items throughout the deployment.

"variables": {
    "OnPremVNETPrefix": "",
    "OnPremVNETSubnet1Name": "VMHOST",
    "OnPremVNETSubnet1Prefix": "",
    "HyperVHostName": "HYPERVHOST",
    "HyperVHostImagePublisher": "MicrosoftWindowsServer",
    "HyperVHostImageOffer": "WindowsServer",
    "HyperVHostWindowsOSVersion": "2016-Datacenter",
    "HyperVHostOSDiskName": "[concat(variables('HyperVHostName'), '-OSDISK')]",
    "HyperVHostVmSize": "Standard_D4s_v3",
    "HyperVHostVnetID": "[resourceId('Microsoft.Network/virtualNetworks', 'OnPremVNET')]",
    "HyperVHostSubnetRef": "[concat(variables('HyperVHostVnetID'), '/subnets/', variables('OnPremVNETSubnet1Name'))]",
    "HyperVHostNicName": "[concat(variables('HyperVHostName'), '-NIC')]",
    "HyperVHost-PUBIPName": "[concat(variables('HyperVHostName'), '-PIP')]",
    "HyperVHostConfigArchiveFolder": ".",
    "HyperVHostConfigArchiveFileName": "",
    "HyperVHostConfigURL": "",
    "HyperVHostInstallHyperVScriptFolder": ".",
    "HyperVHostInstallHyperVScriptFileName": "InstallHyperV.ps1",
    "HyperVHostInstallHyperVURL": ""

In this section I've defined the Virtual Network and subnet, the name of my host machine, the operating system I want installed, the size of machine to be deployed and the names of elements such as the NIC and Public IP.

The last three lines refer to the installation of Hyper-V and the deployment of my pre-built VMs, which I'll talk about in more detail in a bit.

VM deployment

Lines 35 to 145 are the standard deployment settings you'd see in any ARM template that deploys a virtual machine in Azure. It refers how to set up the virtual network, subnet, public IP, the VM, etc.

Install Hyper-V

Lines 146 to 170 are where I get Hyper-V installed on the VM.

      "resources": [
          "name": "InstallHyperV",
          "type": "extensions",
          "location": "[resourceGroup().location]",
          "apiVersion": "2017-12-01",
          "dependsOn": [
            "[resourceId('Microsoft.Compute/virtualMachines', variables('HyperVHostName'))]"
          "tags": {
            "displayName": "Install Hyper-V"
          "properties": {
            "publisher": "Microsoft.Compute",
            "type": "CustomScriptExtension",
            "typeHandlerVersion": "1.4",
            "autoUpgradeMinorVersion": true,
            "settings": {
              "fileUris": [
              "commandToExecute": "[concat('powershell -ExecutionPolicy Unrestricted -File ', variables('HyperVHostInstallHyperVScriptFolder'), '/', variables('HyperVHostInstallHyperVScriptFileName'))]"

This section takes advantage of the Custom Script Extension that helps with post deployment configuration, software installation etc. It has the ability to download scripts and then execute them.

The Custom Script Extension can download a script from sources such as GitHub, Azure Storage, an internal file server, just ensure that if you are downloading from something like GitHub you have the necessary Internet access available through your Network Security Group, firewall, etc. Any scripts you call shouldn't require user input and they have up to 90minutes to run, anything longer than that and a failed state will be reported.

This part of my ARM template goes away and downloads the PowerShell script that says Install Hyper-V and the management tools and executes that script. You can see the variables (HyperVHostInstallHyperVScriptFileName, HyperVHostInstallHyperVURL and HyperVHostInstallHyperVScriptFolder) that were defined earlier being referenced in this section.

Deploy the pre-built VMs

Lines 172 to 195 look at deploying the pre-built VMs.

          "name": "HyperVHostConfig",
          "type": "extensions",
          "location": "[resourceGroup().location]",
          "apiVersion": "2017-12-01",
          "dependsOn": [
            "[resourceId('Microsoft.Compute/virtualMachines', variables('HyperVHostName'))]",
            "[resourceId('Microsoft.Compute/virtualMachines/extensions', variables('HyperVHostName'), 'InstallHyperV')]"
          "tags": {
            "displayName": "HyperVHostConfig"
          "properties": {
            "publisher": "Microsoft.Powershell",
            "type": "DSC",
            "typeHandlerVersion": "2.9",
            "autoUpgradeMinorVersion": true,
            "settings": {
              "configuration": {
                "url": "[concat(variables('HyperVHostConfigURL'))]",
                "script": "HyperVHostConfig.ps1",
                "function": "Main"
              "configurationArguments": {
                "nodeName": "[variables('HyperVHostName')]"

This part of the template takes advantage of the Desired State Configuration (DSC) extension. This is a standalone action for this template, it doesn't require any set up within DSC in Azure. The Azure DSC extension using the Azure VM Agent to deliver this configuration. In this instance it downloads the ZIP file that I have referenced in my variables then extracts the PowerShell(PS1) file that is within there and executes the commands in there.

Within this PS1 file is the configuration that downloads my pre-built VMs, setups up the networking for Hyper-V and then imports my VMs and starts them up in Hyper-V.

PowerShell scripts

As I mentioned above my ARM template pulls in two PowerShell scripts. Let's take a look at them in more detail...

Hyper-V Configuration Script

The first script is the script that ensures the Hyper-V role is installed on my Azure VM.

Set-ExecutionPolicy Unrestricted -Force
Install-PackageProvider -Name NuGet -MinimumVersion -Force
Find-Module -Includes DscResource -Name xHyper-v | Install-Module -Force

#Install Hyper-V and Reboot
Install-WindowsFeature -Name Hyper-V `
                       -IncludeAllSubFeature `
                       -IncludeManagementTools `
                       -Verbose `

The script does the following:

  • Downloads NuGet package provider
  • Installs the DscResource and xHyper-V PS modules in support of the upcoming DSC Extension run in the next PowerShell script
  • Installs Hyper-V with all Features and Management Tools and then Restarts the Machine

Very straight forward.

Hyper-V setup and VM deployment

The next PowerShell script is a bit more involved. As it does a bit more.

Configuration Main
    Param ( [string] $nodeName )

    Import-DscResource -ModuleName 'PSDesiredStateConfiguration', 'xHyper-V'

    node $nodeName
        # Ensures a VM with default settings
        xVMSwitch InternalSwitch
            Ensure         = 'Present'
            Name           = 'NatSwitch'
            Type           = 'Internal'

        Script ConfigureHyperV
            GetScript = 
                @{Result = "ConfigureHyperV"}

            TestScript = 
                return $false

            SetScript =
                $zipDownload = ""
                $downloadedFile = "D:\"
                $vmFolder = "C:\VM"
                Resize-Partition -DiskNumber 0 -PartitionNumber 2 -Size (400GB)
                Invoke-WebRequest $zipDownload -OutFile $downloadedFile
                Add-Type -assembly ""
                [io.compression.zipfile]::ExtractToDirectory($downloadedFile, $vmFolder)
                $NatSwitch = Get-NetAdapter -Name "vEthernet (NatSwitch)"
                New-NetIPAddress -IPAddress -PrefixLength 24 -InterfaceIndex $NatSwitch.ifIndex
                New-NetNat -Name NestedVMNATnetwork -InternalIPInterfaceAddressPrefix -Verbose
                New-VM -Name AD01 `
                       -MemoryStartupBytes 2GB `
                       -BootDevice VHD `
                       -VHDPath 'C:\VM\AD01.vhdx' `
             -Path 'C:\VM' `
                       -Generation 1 `
                     -Switch "NATSwitch"
                Start-VM -Name AD01
                New-VM -Name FS01 `
                    -MemoryStartupBytes 2GB `
                    -BootDevice VHD `
                    -VHDPath 'C:\VM\FS01.vhdx' `
                    -Path 'C:\VM' `
                    -Generation 1 `
                    -Switch "NATSwitch"
                Start-VM -Name FS01
                New-VM -Name BP01 `
                    -MemoryStartupBytes 8GB `
                    -BootDevice VHD `
                    -VHDPath 'C:\VM\BP01.vhdx' `
                    -Path 'C:\VM' `
                    -Generation 1 `
                    -Switch "NATSwitch"
                Start-VM -Name BP01

The script does the following:

  • Ensures there is a Virtual Switch, labelled Internal is configured within Hyper-V
  • Sets the download location of my VMs and where they will go
  • Resizes the OS disk on my Azure VM to 400GB
  • Downloads my VMs (in ZIP file format)
  • Extracts them from the ZIP file
  • Sets up the Hyper-V NAT network
  • Imports the Active Directory (AD01) VM
  • Imports the File Server (FS01) VM
  • Imports the Backup (BP01) VM
  • And starts up those new VMs

After the deployment is complete I have a few checks to complete around the IP addressing of the VMs themselves but other than that the environment is ready to use.

Round Up

I appreciate this isn't a fully automated deployment of a lab environment but it does save a lot of time and rework and can be up and running in an hour or two.

You can find my ARM template and PowerShell scripts on my GitHub at I haven't uploaded the VMs that I have built due to their size.

I'd welcome any feedback on what I've done. Please do reach out with any questions as well, happy to help!

Blog Comments powered by Disqus.

Next Post Previous Post