Posts Get VM SKU Availability in Azure Availability Zones
Post
Cancel

Get VM SKU Availability in Azure Availability Zones

Azure VM SKU capacity check in Azure Availability Zone

In my previous article I went over why and when we sometimes face capacity issues in Azure while trying to provision Virtual Machines : Understanding and Overcoming Azure VM SKU Capacity Limitations.

In this article I want to share more advanced automation to deal with that task automatically:

Scripted SKU Availability Testing

To help assess which SKUs are currently available in specific zones, you can use a PowerShell script that:

  • Lists available VM SKUs in your selected region.
  • Tests zonal provisioning via -WhatIf simulation.
  • Helps you proactively plan capacity before scaling or migrating workloads.

This script is especially helpful when preparing infrastructure for:

  • Production launches
  • Lift-and-shift migrations
  • Scaling workloads into new zones

Feel free to use and tweek according to your needs:

<#
.AUTHOR
Vukasin Terzic
https://azureis.fun
#>
function Get-AzVMSKUAvailabilityInZone {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]$Region,
[Parameter(Mandatory = $true)]
[string]$ResourceGroupName,
[Parameter(Mandatory = $false)]
[string[]]$Skus,
[Parameter(Mandatory = $false)]
[string]$ExportCsvPath,
[Parameter(Mandatory = $false)]
[switch]$CleanUpAfter
)
# Ensure Az module is available
if (-not (Get-Module -ListAvailable -Name Az.Compute)) {
Write-Error "Az.Compute module not found. Please install the Az module first."
return
}
# Create Resource Group if it doesn't exist
if (-not (Get-AzResourceGroup -Name $ResourceGroupName -ErrorAction SilentlyContinue)) {
Write-Host "Creating resource group '$ResourceGroupName' in region '$Region'..."
New-AzResourceGroup -Name $ResourceGroupName -Location $Region | Out-Null
Start-Sleep -Seconds 5 # Wait for resource group to be created
} else {
Write-Host "Using existing Resource group '$ResourceGroupName'."
}
# Create test networking resources
$vnetName = "vmtest-vnet1"
$subnetName = "vmtest-subnet1"
$vnet = Get-AzVirtualNetwork -Name $vnetName -ResourceGroupName $ResourceGroupName -ErrorAction SilentlyContinue
if (-not $vnet) {
Write-Host "Creating test VNet and Subnet..."
$subnetConfig = New-AzVirtualNetworkSubnetConfig -Name $subnetName -AddressPrefix "10.0.0.0/24"
$vnet = New-AzVirtualNetwork -Name $vnetName -ResourceGroupName $ResourceGroupName -Location $Region -AddressPrefix "10.0.0.0/16" -Subnet $subnetConfig
} else {
Write-Host "Using existing VNet '$vnetName' and Subnet '$subnetName'..."
}
$subnet = $vnet.Subnets[0]
# Check if the region is valid
Write-Host "Validating region '$Region'..."
$validRegions = Get-AzLocation | Select-Object -ExpandProperty Location
if ($Region -notin $validRegions) {
Write-Error "The specified region '$Region' is not valid. Available regions are: $($validRegions -join ', ')"
return
}
# Get available VM SKUs
Write-Host "Fetching VM SKUs for region '$Region'..."
$allSkus = Get-AzComputeResourceSku | Where-Object { $_.Locations -contains $Region -and $_.ResourceType -eq "virtualMachines" }
# Validate user-supplied SKUs
if ($Skus) {
$availableSkuNames = $allSkus.Name
$notFoundSkus = $Skus | Where-Object { $_ -notin $availableSkuNames }
if ($notFoundSkus.Count -gt 0) {
Write-Warning "The following SKUs were not found or are not available in region '$Region': $($notFoundSkus -join ', ') and will be ignored."
}
$filteredSkus = $allSkus | Where-Object { $Skus -contains $_.Name }
} else {
$filteredSkus = $allSkus
}
$results = @()
# Generate temporary random credentials
function New-RandomPassword {
param([int]$length = 16)
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%&*'
-join ((1..$length) | ForEach-Object { $chars | Get-Random })
}
$adminUsername = "vmadmin"
$adminPassword = ConvertTo-SecureString (New-RandomPassword) -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential ($adminUsername, $adminPassword)
foreach ($sku in $filteredSkus) {
$zones = $sku.LocationInfo | Where-Object { $_.Location -eq $Region } | Select-Object -ExpandProperty Zones
if ($zones) {
foreach ($zone in $zones) {
$vmName = "vmtest-$($sku.Name.ToLower())-z$zone"
$nicName = "$vmName-nic"
try {
Write-Host "Creating NIC for $vmName ..."
$nic = New-AzNetworkInterface -Name $nicName -ResourceGroupName $ResourceGroupName -Location $Region -SubnetId $subnet.Id -ErrorAction Stop
$vmConfig = New-AzVMConfig -VMName $vmName -VMSize $sku.Name -Zone $zone
$vmConfig = Set-AzVMOperatingSystem -VM $vmConfig -Windows -ComputerName $vmName -Credential $cred -ProvisionVMAgent -EnableAutoUpdate
$vmConfig = Set-AzVMSourceImage -VM $vmConfig -PublisherName "MicrosoftWindowsServer" -Offer "WindowsServer" -Skus "2019-Datacenter" -Version "latest"
$vmConfig = Add-AzVMNetworkInterface -VM $vmConfig -Id $nic.Id
#Disable boot diagnostics by not attaching a diagnostics profile. Otherwise it will require storage account.
Write-Host "Disabling boot diagnostics for $vmName ..."
$vmConfig.DiagnosticsProfile = $null
Write-Host "Testing provisioning of $vmName in zone $zone using WhatIf..."
$whatIf = New-AzVM -ResourceGroupName $ResourceGroupName -Location $Region -VM $vmConfig -WhatIf -ErrorAction Stop
$results += [PSCustomObject]@{
VMSize = $sku.Name
Zone = $zone
AvailableInZone = $true
ProvisionableNow = $true
}
}
catch {
Write-Warning "Provisioning test failed for $($sku.Name) in zone $zone : $($_.Exception.Message)"
$results += [PSCustomObject]@{
VMSize = $sku.Name
Zone = $zone
AvailableInZone = $true
ProvisionableNow = $false
}
}
# Cleanup NIC
Write-Host "Cleaning up NIC: $nicName"
Remove-AzNetworkInterface -Name $nicName -ResourceGroupName $ResourceGroupName -Force -ErrorAction SilentlyContinue
}
}
else {
$results += [PSCustomObject]@{
VMSize = $sku.Name
Zone = "-"
AvailableInZone = $false
ProvisionableNow = $false
}
}
}
if ($ExportCsvPath) {
Write-Host "Exporting results to $ExportCsvPath"
$results | Export-Csv -Path $ExportCsvPath -NoTypeInformation
}
# Optional cleanup of entire resource group
if ($CleanUpAfter) {
Write-Host "Cleaning up entire resource group '$ResourceGroupName'..."
Remove-AzResourceGroup -Name $ResourceGroupName -Force -AsJob
} else {
Write-Host "Test completed. Resource group '$ResourceGroupName' retained for review."
}
return $results
}
# Example usage
Get-AzVMSKUAvailabilityInZone -Region "eastus2" -ResourceGroupName "TempRG1" -Skus "Standard_D4s_v5", "Standard_D8s_v5" -ExportCsvPath "C:\Temp\Report.csv" -CleanUpAfter

The result you will receive will look like this:

PowerShell check Azure VM SKU Availability and Capacity

In case you have any questions feel free to reach out.

Thanks for reading and keep clouding around.

Vukasin Terzic

This post is licensed under CC BY 4.0