Get all available patches for VMs on Azure

Managing patches across many virtual machines (VMs) in an Azure environment is critical for ensuring security and performance. In Azure, we now have a new product: Azure Update Manager. The service can help us keep our machines up-to-date. Implementing a patch schedule is an aspect of maintaining the security and stability of an IT environment. Critical and security patches are often applied to all machines as soon as possible. Updating test machines before production machines is an often-used practice for other patches. That is commonly known as a staged or phased rollout.

The problem I found is that the service does not seem to support schedules using a phased rollout out of the box. On Windows VMs, we can configure a date, and only patches available before that date will be applied. We could create two patch schedules, one for non-production and one for production, and use the same date in both schedules. The production schedule would be scheduled, for example, a week after the one for non-production. However, this feature is not supported for Linux VMs. You can, however, provide a list of specific patches to be applied. It turns out that there is no easy way to get to that list of patches.

This blog will show you how to use Azure APIs to automate the retrieval of VM information and assess patch status. The end result is a list of currently available patches for your VMs. These can be used as input for patch schedules to ensure that updates are applied in a controlled manner. The script can be extended to create a patch schedule or to perform other patch management tasks.

Before diving into the code and how it works, let’s look at why it’s important to update test machines before production machines and how a phased rollout can help.

Importance of Updating Test Machines First

  1. Risk Mitigation

    • Test machines provide a controlled environment where patches can be applied and evaluated without impacting critical production systems.
    • Testing helps identify potential issues, conflicts, or compatibility issues between the patches and existing software configurations.
  2. Validation of Application Compatibility

    • Test environments often mimic production settings, allowing for validation of how patches interact with specific applications and services unique to the organization.
  3. Verification of System Stability

    • Patches have the potential to introduce unforeseen issues. By updating test machines first, organizations can assess their systems' stability before rolling out production changes.
  4. User Acceptance Testing (UAT)

    • In some cases, patches may affect the end-user experience. Updating test machines provides an opportunity for User Acceptance Testing, ensuring that any changes are acceptable and don’t disrupt user workflows.

Example Patch Schedule

Consider a scenario where a company has a monthly patching cycle. Security and critical patches are applied daily on all machines. For other types of patches, the schedule might look something like this:

  1. Week 1: Test Environment Patching (Days 1-7)

    • Apply patches to non-production or test machines.
    • Perform thorough testing to identify and address any issues.
  2. Week 2: Staging Environment Patching (Days 8-14)

    • Apply patches to a staging environment that closely mirrors the production environment.
    • Conduct additional testing with a focus on integration and system-wide functionality.
  3. Week 3: Early Production Deployment (Days 15-21)

    • Deploy patches to a small subset of less critical production machines.
    • Monitor closely for any unexpected issues and gather feedback.
  4. Week 4: Full Production Deployment (Days 22-28)

    • If no major issues are identified, deploy patches to the production environment.
    • Monitor systems closely for any anomalies or performance issues.
  5. Week 5: Post-Deployment Evaluation (Days 29-31)

    • Conduct a post-deployment review to ensure all systems are stable.
    • Address any lingering issues and document lessons learned for future patching cycles.

This phased approach allows organizations to catch and resolve potential issues early in the process, minimizing the impact on production systems and ensuring a smoother overall update experience. It balances the need for prompt security updates with the importance of maintaining system stability and minimizing disruptions to critical business operations.

In this blog post, we’ll dissect a PowerShell script designed to fetch all available patches for VMs across various subscriptions within an Azure tenant. The script utilizes Azure REST APIs to gather information about VMs, assess their patch status, and categorize patches based on the operating system (Linux or Windows).

Authentication

The script begins by obtaining an access token using the Azure CLI. This token is needed for authenticating subsequent requests to Azure services.

$token = az account get-access-token | ConvertFrom-Json
$accessToken = $token.accessToken

The script sets up the necessary headers for HTTP requests, including the authorization token obtained in the previous step.

$headers = @{
    "Authorization" = "Bearer $accessToken"
    "Content-Type"  = "application/json"
}

Get All VMs in the Tenant

There isn’t a single rest call you can use to retrieve all VMs in your tenant in Azure. The highest level you can do that on is the subscription scope. We, therefore, retrieve a list of subscriptions using the Azure Resource Manager (ARM) API.

$subscriptionsEndpoint = "https://management.azure.com/subscriptions?api-version=2022-12-01"
$subscriptions = Invoke-RestMethod -Uri $subscriptionsEndpoint -Method Get -Headers $headers

Next, we iterate through each subscription, fetching information about VMs using the ARM API. VM details, including the operating system, are collected in the $vms array.

$vms = @()
foreach ($subscription in $subscriptions.value) {
    $subscriptionId = $subscription.subscriptionId
    Write-Output "`nFetching VMs from subscription: $($subscriptionId)"

    $url = "https://management.azure.com/subscriptions/$subscriptionId/providers/Microsoft.Compute/virtualMachines?api-version=2023-07-01"

    $vmResponse = Invoke-RestMethod -Uri $url -Method Get -Headers $headers

    foreach ($vm in $vmResponse.value) {
        Write-Output "Found VM: $($vm.name)"
        $os = $vm.properties.storageProfile.osDisk.osType
        $vms += @{ vmid = $vm.id; operatingSystem = $os }
    }
}

Get Patches

The same goes for fetching the patch status. You can’t just request that in a single go. You need to fetch the details per VM. Even worse, there is no API call to the VM endpoint to get this information. We need to construct a query for each VM to retrieve patch information using Azure Resource Manager Graph API. We categorize patches based on the operating system and store them in separate arrays.

$linuxPatches = @()
$windowsPatches = @()
foreach ($vm in $vms) {
    $body = @{
        "query" = "PatchAssessmentResources  | where type =~ 'Microsoft.Compute/virtualMachines/patchAssessmentResults/softwarePatches' and id contains '$($vm.vmid)/patchAssessmentResults/latest/softwarePatches/' | project properties, id"
    } | ConvertTo-Json -Depth 10
    
    # Construct the URL
    $url = "https://management.azure.com/providers/Microsoft.ResourceGraph/resources?api-version=2022-10-01"

    $patchResponse = Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $body

    foreach ($patch in $patchResponse.data) {
        if ($vm.operatingSystem -eq "Linux") {
            If ($linuxPatches -notcontains $patch.properties.patchName) {
                $linuxPatches += $patch.properties.patchName
            }
        }
        else {
            If ($windowsPatches -notcontains $patch.properties.patchName) {
                $windowsPatches += $patch.properties.kbId
            }
        }
    }
}

The information that we’ve now fetched can serve as input for a patch schedule. Creating that schedule is for another blog post. For now, we display the collected Linux and Windows patches.

Write-Host "`nLinux Patches"
$linuxPatches

Write-Host "`nWindows Patches"
$windowsPatches

This PowerShell script demonstrates how to leverage Azure APIs to automate the retrieval of VM information and assess patch status. The script can be extended to create a patch schedule or to perform other patch management tasks.