5

Auditing Azure VM Operating Systems

Managing a growing Azure environment often means dealing with multiple subscriptions, distributed teams, and a wide range of virtual machine…

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.

🛠️ 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:

  1. Connects to Azure using the current identity (interactive or Managed Identity)
  2. Retrieves all subscriptions accessible to that identity
  3. Iterates through each subscription to list all VMs
  4. Extracts each VM’s OS image details (Publisher, Offer, Sku, Version)
  5. Compares the OS against a list of unsupported image URNs
  6. 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

os auditing security compliance vm azure

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

Simon

Cloud Engineer focused on Azure, Terraform & PowerShell. Passionate about automation, efficient solutions, and sharing real-world cloud projects and insights. ⸻ Let me know if you’d like to make it more casual, technical, or personalized.

Leave a Reply

Your email address will not be published. Required fields are marked *