Managing VM Applications with Azure Policies

Joseph Calev

For those unacquainted with the feature, VM Applications allow you to manage applications across virtual machines and virtual machine scale sets. What exactly is an “application”? In truth, it’s whatever you want. Typically, it’s something that runs for a long period of time on the box, such as a service. However, the definition is really up to you.

One big ask we received from our customers was the ability to manage applications via policies. This can be used for the following effects.

  • Ensure that all virtual machines or scale sets have a particular application installed
  • Ensure that all deployments of a particular application are of the most recent version

Both are possible using a DeployIfNotExists Azure policy. In this post, I’ll cover how to accomplish this and point out some common pitfalls.

Let’s say that you’ve created a VM Application named Snoopy. It’s a brand new application with a version of 1.0.0. You want to ensure that Snoopy is installed on every VM in your resource group. Below is the policy that will accomplish that.

{
  "properties": {
    "displayName": "Ensure Snoopy",
    "policyType": "Custom",
    "mode": "All",
    "parameters": {},
    "policyRule": {
      "if": {
        "allOf": [
          {
            "field": "type",
            "equals": "Microsoft.Compute/virtualMachines"
          },
          {
             "anyOf": [
             {
                 "field": "Microsoft.Compute/virtualMachines/osProfile.windowsConfiguration",
                 "exists": true
             },
             {
                 "field": "Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType",
                 "like": "Windows*"
             },
             {
                 "field": "Microsoft.Compute/imagePublisher",
                 "in": [
                    "MicrosoftVisualStudio",
                    "MicrosoftWindowsDesktop",
                    "MicrosoftWindowsServer"
                 ]
            }
          ]
         }
        ]
      },
      "then": {
        "effect": "deployIfNotExists",
        "details": {
          "type": "Microsoft.Compute/virtualMachines",
          "name": "[field('name')]",
          "existenceCondition": {
            "allOf": [
              {
                "count": {
                  "field": "Microsoft.Compute/virtualMachines/applicationProfile.galleryApplications[*]",
                  "where": {
                    "field": "Microsoft.Compute/virtualMachines/applicationProfile.galleryApplications[*].packageReferenceId",
                    "equals": "/subscriptions/6C87EEF9-845B-4460-A87F-03416E2C466C/resourceGroups/policytest/providers/Microsoft.Compute/galleries/MyFirstGallery/applications/Snoopy/versions/1.0.0"
                  }
                },
                "greater": 0
              }
            ]
          },
          "roleDefinitionIds": [
            "/providers/Microsoft.Authorization/roleDefinitions/8e3af657-a8ff-443c-a75c-2fe8c4bcb635"
          ],
          "deployment": {
            "properties": {
              "mode": "incremental",
              "template": {
                "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json",
                "contentVersion": "1.0.0.0",
                "parameters": {
                  "vmName": {
                    "type": "string"
                  },
                  "location": {
                    "type": "string"
                  }
                },
                "resources": [
                  {
                    "apiVersion": "2021-07-01",
                    "type": "Microsoft.Compute/virtualMachines/VMapplications",
                    "name": "[concat(parameters('vmName'),'/Snoopy')]",
                    "location": "[parameters('location')]",
                    "properties": {
                      "packageReferenceId": "/subscriptions/6C87EEF9-845B-4460-A87F-03416E2C466C/resourceGroups/policytest/providers/Microsoft.Compute/galleries/MyFirstGallery/applications/Snoopy/versions/1.0.0"
                    }
                  }
                ]
              },
              "parameters": {
                "vmName": {
                  "value": "[field('name')]"
                },
                "location": {
                  "value": "[field('location')]"
                }
              }
            }
          }
        }
      }
    }
  }
}

Let’s drill into this.

"if": {
        "allOf": [
          {
            "field": "type",
            "equals": "Microsoft.Compute/virtualMachines"
          },
          {
            "anyOf": [
            {
                "field": "Microsoft.Compute/virtualMachines/osProfile.windowsConfiguration",
                "exists": true
            },
            {
                "field": "Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType",
                "like": "Windows*"
            },
            {
                "field": "Microsoft.Compute/imagePublisher",
                "in": [
                   "MicrosoftVisualStudio",
                   "MicrosoftWindowsDesktop",
                   "MicrosoftWindowsServer"
                 ]
            }
         ]
        }
        ]
      }

In this case, I’m saying to run the policy on all Windows machines. This is necessary because a given VM Application may run on either Windows or Linux, but not both. You may have other prerequisites for your own applications, and in the “if” segment, you’ll filter the machines down to only those where you wish to install your application.

"effect": "deployIfNotExists"

Refer to the Policy Documentation on the different policy types available, but if the goal is to deploy an application, then you want a DeployIfNotExists policy. This does what the name states. If the state of the VM doesn’t match the goal, then a deployment is effected to reach it.

        "details": {
          "type": "Microsoft.Compute/virtualMachines",
          "name": "[field('name')]",
          "existenceCondition": {
            "allOf": [
              {
                "count": {
                  "field": "Microsoft.Compute/virtualMachines/applicationProfile.galleryApplications[*]",
                  "where": {
                    "field": "Microsoft.Compute/virtualMachines/applicationProfile.galleryApplications[*].packageReferenceId",
                    "equals": "/subscriptions/6C87EEF9-845B-4460-A87F-03416E2C466C/resourceGroups/policytest/providers/Microsoft.Compute/galleries/MyFirstGallery/applications/Snoopy/versions/1.0.0"
                  }
                },
                "greater": 0
              }
            ]
          }

Here, we’re specifying a type of “Microsoft.Compute/virtualMachines.” This is what we want to evaluate. For each virtual machine, we’re going to evaluate the statement below. There, the “allOf” element indicates that every property below must be true. This is where we check for the application.

In the allOf, where saying that the count of a given application must be greater than zero. To evaluate it, we examine the galleryApplications array under the applicationProfile property on the virtual machine. For each array element, the field we evaluate is packageReferenceId. This is the ARM resource Id of the VM Application. The easiest way to obtain it is via Powershell.

Get-AzGalleryApplicationVersion -GalleryName $gallery -ResourceGroupName $rg -GalleryApplicationName "Snoopy" | select-object Id

This will provide output similar to:

Id
--
/subscriptions/6C87EEF9-845B-4460-A87F-03416E2C466C/resourceGroups/myrg/providers/Microsoft.Compute/galleries/MyFirstGallery/applications/Snoopy/versions/1.0.0
/subscriptions/6C87EEF9-845B-4460-A87F-03416E2C466C/resourceGroups/myrg/providers/Microsoft.Compute/galleries/MyFirstGallery/applications/Snoopy/versions/2.0.0

Of course, it’s not difficult to determine that the resourceId is just /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/galleries/{galleryName}/applications/{applicationName}/versions/{version}

So, for the “equals” section you’ll use the resourceId of the VM Application you want to ensure is installed.

"roleDefinitionIds": [
            "/providers/Microsoft.Authorization/roleDefinitions/8e3af657-a8ff-443c-a75c-2fe8c4bcb635"
          ],

I’ll admit that the roleDefinitionIds have always seemed a bit more complex than they needed to. The one specified here is the roleDefinition for full access, but you can find a full list of them here. It’s also possible to create your own role definitions.

                "resources": [
                  {
                    "apiVersion": "2021-07-01",
                    "type": "Microsoft.Compute/virtualMachines/VMapplications",
                    "name": "[parameters('vmName')]",
                    "location": "[parameters('location')]",
                    "properties": {
                      "packageReferenceId": "/subscriptions/6C87EEF9-845B-4460-A87F-03416E2C466C/resourceGroups/policytest/providers/Microsoft.Compute/galleries/MyFirstGallery/applications/Snoopy/versions/1.0.0"
                    }
                  }
                ]

I’m going to skip most of the standard role stuff you can decipher in their own documentation and get to the meat, which is installing the necessary application. In this case, we’re saying to add a new VMApplication with a packageReferenceId equal to that desired above. So, what this policy really does is – if the application isn’t installed on the VM – then put it there.

Common Pitfalls

There are several easy pitfalls where one can make a mistake in policies. Here’s one example:

"existenceCondition": {
                        "allOf": [
                          {
                            "field": "Microsoft.Compute/virtualMachines/applicationProfile.galleryApplications[*].packageReferenceId",
                            "equals": "/subscriptions/6C87EEF9-845B-4460-A87F-03416E2C466C/resourceGroups/myrg/providers/Microsoft.Compute/galleries/mygallery/applications/Snoopy/versions/1.0.0"
                          }
                        ]
                    },

From a first stab, this would seem to work, but it won’t. What you’ll see if you try it is the VM Application will be added to the VM. Great! We’re done! Not quite. Some more testing will uncover that the VM Application is not put back if it’s removed. The reason is that you’re dealing with an array and not a single property. For this reason, you need to evaluate the count property as given above.

Another mistake is to evaluate the resource group instead of the virtual machine. I admit that a customer did this and we had to look at it for some time to figure out what was wrong. What the customer saw was that the policy would work the first time. Every VM in the resource group received the application. Yeah! But then any VM added to the resource group wouldn’t share in the joy. The application would not be installed. It turned out the reason was they were evaluating the resource group instead of the virtual machine, so if at least one VM had the application, the “if” condition would evaluate to true and nothing would be installed. Yeah, it sounds obvious now, but it wasn’t back then.

1 comment

Discussion is closed. Login to edit/delete existing comments.

  • Sai Gunaranjan 1

    Thank you Joseph for the detailed post on this topic.

    wanted to note a minor update to the arm template deploying the VM Application

    "resources": [
                      {
                        "apiVersion": "2021-07-01",
                        "type": "Microsoft.Compute/virtualMachines/VMapplications",
                        "name": "[concat(parameters('vmName'),'/Snoopy')]", 
                        "location": "[parameters('location')]",
                        "properties": {
                          "packageReferenceId": "/subscriptions/6C87EEF9-845B-4460-A87F-03416E2C466C/resourceGroups/policytest/providers/Microsoft.Compute/galleries/MyFirstGallery/applications/Snoopy/versions/1.0.0"
                        }
                      }

Feedback usabilla icon