Azure Proactive Resiliency Library v2
Tools Glossary GitHub GitHub Issues Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

virtualMachines

Summary

RecommendationImpactCategoryAutomation AvailableIn Azure Advisor
Run production workloads on two or more VMs using VMSS FlexHighHigh AvailabilityYesNo
Deploy VMs across Availability ZonesHighHigh AvailabilityYesYes
Migrate VMs using availability sets to VMSS FlexHighHigh AvailabilityYesNo
Replicate VMs using Azure Site RecoveryMediumDisaster RecoveryYesYes
Use Managed Disks for VM disksHighHigh AvailabilityYesYes
Host database data on a data diskLowScalabilityYesNo
Backup VMs with Azure Backup serviceMediumDisaster RecoveryYesYes
Review VMs in stopped stateLowGovernanceYesNo
Enable Accelerated Networking (AccelNet)MediumScalabilityYesYes
When AccelNet is enabled, you must manually update the GuestOS NIC driverLowGovernanceNoNo
VMs should not have a Public IP directly associatedMediumSecurityYesNo
VM network interfaces and associated subnets both have a Network Security Group associatedLowSecurityYesNo
IP Forwarding should only be enabled for Network Virtual AppliancesMediumSecurityYesYes
Customer DNS Servers should be configured in the Virtual Network levelLowOther Best PracticesYesNo
Shared disks should only be enabled in clustered serversMediumOther Best PracticesYesNo
Network access to the VM disk should be set to Disable public access and enable private accessLowSecurityYesNo
Ensure that your VMs are compliant with Azure PoliciesLowGovernanceYesNo
Virtual Machines should have Azure Disk Encryption or EncryptionAtHost enabledHighSecurityYesYes
Enable VM InsightsLowMonitoring and AlertingYesNo
Configure monitoring for all Azure Virtual MachinesLowMonitoring and AlertingYesNo
Use maintenance configurations for the VMsHighHigh AvailabilityYesNo
Don't use A or B-Series VMs for production needing constant full CPU performanceHighScalabilityYesNo
Mission Critical Workloads should consider using Premium or Ultra DisksHighScalabilityYesYes
Use Azure Boost VMs for Maintenance sensitive workloadMediumHigh AvailabilityNoNo
Enable Scheduled Events for Maintenance sensitive workload VMsMediumHigh AvailabilityNoNo
Use Azure Disks with Zone Redundant Storage for higher resiliency and availabilityMediumHigh AvailabilityYesYes
Reserve Compute Capacity for critical workloadsHighHigh AvailabilityYesNo
Update the Azure Linux VM AgentLowHigh AvailabilityNoNo
Reserve Compute Capacity in Disaster Recovery RegionsMediumDisaster RecoveryNoNo

Details


Run production workloads on two or more VMs using VMSS Flex

Impact:  High Category:  High Availability

APRL GUID:  273f6b30-68e0-4241-85ea-acf15ffb60bf

Description:

Production VM workloads should be deployed on multiple VMs and grouped in a VMSS Flex instance to intelligently distribute across the platform, minimizing the impact of platform faults and updates.

Potential Benefits:

Enhanced fault/update resilience
Learn More:
What has changed with Flexible orchestration mode
Attach or detach a Virtual Machine to or from a Virtual Machine Scale Set

ARG Query:

Click the Azure Resource Graph tab to view the query

// Azure Resource Graph Query
// Find all VMs that are not associated with a VMSS Flex instance
resources
| where type =~ 'Microsoft.Compute/virtualMachines'
| where isnull(properties.virtualMachineScaleSet.id)
| project recommendationId="273f6b30-68e0-4241-85ea-acf15ffb60bf", name, id, tags



Deploy VMs across Availability Zones

Impact:  High Category:  High Availability

APRL GUID:  2bd0be95-a825-6f47-a8c6-3db1fb5eb387

Description:

Azure Availability Zones, within each Azure region, are tolerant to local failures, protecting applications and data against unlikely Datacenter failures by being physically separate.

Potential Benefits:

Enhanced VM resilience to failures
Learn More:
Create virtual machines in an availability zone using the Azure portal

ARG Query:

Click the Azure Resource Graph tab to view the query

// Azure Resource Graph Query
// Find all VMs that are not assigned to a Zone
Resources
| where type =~ 'Microsoft.Compute/virtualMachines'
| where location in~ ("australiaeast", "brazilsouth", "canadacentral", "centralindia", "centralus", "eastasia", "eastus", "eastus2", "francecentral", "germanywestcentral", "israelcentral", "italynorth", "japaneast", "japanwest", "koreacentral", "mexicocentral", "newzealandnorth", "northeurope", "norwayeast", "polandcentral", "qatarcentral", "southafricanorth", "southcentralus", "southeastasia", "spaincentral", "swedencentral", "switzerlandnorth", "uaenorth", "uksouth", "westeurope", "westus2", "westus3", "usgovvirginia", "chinanorth3")
| where isnull(zones)
| project recommendationId="2bd0be95-a825-6f47-a8c6-3db1fb5eb387", name, id, tags, param1="No Zone"


Migrate VMs using availability sets to VMSS Flex

Impact:  High Category:  High Availability

APRL GUID:  a8d25876-7951-b646-b4e8-880c9031596b

Description:

While availability sets are not scheduled for immediate deprecation, they are planned to be deprecated in the future. Migrate workloads from VMs to VMSS Flex for deployment across zones or within the same zone across different fault domains (FDs) for better reliability.

Potential Benefits:

Enhances reliability and future-proofs VMs
Learn More:
Migrate deployments and resources to Virtual Machine Scale Sets in Flexible orchestration

ARG Query:

Click the Azure Resource Graph tab to view the query

// Azure Resource Graph Query
// Find all VMs using Availability Sets
resources
| where type =~ 'Microsoft.Compute/virtualMachines'
| where isnotnull(properties.availabilitySet)
| project recommendationId = "a8d25876-7951-b646-b4e8-880c9031596b", name, id, tags, param1=strcat("availabilitySet: ",properties.availabilitySet.id)



Replicate VMs using Azure Site Recovery

Impact:  Medium Category:  Disaster Recovery

APRL GUID:  cfe22a65-b1db-fd41-9e8e-d573922709ae

Description:

Replicating Azure VMs via Site Recovery entails continuous, asynchronous disk replication to a target region. Recovery points are generated every few minutes, ensuring a Recovery Point Objective (RPO) in minutes.

Potential Benefits:

Minimize downtime in disasters
Learn More:
Resiliency checklist for Virtual Machines
Run a test failover (disaster recovery drill) to Azure

ARG Query:

Click the Azure Resource Graph tab to view the query

// Azure Resource Graph Query
// Find all VMs that do NOT have replication with ASR enabled
resources
| where type =~ "Microsoft.Compute/virtualMachines"
| extend securityType = iif(isnull(properties.securityProfile.securityType), "Standard", properties.securityProfile.securityType)
| where securityType !in~ ("TrustedLaunch", "ConfidentialVM")
| project id, vmIdForJoin = tolower(id), name, tags
| join kind = leftouter (
    recoveryservicesresources
    | where type =~ "Microsoft.RecoveryServices/vaults/replicationFabrics/replicationProtectionContainers/replicationProtectedItems"
        and properties.providerSpecificDetails.dataSourceInfo.datasourceType =~ "AzureVm"
    | project vmResourceId = tolower(properties.providerSpecificDetails.dataSourceInfo.resourceId)
    )
    on $left.vmIdForJoin == $right.vmResourceId
| where isempty(vmResourceId)
| project recommendationId = "cfe22a65-b1db-fd41-9e8e-d573922709ae", name, id, tags


Use Managed Disks for VM disks

Impact:  High Category:  High Availability

APRL GUID:  122d11d7-b91f-8747-a562-f56b79bcfbdc

Description:

Azure is retiring unmanaged disks on September 30, 2025. Users should plan the migration to avoid disruptions and maintain service reliability.

Potential Benefits:

Avoid retirement disruption, enhance reliability
Learn More:
Migrate your Azure unmanaged disks by Sep 30, 2025
Migrate Windows VM from unmanaged disks to managed disks
Migrate Linux VM from unmanaged disks to managed disks

ARG Query:

Click the Azure Resource Graph tab to view the query

// Azure Resource Graph Query
// Find all VMs that are not using Managed Disks
Resources
| where type =~ 'Microsoft.Compute/virtualMachines'
| where isnull(properties.storageProfile.osDisk.managedDisk)
| project recommendationId = "122d11d7-b91f-8747-a562-f56b79bcfbdc", name, id, tags



Host database data on a data disk

Impact:  Low Category:  Scalability

APRL GUID:  4ea2878f-0d69-8d4a-b715-afc10d1e538e

Description:

A data disk is a managed disk attached to a virtual machine for storing database or other essential data. These disks are SCSI drives labeled as per choice.

Potential Benefits:

Enhances performance, recovery, migration flexibility
Learn More:
Introduction to Azure managed disks - Data disks
Azure managed disk types

ARG Query:

Click the Azure Resource Graph tab to view the query

// Azure Resource Graph Query
// Find all VMs that only have OS Disk
Resources
| where type =~ 'Microsoft.Compute/virtualMachines'
| where array_length(properties.storageProfile.dataDisks) < 1
| project recommendationId = "4ea2878f-0d69-8d4a-b715-afc10d1e538e", name, id, tags



Backup VMs with Azure Backup service

Impact:  Medium Category:  Disaster Recovery

APRL GUID:  1981f704-97b9-b645-9c57-33f8ded9261a

Description:

Enable backups for your virtual machines with Azure Backup to secure and quickly recover your data. This service offers simple, secure, and cost-effective solutions for backing up and recovering data from the Microsoft Azure cloud.

Potential Benefits:

Secure data recovery and backup
Learn More:
What is the Azure Backup service?

ARG Query:

Click the Azure Resource Graph tab to view the query

// Azure Resource Graph Query
// Find all VMs that do NOT have Backup enabled
// Run query to see results.
resources
| where type =~ 'Microsoft.Compute/virtualMachines'
| project name, id, tags
| join kind=leftouter (
    recoveryservicesresources
    | where type =~ 'Microsoft.RecoveryServices/vaults/backupFabrics/protectionContainers/protectedItems'
    | where properties.dataSourceInfo.datasourceType =~ 'Microsoft.Compute/virtualMachines'
    | project idBackupEnabled=properties.sourceResourceId
    | extend name=strcat_array(array_slice(split(idBackupEnabled, '/'), 8, -1), '/')
) on name
| where isnull(idBackupEnabled)
| project-away idBackupEnabled
| project-away name1
| project recommendationId = "1981f704-97b9-b645-9c57-33f8ded9261a", name, id, tags
| order by id asc



Review VMs in stopped state

Impact:  Low Category:  Governance

APRL GUID:  98b334c0-8578-6046-9e43-b6e8fce6318e

Description:

Azure Virtual Machines (VM) instances have various states, like provisioning and power states. A non-running VM may indicate issues or it being unnecessary, suggesting removal could help cut costs.

Potential Benefits:

Reduce costs by removing unused VMs
Learn More:
States and billing status of Azure Virtual Machines

ARG Query:

Click the Azure Resource Graph tab to view the query

// Azure Resource Graph Query
// Find all VMs that are NOT running
Resources
| where type =~ 'Microsoft.Compute/virtualMachines'
| where properties.extended.instanceView.powerState.displayStatus != 'VM running'
| project recommendationId = "98b334c0-8578-6046-9e43-b6e8fce6318e", name, id, tags



Enable Accelerated Networking (AccelNet)

Impact:  Medium Category:  Scalability

APRL GUID:  dfedbeb1-1519-fc47-86a5-52f96cf07105

Description:

Accelerated networking enables SR-IOV to a VM, greatly improving its networking performance by bypassing the host from the data path, which reduces latency, jitter, and CPU utilization for demanding network workloads on supported VM types.

Potential Benefits:

Reduces latency, jitter and CPU use
Learn More:
Accelerated Networking (AccelNet) overview

ARG Query:

Click the Azure Resource Graph tab to view the query

// Azure Resource Graph Query
// Find all VM NICs that do not have Accelerated Networking enabled
resources
| where type =~ 'Microsoft.Compute/virtualMachines'
| mv-expand nic = properties.networkProfile.networkInterfaces
| project name, id, tags, lowerCaseNicId = tolower(nic.id), vmSize = tostring(properties.hardwareProfile.vmSize)
| join kind = inner (
    resources
    | where type =~ 'Microsoft.Network/networkInterfaces'
    | where properties.enableAcceleratedNetworking == false
    | project nicName = split(id, "/")[8], lowerCaseNicId = tolower(id)
    )
    on lowerCaseNicId
| summarize nicNames = make_set(nicName) by name, id, tostring(tags), vmSize
| extend param1 = strcat("NicName: ", strcat_array(nicNames, ", ")), param2 = strcat("VMSize: ", vmSize)
| project recommendationId = "dfedbeb1-1519-fc47-86a5-52f96cf07105", name, id, tags, param1, param2
| order by id asc



When AccelNet is enabled, you must manually update the GuestOS NIC driver

Impact:  Low Category:  Governance

APRL GUID:  73d1bb04-7d3e-0d47-bc0d-63afe773b5fe

Description:

When Accelerated Networking is enabled, the default Azure VNet interface in GuestOS is swapped for a Mellanox, and its driver comes from a 3rd party. Marketplace images have the latest Mellanox drivers, but post-deployment, updating the driver is the user's responsibility.

Potential Benefits:

Enhanced VM network efficiency
Learn More:
Accelerated Networking (AccelNet) overview

ARG Query:

Click the Azure Resource Graph tab to view the query

// cannot-be-validated-with-arg



VMs should not have a Public IP directly associated

Impact:  Medium Category:  Security

APRL GUID:  1f629a30-c9d0-d241-82ee-6f2eb9d42cb4

Description:

For outbound internet connectivity of Virtual Machines, using NAT Gateway or Azure Firewall is recommended to enhance security and service resilience, thanks to their higher availability and SNAT ports.

Potential Benefits:

Enhanced security and service resiliency
Learn More:
Use Source Network Address Translation (SNAT) for outbound connections

ARG Query:

Click the Azure Resource Graph tab to view the query

// Azure Resource Graph Query
// Find all VMs with PublicIPs directly associated with them
Resources
| where type =~ 'Microsoft.Compute/virtualMachines'
| where isnotnull(properties.networkProfile.networkInterfaces)
| mv-expand nic=properties.networkProfile.networkInterfaces
| project name, id, tags, nicId = nic.id
| extend nicId = tostring(nicId)
| join kind=inner (
    Resources
    | where type =~ 'Microsoft.Network/networkInterfaces'
    | where isnotnull(properties.ipConfigurations)
    | mv-expand ipconfig=properties.ipConfigurations
    | extend publicIp = tostring(ipconfig.properties.publicIPAddress.id)
    | where publicIp != ""
    | project name, nicId = tostring(id), publicIp
) on nicId
| project recommendationId = "1f629a30-c9d0-d241-82ee-6f2eb9d42cb4", name, id, tags
| order by id asc



VM network interfaces and associated subnets both have a Network Security Group associated

Impact:  Low Category:  Security

APRL GUID:  82b3cf6b-9ae2-2e44-b193-10793213f676

Description:

Unless you have a specific reason, it's advised to associate a network security group to a subnet or a network interface, but not both, to avoid unexpected communication issues and troubleshooting due to potential rule conflicts between the two associations.

Potential Benefits:

Reduces communication problems
Learn More:
How network security groups filter network traffic

ARG Query:

Click the Azure Resource Graph tab to view the query

// Azure Resource Graph Query
// Provides a list of virtual machines and associated NICs that do have an NSG associated to them and also an NSG associated to the subnet.
Resources
| where type =~ 'Microsoft.Network/networkInterfaces'
| where isnotnull(properties.networkSecurityGroup)
| mv-expand ipConfigurations = properties.ipConfigurations, nsg = properties.networkSecurityGroup
| project nicId = tostring(id), subnetId = tostring(ipConfigurations.properties.subnet.id), nsgName=split(nsg.id, '/')[8]
| parse kind=regex subnetId with '/virtualNetworks/' virtualNetwork '/subnets/' subnet
    | join kind=inner (
        Resources
        | where type =~ 'Microsoft.Network/NetworkSecurityGroups' and isnotnull(properties.subnets)
        | project name, resourceGroup, subnet=properties.subnets
        | mv-expand subnet
        | project subnetId=tostring(subnet.id)
    ) on subnetId
    | project nicId
| join kind=leftouter (
    Resources
    | where type =~ 'Microsoft.Compute/virtualMachines'
    | where isnotnull(properties.networkProfile.networkInterfaces)
    | mv-expand nic=properties.networkProfile.networkInterfaces
    | project vmName = name, vmId = id, tags, nicId = nic.id, nicName=split(nic.id, '/')[8]
    | extend nicId = tostring(nicId)
) on nicId
| project recommendationId = "82b3cf6b-9ae2-2e44-b193-10793213f676", name=vmName, id = vmId, tags, param1 = strcat("nic-name=", nicName)



IP Forwarding should only be enabled for Network Virtual Appliances

Impact:  Medium Category:  Security

APRL GUID:  41a22a5e-5e08-9647-92d0-2ffe9ef1bdad

Description:

IP forwarding allows a virtual machine network interface to receive and send network traffic not destined for or originating from its assigned IP addresses.

Potential Benefits:

Enhances network appliance function
Learn More:
Enable or disable IP forwarding

ARG Query:

Click the Azure Resource Graph tab to view the query

// Azure Resource Graph Query
// Find all VM NICs that have IPForwarding enabled. This feature is usually only required for Network Virtual Appliances
Resources
| where type =~ 'Microsoft.Compute/virtualMachines'
| where isnotnull(properties.networkProfile.networkInterfaces)
| mv-expand nic=properties.networkProfile.networkInterfaces
| project name, id, tags, nicId = nic.id
| extend nicId = tostring(nicId)
| join kind=inner (
    Resources
    | where type =~ 'Microsoft.Network/networkInterfaces'
    | where properties.enableIPForwarding == true
    | project nicId = tostring(id)
) on nicId
| project recommendationId = "41a22a5e-5e08-9647-92d0-2ffe9ef1bdad", name, id, tags
| order by id asc



Customer DNS Servers should be configured in the Virtual Network level

Impact:  Low Category:  Other Best Practices

APRL GUID:  1cf8fe21-9593-1e4e-966b-779a294c0d30

Description:

Configure the DNS Server at the Virtual Network level to prevent any inconsistency across the environment.

Potential Benefits:

Ensures DNS consistency
Learn More:
Name resolution for resources in Azure virtual networks

ARG Query:

Click the Azure Resource Graph tab to view the query

// Azure Resource Graph Query
// Find all VM NICs that have DNS Server settings configured in any of the NICs
Resources
| where type =~ 'Microsoft.Compute/virtualMachines'
| where isnotnull(properties.networkProfile.networkInterfaces)
| mv-expand nic=properties.networkProfile.networkInterfaces
| project name, id, tags, nicId = nic.id
| extend nicId = tostring(nicId)
| join kind=inner (
    Resources
    | where type =~ 'Microsoft.Network/networkInterfaces'
    | project name, id, dnsServers = properties.dnsSettings.dnsServers
    | extend hasDns = array_length(dnsServers) >= 1
    | where hasDns != 0
    | project name, nicId = tostring(id)
) on nicId
| project recommendationId = "1cf8fe21-9593-1e4e-966b-779a294c0d30", name, id, tags
| order by id asc



Shared disks should only be enabled in clustered servers

Impact:  Medium Category:  Other Best Practices

APRL GUID:  3263a64a-c256-de48-9818-afd3cbc55c2a

Description:

Azure shared disks let you attach a disk to multiple VMs at once for deploying or migrating clustered applications, suitable only when a disk is shared among VM cluster members.

Potential Benefits:

Enhances clustered server performance
Learn More:
Azure Shared Disk Introduction
Enable Shared Disks

ARG Query:

Click the Azure Resource Graph tab to view the query

// Azure Resource Graph Query
// Find all Disks configured to be Shared. This is not an indication of an issue, but if a disk with this configuration is assigned to two or more VMs without a proper disk control mechanism (like a WSFC) it can lead to data loss
resources
| where type =~ 'Microsoft.Compute/disks'
| where isnotnull(properties.maxShares) and properties.maxShares >= 2
| project id, name, tags, lowerCaseDiskId = tolower(id), diskState = tostring(properties.diskState)
| join kind = leftouter (
    resources
    | where type =~ 'Microsoft.Compute/virtualMachines'
    | project osDiskVmName = name, lowerCaseOsDiskId = tolower(properties.storageProfile.osDisk.managedDisk.id)
    | join kind = fullouter (
        resources
        | where type =~ 'Microsoft.Compute/virtualMachines'
        | mv-expand dataDisks = properties.storageProfile.dataDisks
        | project dataDiskVmName = name, lowerCaseDataDiskId = tolower(dataDisks.managedDisk.id)
        )
        on $left.lowerCaseOsDiskId == $right.lowerCaseDataDiskId
    | project lowerCaseDiskId = coalesce(lowerCaseOsDiskId, lowerCaseDataDiskId), vmName = coalesce(osDiskVmName, dataDiskVmName)
    )
    on lowerCaseDiskId
| summarize vmNames = make_set(vmName) by name, id, tostring(tags), diskState
| extend param1 = strcat("DiskState: ", diskState), param2 = iif(isempty(vmNames[0]), "VMName: n/a", strcat("VMName: ", strcat_array(vmNames, ", ")))
| project recommendationId = "3263a64a-c256-de48-9818-afd3cbc55c2a", name, id, tags, param1, param2
| order by id asc



Network access to the VM disk should be set to Disable public access and enable private access

Impact:  Low Category:  Security

APRL GUID:  70b1d2be-e6c4-b54e-9959-b1b690f9e485

Description:

Recommended changing to "Disable public access and enable private access" and creating a Private Endpoint to improve security by restricting direct public access and ensuring connections are made privately, enhancing data protection and minimizing potential external threats.

Potential Benefits:

Enhances VM security and privacy
Learn More:
Restrict import/export access for managed disks using Azure Private Link

ARG Query:

Click the Azure Resource Graph tab to view the query

// Azure Resource Graph Query
// Find all Disks with "Enable public access from all networks" enabled
resources
| where type =~ 'Microsoft.Compute/disks'
| where properties.publicNetworkAccess == "Enabled"
| project id, name, tags, lowerCaseDiskId = tolower(id)
| join kind = leftouter (
    resources
    | where type =~ 'Microsoft.Compute/virtualMachines'
    | project osDiskVmName = name, lowerCaseOsDiskId = tolower(properties.storageProfile.osDisk.managedDisk.id)
    | join kind = fullouter (
        resources
        | where type =~ 'Microsoft.Compute/virtualMachines'
        | mv-expand dataDisks = properties.storageProfile.dataDisks
        | project dataDiskVmName = name, lowerCaseDataDiskId = tolower(dataDisks.managedDisk.id)
        )
        on $left.lowerCaseOsDiskId == $right.lowerCaseDataDiskId
    | project lowerCaseDiskId = coalesce(lowerCaseOsDiskId, lowerCaseDataDiskId), vmName = coalesce(osDiskVmName, dataDiskVmName)
    )
    on lowerCaseDiskId
| summarize vmNames = make_set(vmName) by name, id, tostring(tags)
| extend param1 = iif(isempty(vmNames[0]), "VMName: n/a", strcat("VMName: ", strcat_array(vmNames, ", ")))
| project recommendationId = "70b1d2be-e6c4-b54e-9959-b1b690f9e485", name, id, tags, param1
| order by id asc



Ensure that your VMs are compliant with Azure Policies

Impact:  Low Category:  Governance

APRL GUID:  c42343ae-2712-2843-a285-3437eb0b28a1

Description:

Keeping your virtual machine (VM) secure is crucial for the applications you run. This involves using various Azure services and features to ensure secure access to your VMs and the secure storage of your data, aiming for overall security of your VM and applications.

Potential Benefits:

Secure VMs and applications
Learn More:
Policy-driven governance
Azure Policy Regulatory Compliance controls for Azure Virtual Machines

ARG Query:

Click the Azure Resource Graph tab to view the query

// Azure Resource Graph Query
// Find all VMs in "Non-compliant" state with Azure Policies
policyresources
| where type =~ "Microsoft.PolicyInsights/policyStates" and properties.resourceType =~ "Microsoft.Compute/virtualMachines" and properties.complianceState =~ "NonCompliant"
| project
    policyDefinitionId = tolower(properties.policyDefinitionId),
    policyAssignmentId = tolower(properties.policyAssignmentId),
    targetResourceId = tolower(properties.resourceId)
// Join the policy definition details
| join kind = leftouter (
    policyresources
    | where type =~ "Microsoft.Authorization/policyDefinitions"
    | project policyDefinitionId = tolower(id), policyDefinitionDisplayName = properties.displayName
    )
    on policyDefinitionId
| project policyDefinitionId, policyDefinitionDisplayName, policyAssignmentId, targetResourceId
// Join the policy assignment details
| join kind = leftouter (
    policyresources
    | where type =~ "Microsoft.Authorization/policyAssignments"
    | project policyAssignmentId = tolower(id), policyAssignmentDisplayName = properties.displayName
    )
    on policyAssignmentId
| project policyDefinitionId, policyDefinitionDisplayName, policyAssignmentId, policyAssignmentDisplayName, targetResourceId
// Join the target resource details
| join kind = leftouter (
    resources
    | where type =~ "Microsoft.Compute/virtualMachines"
    | project targetResourceId = tolower(id), targetResourceIdPreservedCase = id, targetResourceName = name, targetResourceTags = tags
    )
    on targetResourceId
| project
    recommendationId = "c42343ae-2712-2843-a285-3437eb0b28a1",
    name = targetResourceName,
    id = targetResourceIdPreservedCase,
    tags = targetResourceTags,
    param1 = strcat("DefinitionName: ", policyDefinitionDisplayName),
    param2 = strcat("DefinitionID: ", policyDefinitionId),
    param3 = strcat("AssignmentName: ", policyAssignmentDisplayName),
    param4 = strcat("AssignmentID: ", policyAssignmentId)


Virtual Machines should have Azure Disk Encryption or EncryptionAtHost enabled

Impact:  High Category:  Security

APRL GUID:  f0a97179-133a-6e4f-8a49-8a44da73ffce

Description:

Consider enabling Azure Disk Encryption (ADE) for encrypting Azure VM disks using DM-Crypt (Linux) or BitLocker (Windows). Additionally, consider Encryption at host and Confidential disk encryption for enhanced data security.

Potential Benefits:

Enhances data security and integrity
Learn More:
Overview of managed disk encryption options

ARG Query:

Click the Azure Resource Graph tab to view the query

// Azure Resource Graph Query
// Provides a list of Azure VM disks without Azure Disk Encryption or encryption at host enabled
resources
| where type =~ "microsoft.compute/disks"
| project diskId = id, diskName = name, vmId = tolower(managedBy), azureDiskEncryption = iff(properties.encryptionSettingsCollection.enabled == true, true, false)
| join kind=leftouter (resources
| where type =~ "microsoft.compute/virtualmachines"
| project vmId = tolower(id), vmName = name, encryptionAtHost = iff(properties.securityProfile.encryptionAtHost == true, true, false)) on vmId
| where not(encryptionAtHost) and not(azureDiskEncryption)
| project recommendationId = 'f0a97179-133a-6e4f-8a49-8a44da73ffce', name = vmName, id =vmId, param1 = strcat('diskName:',diskName), param2 = strcat('azureDiskEncryption:',iff(azureDiskEncryption, "Enabled", "Disabled")), param3 = strcat('encryptionAtHost:',iff(encryptionAtHost, "Enabled", "Disabled"))


Enable VM Insights

Impact:  Low Category:  Monitoring and Alerting

APRL GUID:  b72214bb-e879-5f4b-b9cd-642db84f36f4

Description:

VM Insights monitors VM and scale set performance, health, running processes, and dependencies. It enhances the predictability of application performance and availability by pinpointing performance bottlenecks and network issues, and it clarifies if problems are related to other dependencies.

Potential Benefits:

Improves VM performance and health
Learn More:
Overview of VM insights
Did the extension install properly?

ARG Query:

Click the Azure Resource Graph tab to view the query

// Azure Resource Graph Query
// Check for VMs without Azure Monitoring Agent extension installed, missing Data Collection Rule or Data Collection Rule without performance enabled.
Resources
| where type == 'microsoft.compute/virtualmachines'
| project idVm = tolower(id), name, tags
| join kind=leftouter (
    InsightsResources
    | where type =~ "Microsoft.Insights/dataCollectionRuleAssociations" and id has "Microsoft.Compute/virtualMachines"
    | project idDcr = tolower(properties.dataCollectionRuleId), idVmDcr = tolower(substring(id, 0, indexof(id, "/providers/Microsoft.Insights/dataCollectionRuleAssociations/"))))
on $left.idVm == $right.idVmDcr
| join kind=leftouter (
    Resources
    | where type =~ "Microsoft.Insights/dataCollectionRules"
    | extend
        isPerformanceEnabled = iif(properties.dataSources.performanceCounters contains "Microsoft-InsightsMetrics" and properties.dataFlows contains "Microsoft-InsightsMetrics", true, false),
        isMapEnabled = iif(properties.dataSources.extensions contains "Microsoft-ServiceMap" and properties.dataSources.extensions contains "DependencyAgent" and properties.dataFlows contains "Microsoft-ServiceMap", true, false)//,
    | where isPerformanceEnabled or isMapEnabled
    | project dcrName = name, isPerformanceEnabled, isMapEnabled, idDcr = tolower(id))
on $left.idDcr == $right.idDcr
| join kind=leftouter (
    Resources
        | where type == 'microsoft.compute/virtualmachines/extensions' and (name contains 'AzureMonitorWindowsAgent' or name contains 'AzureMonitorLinuxAgent')
        | extend idVmExtension = tolower(substring(id, 0, indexof(id, '/extensions'))), extensionName = name)
on $left.idVm == $right.idVmExtension
| where isPerformanceEnabled != 1 or (extensionName != 'AzureMonitorWindowsAgent' and extensionName != 'AzureMonitorLinuxAgent')
| project recommendationId = "b72214bb-e879-5f4b-b9cd-642db84f36f4", name, id = idVm, tags, param1 = strcat('MonitoringExtension:', extensionName), param2 = strcat('DataCollectionRuleId:', idDcr), param3 = strcat('isPerformanceEnabled:', isPerformanceEnabled)



Configure monitoring for all Azure Virtual Machines

Impact:  Low Category:  Monitoring and Alerting

APRL GUID:  4a9d8973-6dba-0042-b3aa-07924877ebd5

Description:

Azure Monitor Metrics automatically receives platform metrics, but platform logs, which offer detailed diagnostics and auditing for resources and their Azure platform, need to be manually routed for collection.

Potential Benefits:

Enhanced diagnostics and auditing capability
Learn More:
Azure Monitor Agent overview

ARG Query:

Click the Azure Resource Graph tab to view the query

// Azure Resource Graph Query
// Find all Virtual Machines without diagnostic settings enabled/with diagnostic settings enabled but not configured both performance counters and event logs/syslogs.
resources
| where type =~ "microsoft.compute/virtualmachines"
| project name, id, tags, lowerCaseVmId = tolower(id)
| join kind = leftouter (
    resources
    | where type =~ "Microsoft.Compute/virtualMachines/extensions" and properties.publisher =~ "Microsoft.Azure.Diagnostics"
    | project
        lowerCaseVmIdOfExtension = tolower(substring(id, 0, indexof(id, "/extensions/"))),
        extensionType = properties.type,
        provisioningState = properties.provisioningState,
        storageAccount = properties.settings.StorageAccount,
        // Windows
        wadPerfCounters = properties.settings.WadCfg.DiagnosticMonitorConfiguration.PerformanceCounters.PerformanceCounterConfiguration,
        wadEventLogs = properties.settings.WadCfg.DiagnosticMonitorConfiguration.WindowsEventLog,
        // Linux
        ladPerfCounters = properties.settings.ladCfg.diagnosticMonitorConfiguration.performanceCounters.performanceCounterConfiguration,
        ladSyslog = properties.settings.ladCfg.diagnosticMonitorConfiguration.syslogEvents
    | extend
        // Windows
        isWadPerfCountersConfigured = iif(array_length(wadPerfCounters) > 0, true, false),
        isWadEventLogsConfigured = iif(isnotnull(wadEventLogs) and array_length(wadEventLogs.DataSource) > 0, true, false),
        // Linux
        isLadPerfCountersConfigured = iif(array_length(ladPerfCounters) > 0, true, false),
        isLadSyslogConfigured = isnotnull(ladSyslog)
    | project
        lowerCaseVmIdOfExtension,
        extensionType,
        provisioningState,
        storageAccount,
        isPerfCountersConfigured = case(extensionType =~ "IaaSDiagnostics", isWadPerfCountersConfigured, extensionType =~ "LinuxDiagnostic", isLadPerfCountersConfigured, false),
        isEventLogsConfigured = case(extensionType =~ "IaaSDiagnostics", isWadEventLogsConfigured, extensionType =~ "LinuxDiagnostic", isLadSyslogConfigured, false)
    )
    on $left.lowerCaseVmId == $right.lowerCaseVmIdOfExtension
| where isempty(lowerCaseVmIdOfExtension) or provisioningState !~ "Succeeded" or not(isPerfCountersConfigured and isEventLogsConfigured)
| extend
    param1 = strcat("DiagnosticSetting: ", iif(isnotnull(extensionType), strcat("Enabled, partially configured (", extensionType, ")"), "Not enabled")),
    param2 = strcat("ProvisioningState: ", iif(isnotnull(provisioningState), provisioningState, "n/a")),
    param3 = strcat("storageAccount: ", iif(isnotnull(storageAccount), storageAccount, "n/a")),
    param4 = strcat("PerformanceCounters: ", case(isnull(isPerfCountersConfigured), "n/a", isPerfCountersConfigured, "Configured", "Not configured")),
    param5 = strcat("EventLogs/Syslogs: ", case(isnull(isEventLogsConfigured), "n/a", isEventLogsConfigured, "Configured", "Not configured"))
| project recommendationId = "4a9d8973-6dba-0042-b3aa-07924877ebd5", name, id, tags, param1, param2, param3, param4, param5



Use maintenance configurations for the VMs

Impact:  High Category:  High Availability

APRL GUID:  52ab9e5c-eec0-3148-8bd7-b6dd9e1be870

Description:

The maintenance configuration settings let users schedule and manage updates, making sure the updates or interruptions on the VM are performed within a planned timeframe.

Potential Benefits:

Scheduled updates for VMs
Learn More:
Use maintenance configurations to control and manage the VM updates

ARG Query:

Click the Azure Resource Graph tab to view the query

// Azure Resource Graph Query
// Find VMS that do not have maintenance configuration assigned
Resources
| extend resourceId = tolower(id)
| project name, location, type, id, tags, resourceId, properties
| where type =~ 'Microsoft.Compute/virtualMachines'
| join kind=leftouter (
maintenanceresources
| where type =~ "microsoft.maintenance/configurationassignments"
| project planName = name, type, maintenanceProps = properties
| extend resourceId = tostring(maintenanceProps.resourceId)
) on resourceId
| where isnull(maintenanceProps)
| project recommendationId = "52ab9e5c-eec0-3148-8bd7-b6dd9e1be870",name, id, tags
| order by id asc



Don't use A or B-Series VMs for production needing constant full CPU performance

Impact:  High Category:  Scalability

APRL GUID:  3201dba8-d1da-4826-98a4-104066545170

Description:

A-series VMs are tailored for entry-level workloads like development and testing, including use cases such as development and test servers, low traffic web servers, and small to medium databases.

Potential Benefits:

Ensures full CPU usage for heavy tasks
Learn More:
B-series burstable virtual machine sizes

ARG Query:

Click the Azure Resource Graph tab to view the query

// Azure Resource Graph Query
// Find all VMs using A or B series families
resources
| where type == 'microsoft.compute/virtualmachines'
| where properties.hardwareProfile.vmSize contains "Standard_B" or properties.hardwareProfile.vmSize contains "Standard_A"
| project recommendationId = "3201dba8-d1da-4826-98a4-104066545170", name, id, tags, param1=strcat("vmSku: " , properties.hardwareProfile.vmSize)



Mission Critical Workloads should consider using Premium or Ultra Disks

Impact:  High Category:  Scalability

APRL GUID:  df0ff862-814d-45a3-95e4-4fad5a244ba6

Description:

Compared to Standard HDD and SSD, Premium SSD, SSD v2, and Ultra Disks offer improved performance, configurability, and higher single-instance VM uptime SLAs. The lowest SLA of all disks on a VM applies, so it is best to use Premium or Ultra Disks for the highest uptime SLA.

Potential Benefits:

Enhanced performance, cost efficiency, and uptime SLA
Learn More:
Disk type comparison and decision tree

ARG Query:

Click the Azure Resource Graph tab to view the query

// Azure Resource Graph Query
// Find all VMs that have an attached disk that is not in the Premium or Ultra sku tier.

resources
| where type =~ 'Microsoft.Compute/virtualMachines'
| extend lname = tolower(name)
| join kind=leftouter(resources
    | where type =~ 'Microsoft.Compute/disks'
    | where not(sku.tier =~ 'Premium') and not(sku.tier =~ 'Ultra')
    | extend lname = tolower(tostring(split(managedBy, '/')[8]))
    | project lname, name
    | summarize disks = make_list(name) by lname) on lname
| where isnotnull(disks)
| project recommendationId = "df0ff862-814d-45a3-95e4-4fad5a244ba6", name, id, tags, param1=strcat("AffectedDisks: ", disks)



Use Azure Boost VMs for Maintenance sensitive workload

Impact:  Medium Category:  High Availability

APRL GUID:  9ab499d8-8844-424d-a2d4-8f53690eb8f8

Description:

If the workload is Maintenance sensitive, consider Azure Boost compatible VMs. Azure Boost is designed to lessen the impact on customers when Azure maintenance activities occur on the host.

Potential Benefits:

Less maintenance impact
Learn More:
Microsoft Azure Boost
Announcing the general availability of Azure Boost

ARG Query:

Click the Azure Resource Graph tab to view the query

// under-development



Enable Scheduled Events for Maintenance sensitive workload VMs

Impact:  Medium Category:  High Availability

APRL GUID:  2de8fa5e-14f4-4c4c-857f-1520f87a629f

Description:

If your workload is Maintenance sensitive, enable Scheduled Events. This Azure Metadata Service lets your app prepare for virtual machine maintenance by providing information on upcoming events like reboots, reducing disruptions.

Potential Benefits:

Minimize downtime for VMs
Learn More:
Monitor scheduled events for your Azure VMs
Azure Metadata Service Scheduled Events for Linux VMs
Azure Metadata Service Scheduled Events for Windows VMs

ARG Query:

Click the Azure Resource Graph tab to view the query

// under-development



Use Azure Disks with Zone Redundant Storage for higher resiliency and availability

Impact:  Medium Category:  High Availability

APRL GUID:  fa0cf4f5-0b21-47b7-89a9-ee936f193ce1

Description:

Azure disks offers a zone-redundant storage (ZRS) option for workloads that need to be resilient to an entire zone being down. Due to the cross-zone data replication, ZRS disks have higher write latency when compared to the locally-redundant option (LRS), so make sure to benchmark your disks.

Potential Benefits:

Enhanced Disk resilience to failures
Learn More:
Redundancy options for managed disks

ARG Query:

Click the Azure Resource Graph tab to view the query

// Azure Resource Graph Query
// Find eligible Disks that are not zonal nor zone redundant
resources
| where type == 'microsoft.compute/disks'
| where location in~ ("australiaeast", "brazilsouth", "canadacentral", "centralindia", "centralus", "eastasia", "eastus", "eastus2", "francecentral", "germanywestcentral", "israelcentral", "italynorth", "japaneast", "japanwest", "koreacentral", "mexicocentral", "newzealandnorth", "northeurope", "norwayeast", "polandcentral", "qatarcentral", "southafricanorth", "southcentralus", "southeastasia", "spaincentral", "swedencentral", "switzerlandnorth", "uaenorth", "uksouth", "westeurope", "westus2", "westus3", "usgovvirginia", "chinanorth3")
| where sku has "Premium_LRS" or sku has "StandardSSD_LRS"
| where sku.name has_cs 'ZRS' or array_length(zones) > 0
| project recommendationId="fa0cf4f5-0b21-47b7-89a9-ee936f193ce1", name, id, tags, param1 = sku, param2 = sku.name


Reserve Compute Capacity for critical workloads

Impact:  High Category:  High Availability

APRL GUID:  302fda08-ee65-4fbe-a916-6dc0b33169c4

Description:

Azure Capacity Reservations ensure high availability for virtual machines by reserving compute capacity in advance within a specific region or availability zone. This guarantees that VMs will have the necessary resources during peak demand or maintenance events, enhancing reliability and uptime.

Potential Benefits:

Guaranteed capacity in constrained regions/zones
Learn More:
On-demand Capacity Reservation

ARG Query:

Click the Azure Resource Graph tab to view the query

// Azure Resource Graph Query
// Find all Virtual Machines not associated with a Capacity Reservation, and provide details for Capacity Reservation like vmSize, location, and zone.
resources
| where type =~ 'Microsoft.Compute/virtualMachines'
| where isnull(properties.capacityReservation)
| extend zoneValue = iff(isnull(zones), "null", zones)
| project recommendationId = "302fda08-ee65-4fbe-a916-6dc0b33169c4", name, id, tags, param1 = strcat("VmSize: ", properties.hardwareProfile.vmSize), param2 = strcat("Location: ", location), param3 = strcat("Zone: ", zoneValue)


Update the Azure Linux VM Agent

Impact:  Low Category:  High Availability

APRL GUID:  5f7e8a12-3c4f-456b-919c-2e9adff98c38

Description:

If you've installed the Azure Linux Agent or are using an endorsed distribution image, ensure your agent version is up-to-date. Some Linux distributions may disable auto-update or use older agent versions.

Potential Benefits:

Reduces complications with VM provisioning
Learn More:
How to update the Azure Linux Agent on a VM

ARG Query:

Click the Azure Resource Graph tab to view the query

// under-development


Reserve Compute Capacity in Disaster Recovery Regions

Impact:  Medium Category:  Disaster Recovery

APRL GUID:  587ca3e4-113b-4c4f-b4e0-92cd8d2065b6

Description:

On-Demand Capacity Reservations ensure recovery of virtual machines in the event of a natural disaster by reserving compute capacity in advance within a specific region or zone. This guarantees that VMs have the necessary resources during disaster recovery failover events thus reducing downtime.

Potential Benefits:

Guaranteed capacity in disaster recovery regions
Learn More:
On-demand Capacity Reservation

ARG Query:

Click the Azure Resource Graph tab to view the query

// cannot-be-validated-with-arg