Azure Policy Guest Configuration – Using Tags for Configuration of Features

The preview for being able to audit and correct settings inside of a virtual machine using Azure Policy Guest Configuration is available now, this post explores deploying settings into the .

Recently on a POC with a customer we looked at configuring Windows Features using Guest Configuration. The ask was if a could have the required features installed based on the value of a tag.

Since Guest Configuration has a nice feature where it can take in parameters and consume these as part of the configuration it should be possible to have the value of these parameters come from something such as a tag which Azure Policy understands. Let's go through the steps involved in completing this.

Before I start – this solution works ok in my environment, but the DSC resources used are considered experimental so before deploying make sure you do your due diligence!

Getting Started

Azure Guest Configuration Policy is based on DSC version 3 which is designed to run on PowerShell 7 so you should have that installed. If you need help setting up the development environment you can follow the instructions here or just see below.

You can clone these files from my GitHub repository – all the script examples are included in the files you download.

Install the GuestConfiguration and PsDscResources modules – these enable us to build the package which is going to be used by the policy.

Install-Module GuestConfiguration -Verbose -Force
Install-Module PSDscResources -Verbose -Force

You will also need to create an Azure Account to hold the package, the virtual machine should have access to be able to download from this account. You can use PowerShell to create a new resource group and account. Remember to update the names and locations to suit your subscription.


$resourceGroupName = “resource group name” # Update with your own details
$storageAccountName = “ account name” # Update with your own details
$location = “australiaeast” # Update with your own details

New-AzResourceGroup -Location $location -Name $resourceGroupName -Force
New-AzStorageAccount -ResourceGroupName $resourceGroupName `
-StorageAccountName $storageAccountName `
-Location $location `
-SkuName Standard_LRS `
-Kind StorageV2 `
-EnableHttpsTrafficOnly $true `
-MinimumTlsVersion TLS1_2
$ctx = (Get-AzStorageAccount -ResourceGroupName $resourceGroupName -Name $storageAccountName).Context
New-AzStorageContainer -Name dsc -Context $ctx -Permission Off

Machines which are going to be configured to use Guest Configuration will also need to have the extension automatically installed. To do that we assign the “Deploy prerequisites to enable Guest Configuration policies on virtual machines” built-in policy to the resource group.

$assignment = New-AzPolicyAssignment -Name deployGCPre `
-Location $location `
-Scope (Get-AzResourceGroup -Name $resourceGroupName).ResourceId `
-PolicySetDefinition (Get-AzPolicySetDefinition -Id ‘/providers/Microsoft.Authorization/policySetDefinitions/12794019-7a00-42cf-95c2-882eed337cc8') `

Finally, we assign permission to the policy to be able to deploy the resources needed for Guest Configuration.

New-AzRoleAssignment -Scope (Get-AzResourceGroup -Name $resourceGroupName).ResourceId `
-RoleDefinitionName ‘Contributor' `
-ObjectId $assignment.Identity.principalId

In the files cloned from GitHub there is a folder called cFeatureSet. It is a DSC resource which allows you to install Windows Features – it must be copied to one of the PowerShell module locations for PowerShell 7 (you can check the value of $env:PSModulePath to find one of the locations).

Copy-Item .cFeatureSet $env:PSModulePath.Split(“;”)[0] -Force -Recurse

DSC Configuration

If you have used PowerShell DSC before this bit is easy – we create a DSC configuration to install some features using the custom module. The different part if how we input the parameters. When generating the MOF file, it doesn't matter what value we put in for the feature name value as it is going to be replaced by the tag values in our policy. This is why I have just put its value as “replaced” in the configuration.

The file configuration.ps1 contains a simple configuration – you can execute it and it will generate the MOF file for us.

Configuration InstallFeatures {
Import-DscResource -ModuleName PSDscResources
Import-DscResource -ModuleName cFeatureSet

Node localhost {
cFeatureSet features {
FeatureName = “replaced”
Ensure = “Present”

NOTE: My VS Code and PowerShell wasn't playing nice with the custom module – the simple fix was to run the above code using PowerShell 5.1 which generated the MOF file for me.

If you open the MOF file it should look like below – note how the “replaced” word is still in there. For DSC this is normally not how it works but APGC is special.


Generate the Package

The GuestConfiguration module makes the next part easy, when you execute the code below it creates a zip file with the MOF file and all required modules. This gets uploaded to the storage account created earlier and a SAS token is generated. The virtual machine uses this SAS token to access the package and download it, it also verifies the package based on its hash value. We use that token and hash value to input into our policy definition so I'll output when I run the code.

New-GuestConfigurationPackage -Name InstallFeatures `
-Configuration .InstallFeatureslocalhost.mof `
-Type AuditAndSet -Force

$content = Publish-GuestConfigurationPackage -Path `
-ResourceGroupName $resourceGroupName `
-StorageAccountName $storageAccountName -StorageContainerName dsc -Force | ForEach-Object ContentUri

Write-Output “$content”
Write-Output $(Get-FileHash

Policy Definition

To remediate resources we need an Azure Policy definition and the GuestConfiguration module helps you do this. For the example here I used the code below to create the policy but then I have modified it to use the tag values as input to the guest assignment. The code is provided for you information only, you should deploy the policy in the policies folder. Have a look at DeployIfNotExists.json and update the content URI and hash value with the outputs from before.

$policyDefinition = Get-Content -Path .policiesDeployIfNotExists.json | ConvertFrom-Json

$ = $hash
$[1].equals = $hash
$ = $hash

$ = $content
$ = $content

$policyDefinition | ConvertTo-Json -Depth 100 | Out-File .policiesDeployIfNotExists.json -Force

The final step is to publish the policy to Azure – again the GuestConfiguration module takes care of this. If you want the policy published to a different scope you may have add the ManagementGroupName parameter.

Publish-GuestConfigurationPolicy -Path .policies

Testing the Policy and Configuration

Now we have a package published to a storage account, and an Azure policy ready to start checking compliance, first step is to build a virtual machine in the resource group where the Guest Configuration policy was deployed before.

Check after a few minutes of deploying, but the Guest Configuration extension should be deployed by that policy.


Now to test the configuration policy – I've applied a tag to that virtual machine with a comma separated list of the Windows features that I want installed.


I can now create a policy assignment for the new policy and assign it to the resource group. I don't need to specify any parameters, but I do need to create the managed identity when assigning so the policy has permission to deploy the Guest Assignment resource.

After I've deployed the policy and used Start-AzPolicyComplianceScan to trigger an evaluation the resource is going to return as non-compliant.


When I check on the details about why this is non-compliant I can see something really interesting – there is a field called parameterHash which must match the target value. If I take that value and decode the base64 value in there – I can see that it has taken the value of the FeatureName tag and is going to use that as the input parameter to the Guest Assignment object.


If you follow through the object which gets deployed via the policy, you can trace how these tag values end up being applied in the configuration.

All that is left to do now is to create a remediation task to correct the object.


The remediation task will go and deploy a resource called a Guest Assignment. It will contain all the details for the virtual machine to use including the URI for the package, the content hash and of course the parameter values which we supplied via the tag which will be passed to the DSC class.


DSC runs on a timer – the virtual machine is polling Azure looking for Guest Assignment resources assigned to itself – it authenticates using its managed identity to do this. When it finds an assigned resource it will download the zip file and extract it. The DSC agent then uses the Test and Set methods of the DSC resource to modify the virtual machine, in this case it runs Install-WindowsFeature to add the features specified in the tag.

Any success or failure is fed back to the Guest Assignment object which in turn is evaluated by the policy. So basically if the Guest Assignment object shows as non-compliant the policy will reflect that. It is important to understand that the policy is checking the assignment object and not the virtual machine itself.

There is more information on which log files you can examine to troubleshoot how Guest Configuration works – keep in mind that it is still in preview.

After a few minutes (the time it took to write the paragraphs above) – I can check the settings using a Run Command. Both roles are now installed.


So that is how I've been able to use Guest Configuration to dynamically manage settings within a virtual machine.

Just a couple of caveats about doing this:

  • Many resources may not work out of the box with DSC version 3. It uses PowerShell Core so modules which rely on Windows PowerShell modules (e.g. ServerManager, Networking) may not work. This is why I am using a custom class based resource (available in my GitHub repository)
  • The DSC resource in this demo isn't production ready – e.g. it won't know how to remove features and doesn't give meaningful information when it runs the Get method. If I get time to fix it I'll update this blog.
  • If you have issues raise an issue in the GitHub repo and I'll see if I can help.
  • My tests are small in a clean environment so ensure that you test appropriately.


The sample are not supported under any Microsoft standard support program or service. The sample are provided AS IS without warranty of any kind. Microsoft further disclaims all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. The entire risk arising out of the use or performance of the sample and documentation remains with you. In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the scripts be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the sample scripts or documentation, even if Microsoft has been advised of the possibility of such damages.


This article was originally published by Microsoft's Networking Blog. You can find the original article here.