How to Apply DSC Configurations to VMs in Azure ARM Templates

Adam Bertram

Adam Bertram

Read more posts by this author.

If you’re deploying Azure Windows virtual machines (VMs) via ARM templates and need to configure Windows, this article is for you. In this tutorial, you’re going to learn how to use the Desired State Configuration (DSC) extension for ARM templates to seamlessly deploy and configure an Azure VM Scale Set with a single template.

When you deploy a VM (or a VM scale set) in Azure, you just don’t deploy a VM. There are always many other tasks to perform like provisioning disks, networking and so on. But engineers typically also need to perform OS-level configuration like installing software, Windows features, etc. The OS level is where PowerShell and Desired State Configuration (DSC) scripts come in handy.

Most people think of an ARM template as good at deploying Azure infrastructure. ARM templates work great for deploying VMs, networking and other Azure resources but it can also invoke scripts that will automatically run on VMs to configure the VMs also.

ARM templates have optional extension handlers that allow you to extend the capabilities of ARM templates. The extension profile we’re interested in for this article is the Azure DSC extension handler.

The Azure DSC extension is an ARM template extension that downloads and runs DSC scripts on a VM that was just deployed in the ARM template. The extension is a great way to package up all of the post-configuration tasks you need to when deploying VMs with ARM templates.

For this tutorial, you’re going to learn how to invoke DSC scripts via the Azure DSC extension by executing ARM deployments via a template. You’ll be using PowerShell to invoke the templates but many of the concepts explained here will also apply when invoking ARM template deployments via other means.

Before You Start

This article is a tutorial which walks you through, step-by-step, how to perform a set of tasks. If you intend to follow along, please be sure you have the following prerequisites in place.

  • An Azure resource group to hold all of the resources you’ll be creating. This tutorial will use AzureDSCDemo.
  • An Azure storage account – The tutorial will use azuredscdemostorage
  • An Azure Key vault – You’ll use this to pull sensitive information like the VM scale set’s admin username and password in the ARM deployment. This tutorial will use a key vault called AzureDSCDemoKv. Be sure to check Azure Resource Manager for template deployment in the key vault under Access Policies to give the ARM deployment access.
  • Azure PowerShell module and authenticated – this tutorial is using v3.4
  • On Windows 10 – Creating the DSC zip file won’t work on MacOS or Linux.

You can find all of the files used for this tutorial here.

Creating the DSC Zip File

Before you can apply a DSC configuration, you must first have a DSC script to apply. For this tutorial, you’ll be using a super-simple DSC script that installs a single Windows feature.

You can see below, this DSC script ensure the Web-Server Windows feature is installed on the machine it is run on. The local machine will be the Azure VM scale set being deployed later. Go ahead and save this as iis_setup.ps1. In this example, it will be saved in my demo folder.

Configuration iis_setup {

    Param ()

    Import-DscResource -ModuleName PSDesiredStateConfiguration

    Node 'localhost'
        WindowsFeature WebServerRole
            Name = "Web-Server"
            Ensure = "Present"

Once you’ve got the DSC script created, the next step is to zip it up. Azure DSC needs this ZIP file because when creating more advanced DSC scripts, it allows you to package up dependent resources with it.

The easiest way to create the required DSC zip file is to use the Publish-AzVmDscConfiguration PowerShell cmdlet. Below you can see how to package up the DSC script creating a file called

Publish-AzVMDscConfiguration .\iis_setup.ps1 -OutputArchivePath '.\'

The Publish-AzVmDscConfiguration cmdlet not only zips up the DSC script, it also creates a file called dscmetadata.json in the archive. In this example, it will simply contain a single Modules node. If the DSC script contained any dependent modules, it would copy those from the local machine, include them in the ZIP file and add them to the Modules JSON node.


You might be tempted to zip the DSC script manually or using the Compress-Archive cmdlet. This doesn’t work! In my experience, Publish-AzVmDscConfiguration zip up the file and creates the requires dscmetada.json file.

Getting the DSC Zip File to Azure

Now that you have the DSC zip file created, you now need to get it to a place where the ARM template can eventually download it. The ARM deployment doesn’t care where the DSC package lives. As long as it can access it, it’s OK. But for this tutorial, you’ll be uploading it to an Azure storage container.

Assuming you already have a storage account created, run the below PowerShell code to create a storage container called envsetupscripts and upload the file to the storage account.

$storageAccountName = 'azuredscdemostorage'
$resourceGroupName = 'AzureDSCDemo'
$dscZipFilePath = '.\'
$storageContainerName = 'envsetupscripts'

$StorageAccount = Get-AzStorageAccount -Name $storageAccountName -ResourceGroupName $resourceGroupName

# Create, and store, new container
$StorageAccount | New-AzStorageContainer -Name $storageContainerName
$Container = $StorageAccount | Get-AzStorageContainer

# Upload single file
$Container | Set-AzStorageBlobContent -File $dscZipFilePath

If the DSC configuration can be publicly accessible, another good place to store the file is with the other files for a project like this in a GitHub repo.

Creating the Storage Account SAS Token

Because the DSC archive is stored on a private storage account, you need to provide a way for the pipeline to download it. One way to do that is to create a temporary SAS token.

$context = (Get-AzStorageAccount -ResourceGroupName '*AzureDSCDemo*' -AccountName '*azuredscdemostorage*').context
$sasToken = New-AzStorageAccountSASToken -Context $context -Service Blob -ResourceType Service,Container,Object -Permission r

For more information on creating SAS tokens, be sure to check out the How to Generate Azure SAS Tokens to Access Storage Accounts article.

Adding Key Vault Secrets

Since the ARM template that deploys the VM scale set requires defining the VMs’ admin username and password, it’s important you don’t keep those in the template itself or the parameters file as plain text. Instead, you should store them in a secure location like an Azure Key Vault.

For this tutorial, add three secrets – DefaultAdminUsername, DefaultAdminPassword (for the VMs) and the SAS token just created saved as SASToken.

$kvName = '*AzureDSCDemoKv*'
$vmAdminUserNameSec = ConvertTo-SecureString -String 'adam' -AsPlainText -Force
$vmAdminPasswordSec = ConvertTo-SecureString -String 'I like azure.' -AsPlainText -Force
$encSasToken = ConvertTo-SecureString -String 'I like azure.' -AsPlainText -Force
Set-AzKeyVaultSecret -VaultName $kvName -Name DefaultAdminUsername -SecretValue $vmAdminUserNameSec
Set-AzKeyVaultSecret -VaultName $kvName -Name DefaultAdminPassword -SecretValue $vmAdminPasswordSec
Set-AzKeyVaultSecret -VaultName $kvName -Name SASToken -SecretValue $encSasToken

Creating the ARM Template Parameters File

The ARM template parameters file is nothing fancy. This tutorial is just using it to pass in the required parameters to the template. If you’d like to learn more about parameter files, check out the Create a parameter file Microsoft docs.

There are a few things to note in this parameters file.

  • storageAccountSasToken – This is the parameter that will be filled interactively when running the ARM deployment. There is no value purposefully provided here.
  • adminUsername and adminPassword – These two parameters are filled by the deployment reading the two secrets created earlier in the Azure Key Vault.

Be sure to replace [your subscription id] with your actual Azure subscription ID.

    "$schema": "",
    "contentVersion": "",
    "parameters": {
        "storageAccountName": {
            "value": "azuredscdemostorage"
        "setupScriptContainerName": {
            "value": "envsetupscripts"
        "storageAccountSasToken": {
            "value": ""
        "vmssInstanceCount": {
            "value": 2
        "vmSize": {
            "value": "Standard_D1"
        "adminUsername": {
            "reference": {
                "keyVault": {
                    "id": "/subscriptions/[your subscription id]/resourceGroups/AzureDSCDemo/providers/Microsoft.KeyVault/vaults/AzureDSCDemoKv"
                "secretName": "DefaultAdminUsername"
        "adminPassword": {
            "reference": {
                "keyVault": {
                    "id": "/subscriptions/[your subscription id]/resourceGroups/AzureDSCDemo/providers/Microsoft.KeyVault/vaults/AzureDSCDemoKv"
                "secretName": "DefaultAdminPassword"

Creating the ARM Template

Rather than cover the entire ARM template that provisions an example VM scale set, let’s focus on what’s applicable to DSC. Below you can see a working example of how to set up an extensionProfile for a VM scale set.

There are a few important features of this code that should be pointed out.

  • forceUpdateTag – This attribute is important if you intend to make changes to the DSC configuration file and test. By default, DSC will not try to reapply new configurations. The value for this attribute doesn’t matter as long as it’s different than the last. Once you’ve done your testing, you can remove this.
  • url – This is the URL that points to the DSC zip file. The DSC extension doesn’t care where it is as long as it can download it from this URL. In this example, the DSC archive is on a private Azure storage account, is called and is in a storage container called envsetupscripts.

    The below example is using parameters but the main structure is: https://[storage_account_name]

  • script – This is the name of the DSC script contained in the DSC archive.
  • function – This is the name of the configuration inside of the DSC script file.
  • configurationUrlSasToken – This is the SAS token created earlier. This will be used by the pipeline to authenticate to the storage account to download the DSC archive.
"extensionProfile": {
    "extensions": [
            "name": "Microsoft.Powershell.DSC",
            "properties": {
                "publisher": "Microsoft.Powershell",
                "type": "DSC",
                "typeHandlerVersion": "2.9",
                "autoUpgradeMinorVersion": true,
                "forceUpdateTag": "3",
                "settings": {
                    "configuration": {
                        "url": "[concat('https://',parameters('storageAccountName'),'',parameters('setupScriptContainerName'),'/',variables('iisDSCSetupArchiveFileName'))]",
                        "script": "iis_setup.ps1",
                        "function": "iis_setup"
                "protectedSettings": {
                    "configurationUrlSasToken": "[parameters('storageAccountSasToken')]"

Deploying the Azure Resources

Once you’ve got the DSC script created, uploaded along with the ARM template and parameters file created, you’re ready to test it out.

Run the New-AzResourceGroupDeployment command providing the template, template parameter file, resource group name and the SAS token generated as shown below. This command will start the deployment and provision all the resources defined in the ARM template including running the DSC extension we’re concerned about in this tutorial.

New-AzResourceGroupDeployment -TemplateFile ./vmss.json -TemplateParameterFile ./vmss.parameters.json -ResourceGroupName AzureDSCDemo -storageAccountSasToken $sasToken -Verbose

Once the deployment is complete, you can then check any VM in the scale set and you will find that the Web-Server Windows feature is installed!


When you’re done with this tutorial, remember to remove the resource group created along with all of the resources so you don’t get charged!

Get-AzResourceGroup -Name AzureDSCDemo | Remove-AzResourceGroup


Should you run into any problems with the DSC extension, the extension logs located in the C:\WindowsAzure\Logs\Plugins\Microsoft.Powershell.DSC\ on the VMs may shed some light on what’s going on.


Subscribe to Adam the Automator

Get the latest posts delivered right to your inbox

Looks like you're offline!