Azure VM Gold Images

In this article I show how to create and deploy custom images (also known as Gold Images) in Azure. A Gold image is a fully patched image that had all our needed software, registry settings, and configurations installed. In a previous article, DSC with Infrastructure-As-Code and Azure Automation is a potent combination, I outlined the limitations of using a “Gold Image” to provision your Virtual Machines, that keeping the machines cloned from these golden images up-to-date with latest versions of software and patches is non-trivial task, and in that article I outlined a strategy for provisioning virtual machines that are in a continual state of operational readiness using Azure Automation DSC and infrastructure as code. However, some clients do want to continue using Gold Images. There is sometimes a reluctance to change established ways of doing things, or just paucity of time . To satisfy such clients, we do need to create and provision such “Gold Images”, and in this article, I show how.

1. Create a Windows VM

Run the provided script: Create-Windows-Vm.ps1 or create a VM from the Azure Portal

$resourceGroup = "rg-hb-image"
$location = "EastUS"
$storageAccountName = "hbbaseimagestorage"
$vnetName = "iaas-net"
$subnetAddress = "10.0.1.0/24"
$vnetAddress = "10.0.0.0/16"
$nicName="vm1-nic"
$vmName = "win-base"
$diskName="os-disk"

###################################################
#Login
###################################################

Login-AzureRmAccount

###################################################
#Resource Group
###################################################
New-AzureRmResourceGroup -Name $resourceGroup -Location $location

###################################################
#Storage
###################################################

New-AzureRmStorageAccount -Name $storageAccountName -ResourceGroupName $resourceGroup `
                          -Type Standard_LRS -Location $location


###################################################
#Network
###################################################
$subnet=New-AzureRmVirtualNetworkSubnetConfig -Name frontendSubnet -AddressPrefix $subnetAddress
$vnet = New-AzureRmVirtualNetwork -Name $vnetName -ResourceGroupName $resourceGroup -Location $location  -AddressPrefix $vnetAddress -Subnet $subnet
                   
###################################################
#pip
###################################################

$pip = New-AzureRmPublicIpAddress -Name $nicName -ResourceGroupName $resourceGroup `
                                  -Location $location -AllocationMethod Dynamic


###################################################
#nic
###################################################
$nic = New-AzureRmNetworkInterface -Name $nicName -ResourceGroupName $resourceGroup `
                                   -Location $location -SubnetId $vnet.Subnets[0].Id -PublicIpAddressId $pip.Id

###################################################
#vm
###################################################
$vm = New-AzureRmVMConfig -VMName $vmName -VMSize "Basic_A1"

#set admin credentials on the box
$cred=Get-Credential -Message "Admin credentials"

$vm=Set-AzureRmVMOperatingSystem -VM $vm -Windows -ComputerName $vmName -Credential $cred `
                                 -ProvisionVMAgent -EnableAutoUpdate

$vm=Set-AzureRmVMSourceImage -VM $vm -PublisherName "MicrosoftWindowsServer" `
                             -Offer "WindowsServer" -Skus "2012-R2-Datacenter" -Version "latest"

$vm=Add-AzureRmVMNetworkInterface -VM $vm -Id $nic.Id


$storageAcc=Get-AzureRmStorageAccount -ResourceGroupName $resourceGroup -Name $storageAccountName
$osDiskUri=$storageAcc.PrimaryEndpoints.Blob.ToString() + "vhds/" + $diskName  + ".vhd"
$vm=Set-AzureRmVMOSDisk -VM $vm -Name $diskName -VhdUri $osDiskUri -CreateOption fromImage

New-AzureRmVM -ResourceGroupName $resourceGroup -Location $location -VM $vm

2. Extract a Custom(Gold) Image

Warning: Once you generalize a vm, you cannot start it. You will then have to clone it from the resulting image.

Provided script file : Extract-VM-Image.ps1

  1. RDP into the box from where image is to be extracted

  2. Navigate to C:WindowsSystem32sysprep and run

    .sysprep.exe
    
  3. select : “enter System Out-of-Box-Experience (OOBE)” in the ensuing dialogue

    1. Click “Generalize”
    2. Shutdown Options : Shutdown
  4. Check Status

    Get-AzureRmVm -ResourceGroupName $resourceGroup -Name $vmName -Status
    

    It will say “Vm Stopped”. We should deallocate it as we are still being charged for compute.

  5. Deallocate the VM

    Stop-AzureRmVm  -ResourceGroupName $resourceGroup -Name $vmName 
    

    then check state again: it should say “Vm deallocated”

  6. Run Set-AzureRmVm with the Generalized option so that Azure knows that the machine is in a good state to take a image.

    Set-AzureRmVm -ResourceGroupName $resourceGroup -Name $vmName -Generalized
    

    Check the status again and we see that the displayStatus is “Vm generalized”

  7. Now we can save the image to a storage account

    Save-AzureRmVMImage -ResourceGroupName $resourceGroup -Name $vmName -DestinationContainerName "vm-images" -VHDNamePrefix "win-web-app" -Path "win-web-app.json"
    
    1. This will create a image in the same storage account as the current disk file, in a new container specified by the “DestinationContainerName” parameter
    2. Azure generates a random file name based on the “VHDNamePrefix” parameter
    3. Azure will save a local copy of the generated ARM template at the location specified by the “Path” parameter. The template will contain the full URI to the new disk image. In the resulting arm template, note the image/uri location, which we will use to create vms from this image. You will provide this location in Section 3.3

    3. Save it in Azure as a Managed Image

    1. In the Azure Portal, click New
    2. Select “Image”
    3. In the “Create Image” window, enter the image/uri location you noted in 2.7.3 above.

You can now query the image with this powershell:

Get-AzureRmImage -ResourceGroupName $ImageRGName

To get all images in the subscription

Get-AzureRmImage

To get the id of the image

$VMImageId = (Get-AzureRmImage -ResourceGroupName $ImageRGName -ImageName $ImageName).id

4. Create a VM from the Gold Image

Run provide script : Create-VM-From-Image.ps1

$VMNameSuffix = "Inf"
$SrcUri = ""
$VmSize = "Standard_A1" 
$LocalMachineAdminAcctPwd = "Password123456"
$RgLocation = "eastus"

#Landlord
$VNetName ="myvnet"

#Image
$ImageRGName = "rg-images"
$ImageName = "MyImage"


########################################################
# Login Runbook
########################################################

#Login-AzureRmAccount

Import-AzureRmContext -Path "c:tempazureprofile.json"

########################################################
#  Varianbles
########################################################

$businessunit = "yyABDC"
$UseCase =  "wqOPSCA" 
$RGLocation = "eastus" 


########################################################
# Global Varianbles
########################################################


$tenant         = "Tenant_"

$TenantName     = "$businessunit"+"-"+"$UseCase"

$RGNames = @{"RGLocation"    = $RGLocation;
            "RGNameVM"      = $tenant+$businessunit+"_"+$UseCase+"_VM";
           
}

########################################################
# Varianbles
########################################################

$VMName = $TenantName+ "-" + $VMNameSuffix;

$PIPName = $TenantName+"_" + $VMName + "_Public_IP_Address";

$NICName = $TenantName+"_" + $VMName + "_Network_Interface";

$PIPLockName = $TenantName + "_" + $VMName + "_Public_IP_Address_Lock";

$NICLockName = $TenantName + "_" + $VMName + "_Network_Interface_Lock";

$VMLockName  = $TenantName + "_" + $VMName + "_VM_Lock";

$ComputerName = ($businessunit + "-" + $VMName).Substring(0,14);


$pip = New-AzureRmPublicIpAddress -ResourceGroupName $RGNames.RGNameVM -Name $PIPName -Location $RGLocation -AllocationMethod Dynamic

#subnet
$subnet=New-AzureRmVirtualNetworkSubnetConfig -Name frontendSubnet -AddressPrefix 10.0.1.0/24

#vnet
$vnet = New-AzureRmVirtualNetwork -Name $vnetName -ResourceGroupName $RGNames.RGNameVM -Location $RGLocation `
                                  -AddressPrefix 10.0.0.0/16 -Subnet $subnet

$nic = New-AzureRmNetworkInterface -ResourceGroupName $RGNames.RGNameVM -Name $NICName -SubnetId $vnet.Subnets[0].Id  -Location $RGNames.RGLocation -PublicIpAddressId $pip.Id

#image
$VMImageId = (Get-AzureRmImage -ResourceGroupName $ImageRGName -ImageName $ImageName).id


#vm

$NewVm = New-AzureRmVMConfig -VMName $VmName -VMSize "Standard_A1"

#Creds
$Secure_String_Pwd = ConvertTo-SecureString $LocalMachineAdminAcctPwd -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential (“SuperAdmin”, $Secure_String_Pwd)

#os
$NewVm = Set-AzureRmVMOperatingSystem -VM $NewVm -Windows -ComputerName $ComputerName -Credential $cred -ProvisionVMAgent -EnableAutoUpdate  

#nic
$NewVm = Add-AzureRmVMNetworkInterface -VM $NewVm -Id $nic.Id


#image
$VMImageId = (Get-AzureRmImage -ResourceGroupName $ImageRGName -ImageName $ImageName).id

$NewVm = Set-AzureRmVMSourceImage -VM $NewVm -Id $VMImageId

$NewVm = Set-AzureRmVMOSDisk -VM $NewVm -StorageAccountType StandardLRS -DiskSizeInGB 128 -CreateOption FromImage -Caching ReadWrite


New-AzureRmVM -ResourceGroupName $RGNames.RGNameVM -Location $RGNames.RGLocation -VM $NewVm

And there you are. A gold image and a vm cloned from said gold image.

## Source Code

Source Code Download