You love PowerShell, have a custom VM image (specialized or generalized) and you want to create a new virtual machine from it under Azure Resource Manager API? You're at the right place...

Prerequisites

  • Microsoft Azure PowerShell v0.9.4 or later, I used 0.9.7
  • A custom VM image captured using the Azure Resource Manager API (more detail just below)

Things to know before you get started

Here is few items you need to be aware before you continue...

The custom VM image need to have been captured using a v2 Compute resource provider

VM images captured from a v1 Compute resource provider (Microsoft.ClassicCompute) cannot be used to create virtual machines using a v2 Compute resource provider (Microsoft.Compute) or the other way around.

If you need a new image after reading this, see Step by Step: How to capture your own custom virtual machine image under Azure Resource Manager API in the References section.

The Custom VM image need to reside in the same storage account as your virtual machine at creation time

Another important limitation is that the image need to be present at creation time in the very same storage account that will host your virtual machine's vhd. The custom VM image can be deleted afterward without problems.

It's not that big thing if you are using a standard storage account. On the other hand, if you want your virtual machine to run on premium storage, leaving that P10 (or bigger) custom VM image around, doing nothing, will cost you the same money per month as any other premium storage disk. For more details, see Azure Storage Pricing link in the References section.

Creating a virtual machine from the custom image

Now the fun part... In a basic example, you'll see how to reference your custom image in the VM creation process using PowerShell cmdlets. Ready?

We assume the storage account already exists since you are supposed to have your custom VM image already in it at this point.

First we need to authenticate against Azure, use the desired subscription and storage account.

Add-AzureAccount 
Switch-AzureMode AzureResourceManager
Select-AzureSubscription -SubscriptionId $subscriptionId

$storageAccount = Get-AzureStorageAccount -Name $storageAccountName

We create the networking resources for the VM

# Create the VNET
$vnetDef = New-AzureVirtualNetwork -ResourceGroupName $resourceGroupName -Location $location -Name $vnetName -AddressPrefix '10.0.0.0/16'
$vnet = $vnetDef | Add-AzureVirtualNetworkSubnetConfig -Name 'Subnet-1' -AddressPrefix '10.0.0.0/24' | Set-AzureVirtualNetwork

# Create the NIC
$pip = New-AzurePublicIpAddress -ResourceGroupName $resourceGroupName -Location $location -Name $ipName -DomainNameLabel $domName -AllocationMethod Dynamic
$nic = New-AzureNetworkInterface -ResourceGroupName $resourceGroupName -Location $location -Name $nicName -PublicIpAddressId $pip.Id -SubnetId $vnet.Subnets[0].Id 

Now we can create the VM configuration and glue it all together. Pay special attention to the -SourceImageUri parameter of the Set-AzureVMOSDisk cmdlet, this is where we specify the location of the custom VM image to use.

# Specify the VM name and size
$vm = New-AzureVMConfig -VMName $vmName -VMSize $vmSize 

# Specify local administrator account, and then add the NIC
$cred = New-Object PSCredential $adminUsername, ($adminPassword | ConvertTo-SecureString -AsPlainText -Force)
$vm = Set-AzureVMOperatingSystem -VM $vm -Windows -ComputerName $vmName -Credential $cred -ProvisionVMAgent -EnableAutoUpdate
$vm = Add-AzureVMNetworkInterface -VM $vm -Id $nic.Id

# Specify the OS disk
$diskName = 'osdisk'
$osDiskUri = '{0}vhds/{1}{2}.vhd' -f $storageAccount.PrimaryEndpoints.Blob.ToString(), $vmName.ToLower(), $diskName

$vm = Set-AzureVMOSDisk -VM $vm -Name $diskName -VhdUri $osDiskUri -CreateOption fromImage -SourceImageUri $sourceImageUri -Windows

Sample script

Here is a basic but complete script, without much error handling to reduce complexity... Please note that this script deploy a Windows machine, replace the -Windows switch for the -Linux switch in the Set-AzureVMOperatingSystem & Set-AzureVMOSDisk cmdlets if you want to deploy a Linux machine.

# We'll use a couple of variable here, fill these with your own values
$subscriptionId =  '' # Your SubscriptionId 
$storageAccountName = '' # Storage account name where your custom image is and where your VM vhd will go
$sourceImageUri = '' # custom VM image blob uri, ex: 'https://vmcapturetest.blob.core.windows.net/system/Microsoft.Compute/Images/mytemplates/template-osDisk.187d9455-535b-48b4-b10d-8370ec9bad42.vhd'
# end of custom variables


# Authenticate against Azure and cache subscriptions data
Add-AzureAccount 

# Switch to the Resource Manager mode, this will be deprecated really soon...
Switch-AzureMode AzureResourceManager

# Switch subscription
Select-AzureSubscription -SubscriptionId $subscriptionId

# Get the storage account
$storageAccount = Get-AzureStorageAccount -Name $storageAccountName

# Enable verbose output and stop on error
$VerbosePreference = 'Continue'
$ErrorActionPreference = 'Stop'

# some reserved script variables
$resourceGroupName = $storageAccount.ResourceGroupName
$location = $storageAccount.Location

$adminUsername = 'VmAdministrator'
$adminPassword = '123!SomeUnSecurePassword!098'

$vmSuffix = Get-Random -Minimum 10000 -Maximum 99999
$vmName = 'VM{0}' -f $vmSuffix
$vmSize = 'Standard_D3'
$nicName = 'VM{0}-NIC' -f $vmSuffix
$ipName = 'VM{0}-IP' -f $vmSuffix
$domName = 'vm-from-customimage-powershell-{0}' -f $vmSuffix
$vnetName = $vmName


# Create the VNET
Write-Verbose 'Creating Virtual Network'
$vnetDef = New-AzureVirtualNetwork -ResourceGroupName $resourceGroupName -Location $location -Name $vnetName -AddressPrefix '10.0.0.0/16'
Write-Verbose 'Adding subnet to Virtual Network'
$vnet = $vnetDef | Add-AzureVirtualNetworkSubnetConfig -Name 'Subnet-1' -AddressPrefix '10.0.0.0/24' | Set-AzureVirtualNetwork

# Create the NIC
Write-Verbose 'Creating Public IP'
$pip = New-AzurePublicIpAddress -ResourceGroupName $resourceGroupName -Location $location -Name $ipName -DomainNameLabel $domName -AllocationMethod Dynamic
Write-Verbose 'Creating NIC'
$nic = New-AzureNetworkInterface -ResourceGroupName $resourceGroupName -Location $location -Name $nicName -PublicIpAddressId $pip.Id -SubnetId $vnet.Subnets[0].Id 

# Specify the VM name and size
Write-Verbose 'Creating VM Config'
$vm = New-AzureVMConfig -VMName $vmName -VMSize $vmSize 

# Specify local administrator account, and then add the NIC
$cred = New-Object PSCredential $adminUsername, ($adminPassword | ConvertTo-SecureString -AsPlainText -Force) # you could use Get-Credential instead to get prompted
# NOTE: if you are deploying a Linux machine, replace the -Windows switch with a -Linux switch.
$vm = Set-AzureVMOperatingSystem -VM $vm -Windows -ComputerName $vmName -Credential $cred -ProvisionVMAgent -EnableAutoUpdate
$vm = Add-AzureVMNetworkInterface -VM $vm -Id $nic.Id

# Specify the OS disk
$diskName = 'osdisk'
$osDiskUri = '{0}vhds/{1}{2}.vhd' -f $storageAccount.PrimaryEndpoints.Blob.ToString(), $vmName.ToLower(), $diskName
# NOTE: if you are deploying a Linux machine, replace the -Windows switch with a -Linux switch.
$vm = Set-AzureVMOSDisk -VM $vm -Name $diskName -VhdUri $osDiskUri -CreateOption fromImage -SourceImageUri $sourceImageUri -Windows

Write-Verbose 'Creating VM...'
$result = New-AzureVM -ResourceGroupName $resourceGroupName -Location $location -VM $vm

if($result.Status -eq 'Succeeded') {
    $result
    Write-Verbose ('VM named ''{0}'' is now ready, you can connect using username: {1} and password: {2}' -f $vmName, $adminUsername, $adminPassword)
} else {
    Write-Error 'Virtual machine was not created sucessfully.'
}

At the end you should have an output that look like this in your PowerShell window

Execution Overview of VM creation

Hope it helped you a bit in your Azure adventure.

References