{"id":61,"date":"2023-02-10T14:36:27","date_gmt":"2023-02-10T22:36:27","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/azure-vm-runtime\/?p=61"},"modified":"2024-05-06T15:35:15","modified_gmt":"2024-05-06T22:35:15","slug":"managing-vm-applications-with-azure-policies","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/azure-vm-runtime\/managing-vm-applications-with-azure-policies\/","title":{"rendered":"Managing VM Applications with Azure Policies"},"content":{"rendered":"<p>For those unacquainted with the feature, <a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/virtual-machines\/vm-applications\">VM Applications<\/a> allow you to manage applications across virtual machines and virtual machine scale sets. What exactly\u00a0<em>is<\/em> an &#8220;application&#8221;? In truth, it&#8217;s whatever you want. Typically, it&#8217;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.<\/p>\n<p>One big ask we received from our customers was the ability to manage applications via policies. This can be used for the following effects.<\/p>\n<ul>\n<li>Ensure that all virtual machines or scale sets have a particular application installed<\/li>\n<li>Ensure that all deployments of a particular application are of the most recent version<\/li>\n<\/ul>\n<p>Both are possible using a <a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/governance\/policy\/concepts\/effects#deployifnotexists\">DeployIfNotExists<\/a> Azure policy. In this post, I&#8217;ll cover how to accomplish this and point out some common pitfalls.<\/p>\n<p>Let&#8217;s say that you&#8217;ve created a VM Application named Snoopy. It&#8217;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.<\/p>\n<pre class=\"prettyprint language-json\"><code class=\"language-json\">{\r\n  \"properties\": {\r\n    \"displayName\": \"Ensure Snoopy\",\r\n    \"policyType\": \"Custom\",\r\n    \"mode\": \"All\",\r\n    \"parameters\": {},\r\n    \"policyRule\": {\r\n      \"if\": {\r\n        \"allOf\": [\r\n          {\r\n            \"field\": \"type\",\r\n            \"equals\": \"Microsoft.Compute\/virtualMachines\"\r\n          },\r\n<span class=\"ui-provider dy bri bgu crz csa csb csc csd cse csf csg csh csi csj csk csl csm csn cso csp csq csr css cst csu csv csw csx csy csz cta ctb ctc ctd cte\" dir=\"ltr\">          {\r\n\u2003\u2003           \"anyOf\": [\r\n \u00a0\u00a0\u00a0\u00a0        {\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0          \"field\": \"Microsoft.Compute\/virtualMachines\/osProfile.windowsConfiguration\",\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0          \"exists\": true\r\n \u00a0\u00a0\u00a0\u00a0        },\r\n \u00a0\u00a0\u00a0\u00a0        {\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0          \"field\": \"Microsoft.Compute\/virtualMachines\/storageProfile.osDisk.osType\",\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0          \"like\": \"Windows*\"\r\n \u00a0\u00a0\u00a0\u00a0        },\r\n \u00a0\u00a0\u00a0\u00a0        {\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0          \"field\": \"Microsoft.Compute\/imagePublisher\",\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0          \"in\": [\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0           \"MicrosoftVisualStudio\",\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0           \"MicrosoftWindowsDesktop\",\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0           \"MicrosoftWindowsServer\"\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0          ]\r\n \u00a0\u00a0\u00a0\u00a0       }\r\n\u2003\u2003        ]\r\n         }<\/span>\r\n        ]\r\n      },\r\n      \"then\": {\r\n        \"effect\": \"deployIfNotExists\",\r\n        \"details\": {\r\n          \"type\": \"Microsoft.Compute\/virtualMachines\",\r\n          \"name\": \"[field('name')]\",\r\n          \"existenceCondition\": {\r\n            \"allOf\": [\r\n              {\r\n                \"count\": {\r\n                  \"field\": \"Microsoft.Compute\/virtualMachines\/applicationProfile.galleryApplications[*]\",\r\n                  \"where\": {\r\n                    \"field\": \"Microsoft.Compute\/virtualMachines\/applicationProfile.galleryApplications[*].packageReferenceId\",\r\n                    \"equals\": \"\/subscriptions\/6C87EEF9-845B-4460-A87F-03416E2C466C\/resourceGroups\/policytest\/providers\/Microsoft.Compute\/galleries\/MyFirstGallery\/applications\/Snoopy\/versions\/1.0.0\"\r\n                  }\r\n                },\r\n                \"greater\": 0\r\n              }\r\n            ]\r\n          },\r\n          \"roleDefinitionIds\": [\r\n            \"\/providers\/Microsoft.Authorization\/roleDefinitions\/8e3af657-a8ff-443c-a75c-2fe8c4bcb635\"\r\n          ],\r\n          \"deployment\": {\r\n            \"properties\": {\r\n              \"mode\": \"incremental\",\r\n              \"template\": {\r\n                \"$schema\": \"https:\/\/schema.management.azure.com\/schemas\/2015-01-01\/deploymentTemplate.json\",\r\n                \"contentVersion\": \"1.0.0.0\",\r\n                \"parameters\": {\r\n                  \"vmName\": {\r\n                    \"type\": \"string\"\r\n                  },\r\n                  \"location\": {\r\n                    \"type\": \"string\"\r\n                  }\r\n                },\r\n                \"resources\": [\r\n                  {\r\n                    \"apiVersion\": \"2021-07-01\",\r\n                    \"type\": \"Microsoft.Compute\/virtualMachines\/VMapplications\",\r\n                    \"name\": \"<span class=\"ui-provider ec bld bhk cwz cxa cxb cxc cxd cxe cxf cxg cxh cxi cxj cxk cxl cxm cxn cxo cxp cxq cxr cxs cxt cxu cxv cxw cxx cxy cxz cya cyb cyc cyd cye\" dir=\"ltr\">[concat(parameters('vmName'),'\/Snoopy')]<\/span>\",\r\n                    \"location\": \"[parameters('location')]\",\r\n                    \"properties\": {\r\n                      \"packageReferenceId\": \"\/subscriptions\/6C87EEF9-845B-4460-A87F-03416E2C466C\/resourceGroups\/policytest\/providers\/Microsoft.Compute\/galleries\/MyFirstGallery\/applications\/Snoopy\/versions\/1.0.0\"\r\n                    }\r\n                  }\r\n                ]\r\n              },\r\n              \"parameters\": {\r\n                \"vmName\": {\r\n                  \"value\": \"[field('name')]\"\r\n                },\r\n                \"location\": {\r\n                  \"value\": \"[field('location')]\"\r\n                }\r\n              }\r\n            }\r\n          }\r\n        }\r\n      }\r\n    }\r\n  }\r\n}<\/code><\/pre>\n<p>Let&#8217;s drill into this.<\/p>\n<pre class=\"prettyprint language-json\"><code class=\"language-json\">\"if\": {\r\n        \"allOf\": [\r\n          {\r\n            \"field\": \"type\",\r\n            \"equals\": \"Microsoft.Compute\/virtualMachines\"\r\n          },\r\n<span class=\"ui-provider dy bri bgu crz csa csb csc csd cse csf csg csh csi csj csk csl csm csn cso csp csq csr css cst csu csv csw csx csy csz cta ctb ctc ctd cte\" dir=\"ltr\">          {\r\n\u2003\u2003          \"anyOf\": [\r\n \u00a0\u00a0\u00a0\u00a0       {\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0         \"field\": \"Microsoft.Compute\/virtualMachines\/osProfile.windowsConfiguration\",\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0         \"exists\": true\r\n \u00a0\u00a0\u00a0\u00a0       },\r\n \u00a0\u00a0\u00a0\u00a0       {\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0         \"field\": \"Microsoft.Compute\/virtualMachines\/storageProfile.osDisk.osType\",\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0         \"like\": \"Windows*\"\r\n \u00a0\u00a0\u00a0\u00a0       },\r\n \u00a0\u00a0\u00a0\u00a0       {\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0         \"field\": \"Microsoft.Compute\/imagePublisher\",\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0         \"in\": [\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0          \"MicrosoftVisualStudio\",\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0          \"MicrosoftWindowsDesktop\",\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0          \"MicrosoftWindowsServer\"\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0          ]\r\n \u00a0\u00a0\u00a0\u00a0       }\r\n\u2003\u2003       ]\r\n        }<\/span>\r\n        ]\r\n      }<\/code><\/pre>\n<p>In this case, I&#8217;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 &#8220;if&#8221; segment, you&#8217;ll filter the machines down to only those where you wish to install your application.<\/p>\n<pre class=\"prettyprint language-json\"><code class=\"language-json\">\"effect\": \"deployIfNotExists\"<\/code><\/pre>\n<p>Refer to the <a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/governance\/policy\/concepts\/effects#deployifnotexists\">Policy Documentation<\/a> 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&#8217;t match the goal, then a deployment is effected to reach it.<\/p>\n<pre class=\"prettyprint language-json\"><code class=\"language-json\">        \"details\": {\r\n          \"type\": \"Microsoft.Compute\/virtualMachines\",\r\n          \"name\": \"[field('name')]\",\r\n          \"existenceCondition\": {\r\n            \"allOf\": [\r\n              {\r\n                \"count\": {\r\n                  \"field\": \"Microsoft.Compute\/virtualMachines\/applicationProfile.galleryApplications[*]\",\r\n                  \"where\": {\r\n                    \"field\": \"Microsoft.Compute\/virtualMachines\/applicationProfile.galleryApplications[*].packageReferenceId\",\r\n                    \"equals\": \"\/subscriptions\/6C87EEF9-845B-4460-A87F-03416E2C466C\/resourceGroups\/policytest\/providers\/Microsoft.Compute\/galleries\/MyFirstGallery\/applications\/Snoopy\/versions\/1.0.0\"\r\n                  }\r\n                },\r\n                \"greater\": 0\r\n              }\r\n            ]\r\n          }<\/code><\/pre>\n<p>Here, we&#8217;re specifying a type of &#8220;Microsoft.Compute\/virtualMachines.&#8221; This is what we want to evaluate. For each virtual machine, we&#8217;re going to evaluate the statement below. There, the &#8220;allOf&#8221; element indicates that every property below must be true. This is where we check for the application.<\/p>\n<p>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.<\/p>\n<pre class=\"prettyprint language-ts\"><code class=\"language-ts\">Get-AzGalleryApplicationVersion -GalleryName $gallery -ResourceGroupName $rg -GalleryApplicationName \"Snoopy\" | select-object Id<\/code><\/pre>\n<p>This will provide output similar to:<\/p>\n<pre class=\"prettyprint language-ts\"><code class=\"language-ts\">Id\r\n--\r\n\/subscriptions\/6C87EEF9-845B-4460-A87F-03416E2C466C\/resourceGroups\/myrg\/providers\/Microsoft.Compute\/galleries\/MyFirstGallery\/applications\/Snoopy\/versions\/1.0.0\r\n\/subscriptions\/6C87EEF9-845B-4460-A87F-03416E2C466C\/resourceGroups\/myrg\/providers\/Microsoft.Compute\/galleries\/MyFirstGallery\/applications\/Snoopy\/versions\/2.0.0<\/code><\/pre>\n<p>Of course, it&#8217;s not difficult to determine that the resourceId is just \/subscriptions\/{subscriptionId}\/resourceGroups\/{resourceGroupName}\/providers\/Microsoft.Compute\/galleries\/{galleryName}\/applications\/{applicationName}\/versions\/{version}<\/p>\n<p>So, for the &#8220;equals&#8221; section you&#8217;ll use the resourceId of the VM Application you want to ensure is installed.<\/p>\n<pre class=\"prettyprint language-json\"><code class=\"language-json\">\"roleDefinitionIds\": [\r\n            \"\/providers\/Microsoft.Authorization\/roleDefinitions\/8e3af657-a8ff-443c-a75c-2fe8c4bcb635\"\r\n          ],<\/code><\/pre>\n<p>I&#8217;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 <a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/role-based-access-control\/built-in-roles\">here<\/a>. It&#8217;s also possible to create your own role definitions.<\/p>\n<pre class=\"prettyprint language-json\"><code class=\"language-json\">                \"resources\": [\r\n                  {\r\n                    \"apiVersion\": \"2021-07-01\",\r\n                    \"type\": \"Microsoft.Compute\/virtualMachines\/VMapplications\",\r\n                    \"name\": \"[parameters('vmName')]\",\r\n                    \"location\": \"[parameters('location')]\",\r\n                    \"properties\": {\r\n                      \"packageReferenceId\": \"\/subscriptions\/6C87EEF9-845B-4460-A87F-03416E2C466C\/resourceGroups\/policytest\/providers\/Microsoft.Compute\/galleries\/MyFirstGallery\/applications\/Snoopy\/versions\/1.0.0\"\r\n                    }\r\n                  }\r\n                ]<\/code><\/pre>\n<p>I&#8217;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&#8217;re saying to add a new VMApplication with a packageReferenceId equal to that desired above. So, what this policy really does is &#8211; if the application isn&#8217;t installed on the VM &#8211; then put it there.<\/p>\n<p><strong>Common Pitfalls<\/strong><\/p>\n<p>There are several easy pitfalls where one can make a mistake in policies. Here&#8217;s one example:<\/p>\n<pre class=\"prettyprint language-json\"><code class=\"language-json\">\"existenceCondition\": {\r\n                        \"allOf\": [\r\n                          {\r\n                            \"field\": \"Microsoft.Compute\/virtualMachines\/applicationProfile.galleryApplications[*].packageReferenceId\",\r\n                            \"equals\": \"\/subscriptions\/6C87EEF9-845B-4460-A87F-03416E2C466C\/resourceGroups\/myrg\/providers\/Microsoft.Compute\/galleries\/mygallery\/applications\/Snoopy\/versions\/1.0.0\"\r\n                          }\r\n                        ]\r\n                    },<\/code><\/pre>\n<p>From a first stab, this would\u00a0<em>seem<\/em> to work, but it won&#8217;t. What you&#8217;ll see if you try it is the VM Application will be added to the VM. Great! We&#8217;re done! Not quite. Some more testing will uncover that the VM Application is not put back if it&#8217;s removed. The reason is that you&#8217;re dealing with an array and not a single property. For this reason, you need to evaluate the count property as given above.<\/p>\n<p>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\u00a0<em>first<\/em> time. Every VM in the resource group received the application. Yeah! But then any VM\u00a0<em>added<\/em> to the resource group wouldn&#8217;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\u00a0<em>one<\/em> VM had the application, the &#8220;if&#8221; condition would evaluate to true and nothing would be installed. Yeah, it sounds obvious now, but it wasn&#8217;t back then.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>For those unacquainted with the feature, VM Applications allow you to manage applications across virtual machines and virtual machine scale sets. What exactly\u00a0is an &#8220;application&#8221;? In truth, it&#8217;s whatever you want. Typically, it&#8217;s something that runs for a long period of time on the box, such as a service. However, the definition is really up [&hellip;]<\/p>\n","protected":false},"author":103696,"featured_media":72,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1,2],"tags":[5,3,4],"class_list":["post-61","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-azure-vm-runtime","category-vm-applications","tag-azure","tag-policy","tag-vmapplications"],"acf":[],"blog_post_summary":"<p>For those unacquainted with the feature, VM Applications allow you to manage applications across virtual machines and virtual machine scale sets. What exactly\u00a0is an &#8220;application&#8221;? In truth, it&#8217;s whatever you want. Typically, it&#8217;s something that runs for a long period of time on the box, such as a service. However, the definition is really up [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/azure-vm-runtime\/wp-json\/wp\/v2\/posts\/61","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/azure-vm-runtime\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/azure-vm-runtime\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-vm-runtime\/wp-json\/wp\/v2\/users\/103696"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-vm-runtime\/wp-json\/wp\/v2\/comments?post=61"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/azure-vm-runtime\/wp-json\/wp\/v2\/posts\/61\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-vm-runtime\/wp-json\/wp\/v2\/media\/72"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/azure-vm-runtime\/wp-json\/wp\/v2\/media?parent=61"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-vm-runtime\/wp-json\/wp\/v2\/categories?post=61"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-vm-runtime\/wp-json\/wp\/v2\/tags?post=61"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}