How to create a virtual machine from a custom image using ARM and Azure PowerShell v1.0.x

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 v1.0.2 or later, I used 1.0.4
  • 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 needs 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 in the References section.

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

Another important limitation is that the image needs 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 Azure 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.

Login-AzureRmAccount

Select-AzureRmSubscription -SubscriptionId $subscriptionId

$storageAccount = Get-AzureRmStorageAccount | ? StorageAccountName -EQ $storageAccountName

We create the networking resources for the VM

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

# Create the NIC
$pip = New-AzureRmPublicIpAddress -ResourceGroupName $resourceGroupName -Location $location -Name $ipName -DomainNameLabel $domName -AllocationMethod Dynamic
$nic = New-AzureRmNetworkInterface -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-AzureRmVMOSDisk cmdlet, this is where we specify the location of the custom VM image to use.

# Specify the VM name and size
$vm = New-AzureRmVMConfig -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 the -Linux switch and remove the -EnableAutoUpdate switch.
$vm = Set-AzureRmVMOperatingSystem -VM $vm -Windows -ComputerName $vmName -Credential $cred -ProvisionVMAgent -EnableAutoUpdate
$vm = Add-AzureRmVMNetworkInterface -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-AzureRmVMOSDisk -VM $vm -Name $diskName -VhdUri $osDiskUri -CreateOption fromImage -SourceImageUri $sourceImageUri -Windows

$result = New-AzureRmVM -ResourceGroupName $resourceGroupName -Location $location -VM $vm

Sample script

Here is a basic but complete script, without much error handling to reduce complexity... Please note that this script deploys a Windows machine, replace the -Windows switch for the -Linux switch in the Set-AzureRmVMOperatingSystem & Set-AzureRmVMOSDisk 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 subscription data
Login-AzureRmAccount

# Switch subscription
Select-AzureRmSubscription -SubscriptionId $subscriptionId

# Get the storage account
$storageAccount = Get-AzureRmStorageAccount | ? StorageAccountName -EQ $storageAccountName

if(-not $storageAccount) {  
    throw "Unable to find storage account '$storageAccountName'. Cannot continue."
}

# 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-AzureRmVirtualNetwork -ResourceGroupName $resourceGroupName -Location $location -Name $vnetName -AddressPrefix '10.0.0.0/16'
Write-Verbose 'Adding subnet to Virtual Network'  
$vnet = $vnetDef | Add-AzureRmVirtualNetworkSubnetConfig -Name 'Subnet-1' -AddressPrefix '10.0.0.0/24' | Set-AzureRmVirtualNetwork

# Create the NIC
Write-Verbose 'Creating Public IP'  
$pip = New-AzureRmPublicIpAddress -ResourceGroupName $resourceGroupName -Location $location -Name $ipName -DomainNameLabel $domName -AllocationMethod Dynamic
Write-Verbose 'Creating NIC'  
$nic = New-AzureRmNetworkInterface -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-AzureRmVMConfig -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-AzureRmVMOperatingSystem -VM $vm -Windows -ComputerName $vmName -Credential $cred -ProvisionVMAgent -EnableAutoUpdate
$vm = Add-AzureRmVMNetworkInterface -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-AzureRmVMOSDisk -VM $vm -Name $diskName -VhdUri $osDiskUri -CreateOption fromImage -SourceImageUri $sourceImageUri -Windows

Write-Verbose 'Creating VM...'  
$result = New-AzureRmVM -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 successfully.'
}

Hope it helped you a bit in your Azure adventure.

References

I am the proud father of two little gems. A beer & wine enthusiasm. For everything else, I work and play with Azure at day, and I am an Azure MVP & Advisor at night.
Montreal, Canada