Managing a growing Azure environment often means dealing with multiple subscriptions, distributed teams, and a wide range of virtual machine configurations. One critical but often overlooked aspect is the operating system (OS) version used on your VMs – especially those that are outdated or no longer supported.
That’s exactly what AzVM-OSInventory helps with: It’s a PowerShell script I built to automatically scan all accessible Azure subscriptions, retrieve all virtual machines, and compare their OS images against a predefined list of unsupported versions.
Let’s walk through what it does and how it works.
Contents
🛠️ Key Features
- Cross-subscription support Automatically iterates through all subscriptions the current identity has access to.
- Support status classification Each VM is checked against a list of URNs known to be out-of-support. You can easily customize this list to reflect your organization’s policies.
- Readable and exportable output Outputs VM data in a human-friendly console format and optionally exports to CSV for further processing.
- Automation-friendly Can be run locally or via an Azure Automation Account using a Managed Identity with reader permissions.
🧭 What the Script Does – In a Nutshell
The script performs the following steps:
- Connects to Azure using the current identity (interactive or Managed Identity)
- Retrieves all subscriptions accessible to that identity
- Iterates through each subscription to list all VMs
- Extracts each VM’s OS image details (Publisher, Offer, Sku, Version)
- Compares the OS against a list of unsupported image URNs
- Outputs a detailed list to the console, and optionally to CSV
It’s designed to be simple to run, easy to modify, and usable both locally and in Azure Automation.
🔧 Configuration: Define What “Unsupported” Means
At the top of the script, you’ll find a list called $UnsupportedOSPatterns. This is where you define which OS images you consider unsupported in your organization:
$UnsupportedOSPatterns = @(
"MicrosoftWindowsServer:WindowsServer:2012-r2-datacenter-gen2",
"Canonical:UbuntuServer:18.04-LTS",
"OpenLogic:CentOS:7.5"
)
Each entry follows the format: Publisher:Offer:Sku – which intentionally excludes the version. That way, all versions of, say, Ubuntu 18.04 are flagged, regardless of patch level.
🔁 How It Works – Behind the Scenes
1. Connect & Get Subscriptions
The script uses Connect-AzAccount to authenticate. Then it runs Get-AzSubscription to retrieve all subscriptions the user or identity can access.
For each subscription, it switches context using Set-AzContext before moving on to VM discovery.
2. Retrieve All VMs per Subscription
Within each subscription, it uses:
$vms = Get-AzVM -Status
This retrieves all VMs along with metadata like their OS type, and image reference.
3. Check the OS Image
For each VM, the script builds a string like:
"Canonical:UbuntuServer:18.04-LTS"
This is compared to the predefined unsupported list. If there’s a match, the VM is flagged as “OutOfSupport”.
4. Output Results to Console and CSV
The script writes everything to the console – with green or red text to quickly identify healthy or outdated systems. It also builds a custom object per VM and stores it in an array.
At the end, it prints a full table using Format-Table and, if the -exportCSV switch is used, writes the output to a CSV file:
.\AzVM-OSInventory.ps1 -exportCSV $true
📌 Example Output

The final summary includes the full list of VMs, their OS image data, and support status.
🛡️ Permissions Required
This script requires only read access to virtual machines:
Microsoft.Compute/virtualMachines/read
It’s a great fit for low-privileged service principals or Azure Automation with Managed Identity.
✅ Use Cases
- Compliance Audits – Ensure no unsupported OS versions are running
- Security Reviews – Older OSes often lack security updates
- Cloud Cleanups – Plan lifecycle management or migrations
- Inventory Reporting – Export OS data across all subscriptions
An Alternative Approach: Querying Azure VM Operating Systems Using KQL
In this post, I demonstrate how to audit Azure VM operating systems using PowerShell. As an alternative, you can leverage Kusto Query Language (KQL) via Azure Resource Graph to directly query VM metadata from the Azure resource management layer.
Here’s a sample KQL query that extracts key OS information along with subscription and resource group details from your VMs:
Resources
| where type == "microsoft.compute/virtualmachines"
| extend parts = split(id, "/")
| extend subscriptionId = parts[2], resourceGroupName = parts[4]
| project
name,
location,
subscriptionId,
resourceGroupName,
osType = properties.storageProfile.osDisk.osType,
osPublisher = properties.storageProfile.imageReference.publisher,
osOffer = properties.storageProfile.imageReference.offer,
osSku = properties.storageProfile.imageReference.sku,
osExactVersion = properties.storageProfile.imageReference.exactVersion,
osVersion = properties.storageProfile.imageReference.version
💭 Final Thoughts
AzVM-OSInventory is simple by design but powerful in practice. It gives you a fast way to scan your entire Azure footprint for legacy systems, without needing a third-party tool.
My take: Small automation scripts like this can fill real gaps in day-to-day cloud governance—especially when you need quick, repeatable insights from Azure.
Full Script
Link: GitHub
<#
.TITLE
AzVM-OSInventory
.SYNOPSIS
Inventory of all Azure VMs across all subscriptions with their OS types and support status.
.DESCRIPTION
This PowerShell script collects information about all virtual machines (VMs)
in all accessible Azure subscriptions, including the operating system type and version.
It also checks whether the OS is considered out of support based on a custom list.
.TAGS
PowerShell, Inventory, Azure, VirtualMachines, Compliance
.MINROLE
Reader
.PERMISSIONS
Microsoft.Compute/virtualMachines/read
.AUTHOR
Simon Vedder (info@simonvedder.com)
.VERSION
1.0
.CHANGELOG
1.0 - Initial release
1.1 - Changed support for OS ARN instead of simple patterns to be more accurate
.LASTUPDATE
2025-08-06
.NOTES
You can update the $UnsupportedOSPatterns array to match your
organization’s support policies
to reflect unsupported OS images (URN without version).
Output is also exported as CSV in the script directory.
.USAGE
Run locally or use in Automation Account with Managed Identity.
#>
# ==================== CONFIGURATION ====================
param (
[Parameter(Mandatory = $false)]
[bool]$exportCSV = $false
)
# List of OS ARNS considered out of support
# Find ARN on: https://az-vm-image.info/?cmd=--all
$UnsupportedOSPatterns = @(
"MicrosoftWindowsDesktop:Windows-10:win10-22h2-pro-g2",
"MicrosoftWindowsServer:WindowsServer:2012-r2-datacenter-gen2",
"Canonical:UbuntuServer:16.04-LTS",
"Canonical:UbuntuServer:18.04-LTS",
"OpenLogic:CentOS:6.5",
"OpenLogic:CentOS:7.5"
)
# ==================== SCRIPT EXECUTION ====================
$vmInventory = @()
#Title
Write-Host "AzVM-OSInventory" -ForegroundColor Yellow
Write-Host "by Simon Vedder"
Write-Host "- VM OS Overview & Support Status -"
Write-Host "------------------------------------"
Write-Host (Get-Date)
Write-Host ""
#Configuration Output
Write-Host "Configuration: " -ForegroundColor Yellow
$formattedList = $UnsupportedOSPatterns | ForEach-Object { " • $_" } | Out-String
Write-Host " - Defined unsupported OS:`n$formattedList"
Write-Host ""
# Login
Write-Host "Connecting to Azure..."
Connect-AzAccount | Out-Null
# Get all subscriptions
try {
Write-Host "Retrieving Azure Subscriptions..."
$subscriptions = Get-AzSubscription -ErrorAction Stop
Write-Host "Found $($subscriptions.Count) subscription(s)" -ForegroundColor Green
}
catch {
Write-Error "Error retrieving subscriptions: $($_.Exception.Message)"
exit 1
}
foreach ($sub in $subscriptions) {
Set-AzContext -SubscriptionId $sub.Id | Out-Null
Write-Host ""
Write-Host "Processing Subscription: $($sub.Name)" -ForegroundColor Cyan
Write-Host "Subscription ID: $($sub.Id)"
try {
Write-Host "Retrieving VMs..."
$vms = Get-AzVM -Status -ErrorAction Stop
Write-Host "Found $($vms.Count) VM(s)" -ForegroundColor Green
}
catch {
Write-Warning "Failed to retrieve VMs in subscription '$($sub.Name)':`
$($_.Exception.Message)"
continue
}
foreach ($vm in $vms) {
$imageRef = $vm.StorageProfile.ImageReference
$outOfSupport = $false
if (-not $imageRef) {
Write-Warning "VM '$($vm.Name)' has no ImageReference - skipping."
continue
}
$urnNoVersion = "$($imageRef.Publisher):$($imageRef.Offer):$($imageRef.Sku)"
$outOfSupport = $UnsupportedOSPatterns -contains $urnNoVersion
$vmInfo = [PSCustomObject]@{
VMName = $vm.Name
ResourceGroup = $vm.ResourceGroupName
Location = $vm.Location
Subscription = $sub.Name
SubscriptionId = $sub.Id
OSType = $vm.StorageProfile.OsDisk.OsType
OSPublisher = $vm.StorageProfile.ImageReference.Publisher
OSOffer = $vm.StorageProfile.ImageReference.Offer
OSSku = $vm.StorageProfile.ImageReference.Sku
OSVersion = $vm.StorageProfile.ImageReference.Version
OutOfSupport = $outOfSupport
}
$vmInventory += $vmInfo
$statusColor = if ($outOfSupport) { 'Red' } else { 'Green' }
Write-Host "VM: $($vm.Name) - $($urnNoVersion) - OutOfSupport: $outOfSupport" `
-ForegroundColor $statusColor
}
Write-Host "Subscription '$($sub.Name)' processing complete"
}
# ==================== OUTPUT ====================
Write-Host ""
Write-Host "======================== RESULTS ========================" `
-ForegroundColor Yellow
Write-Host "Total VMs found: $($vmInventory.Count)"
$vmInventory | Format-Table -Property `
VMName, `
OSPublisher, OSOffer, OSSku, OSVersion, OSType, `
Subscription, ResourceGroup, OutOfSupport -AutoSize
# Export CSV
if($exportCSV) {
$csvPath = "AzVM_OSInventory_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"
$vmInventory | Export-Csv -Path $csvPath -NoTypeInformation
Write-Host "Inventory exported to: $csvPath" -ForegroundColor Yellow
}
Write-Host ""
Write-Host "Script execution completed at $(Get-Date)" -ForegroundColor Yellow