Greenfield Test Environments in Azure

Most IT pros will use and re-use environments for testing anything and everything as well as learning new technologies. Often, building a lab from scratch with AD, Exchange, maybe some other forests, etc can be a very time consuming task.

Azure gives us the ability to build a lab once with all the components we generally need, store the VHDs in the cloud and re-deploy the lab infinitely many times back to it's original state.  Yes, Azure dev/test labs is a feature that can do this at larger scale...this example is for the individual IT pro that owns an Azure subscription, not corporate level testing.

The high level steps are:

  1. Get an Azure subscription (MSDN is a great solution here - https://azure.microsoft.com/en-us/pricing/member-offers/msdn-benefits-details/)
  2. build the lab VMs in Azure (leave yourself a password or two set to never expire - you may want to greenfield this lab in a year!)
  3. shut down the lab VMs so you can re-deploy off their VHDs
  4. Use an ARM template like this one https://azure.microsoft.com/en-us/resources/templates/101-vm-from-user-image/a PowerShell script, or a combination of the two (like I have sampled below).
  5. Once you are done working in the lab, delete the resource group (using my script example the resource group is 'greenfieldlab') and that will delete all of the VMs and storage.
  6. Re-deploy the lab as often as needed for new testing. To update the source lab, just start and update the source VMs/VHDs.

 

 

    This code is Copyright (c) 2017 Microsoft Corporation.
All rights reserved.
THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
INCLUDING BUT NOT LIMITED To THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.'

IN NO EVENT SHALL MICROSOFT AND/OR ITS RESPECTIVE SUPPLIERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
WITH THE USE OR PERFORMANCE OF THIS CODE OR INFORMATION.

.SYNOPSIS

Script deploys a test lab including 7 VMs.
.DESCRIPTIONin

.PARAMETER

.INPUTS
The script will ask for credentials. These are the credentials for your azure subscription, not the VMs. VM creds are already set.

.NOTES
Version: 1.3
Author: John Knightly Microsoft
Creation Date: 4/25/17

#change this to the path of your template deployment file
$templatefile= 'https://mystorageaccountname.blob.core.windows.net/json/lab.json'

#get credential information
login-azurermaccount

#create resourcegroup
$region = 'westus2'
$RG='GreenfieldLab'
New-AzureRmResourceGroup -Name $RG -Location $region

#create vnet
$subnet1 = new-azurermvirtualnetworksubnetconfig -name 'subnet1' -addressprefix '10.6.0.0/24'
$subnetname='subnet1'
$vnetname=$rg+'vnet'

$VNET = New-AzureRmVirtualNetwork -Name $vnetname -Location $region -ResourceGroupName $RG -AddressPrefix '10.6.0.0/24' -subnet $subnet -DnsServer '10.6.0.4'

#create new storage account, get the access key and create a storage context
$saprefix=get-random -Minimum 1000 -Maximum 10000000
$saname = 'greenfieldlab'+$saprefix
$storage = New-AzureRMStorageAccount -ResourceGroupName $RG -Name $saname -Location $region -type Standard_LRS
$storagekey = (get-azureRMstorageaccountkey -resourcegroupname $RG -StorageAccountName $saname).value[1]
$destcontext = New-AzureStorageContext -StorageAccountName $saname -StorageAccountKey $storagekey
$destcontainer = New-AzureStorageContainer -Name 'images' -Context $destcontext
$destvhd = New-AzureStorageContainer -Name 'vhds' -Context $destcontext

#setup vars to copy files from source to newly created storage account
$sourcesaname = 'yourstorageaccountname'
#use your own source key
$sourcekey= 'youcanthavemystorageaccountkey_gogenerateyourown'
$sourcecontext = New-AzureStorageContext -StorageAccountName $sourcesaname -StorageAccountKey $sourcekey
$sourcecontainer = Get-AzureStoragecontainer -Context $sourcecontext
$blobs = get-azurestorageblob -container $sourcecontainer.Name -Context $sourcecontext

#file copy
foreach ($b in $blobs)
{
Start-AzureStorageBlobCopy -SrcContainer $sourcecontainer.name -DestContainer $destcontainer.name -Context $sourcecontext -DestContext $destcontext -SrcBlob $b.Name

}

write-host "Copy could take some time please be patient. Generally 1-2 hours is normal, but there is no SLA in Azure for storage account copies" -ForegroundColor yellow
# below status checks are functioning to hold script until all blobs are completed copying
$_copytime = get-date
$Blobs | foreach {
while ((get-azurestorageblob $_.name -context $destcontext -Container $destcontainer.name | get-azurestorageblobcopystate).Status -eq "pending"){
$_results = get-azurestorageblob $blobs[1].name -context $destcontext -Container $destcontainer.name | get-azurestorageblobcopystate
Write-Progress -Activity "Copying Blobs" -Status "Progress: $(($_results).bytescopied) of $(($_results).TotalBytes) - Total Time Passed: $((new-TimeSpan($_copytime) $(Get-Date)).minutes) minutes" -PercentComplete ($(($_results).bytescopied)/$(($_results).TotalBytes)*100)
start-sleep -Seconds 60
}
}

write-host "Total copy time $((new-TimeSpan($_copytime) $(Get-Date)).minutes) minutes" -foregroundcolor yellow

#set vars for path of VHD file for each vm to be deployed
$contosoVHD = $destcontext.BlobEndPoint + 'images/contosodc120170331122936.vhd'
$eudc1vhd = $destcontext.BlobEndPoint + 'images/eucontosodc120170417083825.vhd'
$fabdcvhd = $destcontext.BlobEndPoint + 'images/fabrikamdc120170331123706.vhd'
$exchvhd = $destcontext.BlobEndPoint + 'images/contosoex120170331131217.vhd'
$contosofsvhd = $destcontext.BlobEndPoint + 'images/contosofs120170331123927.vhd'
$fabfsvhd = $destcontext.BlobEndPoint + 'images/fabrikamFS120170331130906.vhd'
$eufsvhd = $destcontext.BlobEndPoint + 'images/EUFS120170331131044.vhd'
$templatefile= 'https://812231westus2.blob.core.windows.net/json/lab.json'

#deployment of contosodc1 waiting for this one to deploy first since it is vnets DNS server
New-AzureRmResourceGroupDeployment -Name 'contosodc1' -ResourceGroupName $RG -TemplateUri $templatefile -vmName 'contosodc1' -ostype 'Windows' -osdiskvhduri $contosovhd -vmsize 'standard_d2_v2' -existingvirtualnetworkname $vnetname -existingvirtualnetworkresourcegroup $RG -subnetname $subnetname

#wait for contosodc1 deployment to be complete
$contosodcstatus = Get-AzureRmVM -Name contosodc1 -resourcegroup aclxraylab
while ($contosodcstatus.ProvisioningState -ne 'Succeeded')
{start-sleep -seconds 10
$contosodcstatus = Get-AzureRmVM -Name contosodc1 -resourcegroup aclxraylab
write-host 'Waiting for Forest Root DC deployment to complete'
}
Start-Sleep -Seconds 15

#set contosodc1 to static IP
$nic=Get-AzureRmNetworkInterface -Name contosodc1 -ResourceGroupName $rg
$nic.IpConfigurations[0].PrivateIpAllocationMethod = "Static"
$nic.IpConfigurations[0].PrivateIpAddress = "10.6.0.4"
Set-AzureRmNetworkInterface -NetworkInterface $nic

#deploy remaining VMs
New-AzureRmResourceGroupDeployment -Name 'eucontosodc1' -ResourceGroupName $RG -TemplateUri $templatefile -vmName 'eucontosodc1' -ostype 'Windows' -osdiskvhduri $eudc1vhd -vmsize 'standard_d2_v2' -existingvirtualnetworkname $vnetname -existingvirtualnetworkresourcegroup $RG -subnetname $subnetname
New-AzureRmResourceGroupDeployment -Name 'fabrikamdc1' -ResourceGroupName $RG -TemplateUri $templatefile -vmName 'fabrikamdc1' -ostype 'Windows' -osdiskvhduri $fabdcvhd -vmsize 'standard_d2_v2' -existingvirtualnetworkname $vnetname -existingvirtualnetworkresourcegroup $RG -subnetname $subnetname
New-AzureRmResourceGroupDeployment -Name 'contosoex1' -ResourceGroupName $RG -TemplateUri $templatefile -vmName 'contosoex1' -ostype 'Windows' -osdiskvhduri $exchvhd -vmsize 'standard_d2_v2' -existingvirtualnetworkname $vnetname -existingvirtualnetworkresourcegroup $RG -subnetname $subnetname
New-AzureRmResourceGroupDeployment -Name 'contosofs' -ResourceGroupName $RG -TemplateUri $templatefile -vmName 'contosofs1' -ostype 'Windows' -osdiskvhduri $contosofsvhd -vmsize 'standard_d2_v2' -existingvirtualnetworkname $vnetname -existingvirtualnetworkresourcegroup $RG -subnetname $subnetname
New-AzureRmResourceGroupDeployment -Name 'fabrikamfs' -ResourceGroupName $RG -TemplateUri $templatefile -vmName 'FabrikamFS' -ostype 'Windows' -osdiskvhduri $fabfsvhd -vmsize 'standard_d2_v2' -existingvirtualnetworkname $vnetname -existingvirtualnetworkresourcegroup $RG -subnetname $subnetname
New-AzureRmResourceGroupDeployment -Name 'eufs' -ResourceGroupName $RG -TemplateUri $templatefile -vmName 'eufs1' -ostype 'Windows' -osdiskvhduri $eufsvhd -vmsize 'standard_d2_v2' -existingvirtualnetworkname $vnetname -existingvirtualnetworkresourcegroup $RG -subnetname $subnetname

#stop the environment so i'm not getting charged for compute while waiting to start using it
Get-AzureRmvm -ResourceGroupName $rg | stop-AzureRmVM -force

Write-host "Lab deployment is now complete. The VMs are stopped." -ForegroundColor Cyan