Azure Local – Custom Images – Deploy image from compute gallery
- Intro
- Prepare Storage Path
- Get Image definition Id
- Export disk and create image
- Refresh image version
- Cleanup
Intro
In the previous post I created a compute gallery and used custom image builder to prepare my image. Now I want to deploy the image to Azure Local from this compute gallery. This is not a task visible from the Azure portal, so we have to use some nice IaC to accomplish this task.
We will be using PowerShell and Az CLI for this task.
Prepare Storage Path
Assuming we do not have any storage paths yet, we need to deploy one. You can connect to Az CLI either from Azure portal, terminal on your local machine or from a node in your Azure Local stack.
- Type
az login --use-device-code - Set subscription context (should be the subscription your Azure Local stack is deployed to):
az account set --subscription - Now we define some parameters:
$storagepathname="Images" $path="C:\\ClusterStorage\\UserStorage_1\\Images" $subscription="" $resource_group="MyResourceGroup" $customLocName="CustomLocationName" $customLocationID=(az customlocation show --resource-group $resource_group --name $customLocName --query id -o tsv) $location="WestEurope" - Now we create the storage path:
az stack-hci-vm storagepath create --resource-group $resource_group --custom-location $customLocationID --name $storagepathname --path $path
Get Image definition Id
We need to prepare the compute gallery image definition as a managed disk for export.
First we need to get the resourceId of the image definition:
az sig image-version list --resource-group myResourceGroup --gallery-name myGallery --gallery-image-definition myImageDefinition -o table
We could also use PowerShell if prefered:
$sourceImgVer = Get-AzGalleryImageVersion -GalleryImageDefinitionName myImageDefinition -GalleryName myGallery -ResourceGroupName myResourceGroup -Name 1.0.0
Next we will be creating a disk from the image definition:
$source="/subscriptions//resourceGroups//providers/Microsoft.Compute/galleries//images//versions/"
az disk create --resource-group myResourceGroup --location WestEurope --name myManagedOSDisk --gallery-image-reference $source
Again we could use PowerShell if prefered:
$diskConfig = New-AzDiskConfig -Location WestEurope -CreateOption FromImage -GalleryImageReference @{Id = $sourceImgVer.Id}
New-AzDisk -Disk $diskConfig -ResourceGroupName myResourceGroup -DiskName myManagedOSDisk
Export disk and create image
-
Connect to Az CLI (You can use your favorite terminal on your local PC, Azure Cloud Shell or log in to a node in your Azure Local stack)
-
Type
az login --use-device-code(only if connecting from Azure Local node, otherwise just useAz login -
Set subscription context (should be the subscription your compute gallery is deployed to):
az account set --subscription -
Now we need to generate SAS before we can start export:
**DiskName="myManagedOSDisk"** **$imageSourcePath=(az disk grant-access --resource-group myResourceGroup --name $diskName --duration-in-seconds 28800 --query [accessSas] -o tsv)** -
Define variables used for next part:
$resource_group = "" $location = "WestEurope" $osType = "Windows" $imageName = "Custom-AVDIMG-11" -
Get the custom location of your Azure Local stack
$customLocationID=(az customlocation show --resource-group myResourceGroup --name "CustomLocationName" --query id -o tsv)
Note that this command will fail if Azure Local Custom Location is not in the same subscription as the compute gallery. Then use az account set to switch subscription context. -
Get the storagePathID
**$StoragePathID=**(**az stack-hci-vm storagepath show --resource-group $resource_group --custom-location $customLocationID --name $storagepathname** **--query id -o tsv**) -
Now lets create the image:
az stack-hci-vm image create --resource-group $resource_Group --custom-location $customLocationID --location $location --name $imageName --os-type $osType --image-path $imageSourcePath --storage-path-id **$StoragePathID**
If everything lines up, the deployment should complete in a few minutes – but actual copy time to Azure Local Storage Path takes more time to complete. Download time depends on download speed on internet connection where your Azure Local stack resides.
You can see the image in the Azure Portal with status “downloading” while it copies to local storage, and once finished it will be present in Azure:

Below is a script I have used in an Azure DevOps Pipeline (I use a variable group to define and input all required variables).
Task 1 (PowerShell task):
$GalleryName = $env:GalleryName
$imageDefinition = $env:imageDefinition
$resourceGroupName = $env:resourceGroupName
$RGCustomLocation = $env:RGCustomLocation
$location = $env:location
$CustomLocation = $env:CustomLocation
$AZURESUBSCRIPTION = $env:AZURESUBSCRIPTION
$StoragePathName = $env:StoragePathName
$OSType = $env:OSType
$CustomImageName = $env:CustomImageName
$ImageVMName = $env:ImageVMName
$sourceImgVer = Get-AzGalleryImageVersion -GalleryImageDefinitionName $imageDefinition -GalleryName $GalleryName -ResourceGroupName $resourceGroupName | Where-Object {$_.PublishingProfile.ExcludeFromLatest -eq $false}
$DiskName = "managedimagedisk{0}" -f $ImageVMName
$diskConfig = New-AzDiskConfig -Location $location -CreateOption FromImage -GalleryImageReference @{Id = $sourceImgVer.Id}
New-AzDisk -Disk $diskConfig -ResourceGroupName $resourceGroupName -DiskName $diskName
Task 2 (Az CLI PSCore task)
I had issues with the API version used in the newest version of stack-hci-vm does not have access to the required resource providers in Azure. Therefore, I hardcode an older version of that extension in Az CLI: az extension add --name stack-hci-vm --version 1.1.16
az config set extension.use_dynamic_install=yes_without_prompt
az extension add --name stack-hci-vm --version 1.1.16
az extension add --name customlocation
$imageSourcePath = az disk grant-access --access-level Read --resource-group $resourceGroupName --name $DiskName --duration-in-seconds 28000 --query accessSAS
$customLocationId = az customlocation show --resource-group $RGCustomLocation --name $CustomLocation --query id -o tsv
# Get Storage Path ID - suppress errors if not found
$existingImage = az stack-hci-vm image show --name $CustomImageName --resource-group $RGCustomLocation 2>nul
if($existingImage)
{
az stack-hci-vm image delete --name $(CustomImageName) --resource-group $(RGCustomLocation) --yes
Start-Sleep -Seconds 60
}
az stack-hci-vm image create --resource-group $RGCustomLocation --custom-location "$customLocationId" --location $location --name $CustomImageName --os-type $OSType --image-path "$imageSourcePath" --storage-path-id $StoragePathID
az disk revoke-access --resource-group $resourceGroupName --name $diskName
az disk delete --name $diskName --resource-group $resourceGroupName --yes
Refresh image version
Now what if we would like to update the image on Azure Local after deployed, because we got a newer version of the image in our compute gallery?
In my example above, I remove and re-create the image every time, but because the exported disk gets copied to the storage path on Azure Local, we can skip the whole proces of deploying the image all over and just use a simple proces which includes:
- Export the disk again from Compute gallery
- Generate SAS token for the disk
- Use copy tool like AzCopy (you need to download the tool) that supports SAS tokens, to copy the disk to the storage path on Azure Local
azcopy copy "INSERT_BLOB-SAS-URL_HERE" "C:\\ClusterStorage\\UserStorage_1\\Images\" - Cleanup exported disk in Azure after AzCopy completes
If we wanted to automate this proces we would do the following:
- Write a small script that runs in Azure Devops that exports the disk and generate SAS token. The token is then stored into an Azure KeyVault
- Copy methods:
- Method 1: Either use custom extension that can be deployed to one of the nodes in Azure Local stack via Azure DevOps. This custom extension should include AzCopy tool and script that retrieves SAS token from key vault and execute copy
- Method 2: Setup scheduled task on a Azure Local management server that retrieves SAS token from key vault and execute copy
I have written these automations for customers before. They are complex but in large scale environments (e.g. lots of Azure Virtual Desktop session hosts running on Azure Local), it is crucial to safe time keeping image and session hosts up to date and automation plays a central role here.
Cleanup
Remember to cleanup (revoke the SAS and delete the disk afterwards)
az disk revoke-access --resource-group $resourceGroupName --name $diskName
az disk delete --name $diskName --resource-group $resourceGroupName --yes