Azure, Server administration

Automating your mundane Azure Virtual Machine Windows provisioning tasks with PowerShell DSC

6 min read

I’ve been working in various projects and helping different people with their tasks when it comes to provisioning their Azure Windows Servers or Windows 10 virtual machines. One thing that I realized, is how much time is spent post provisioning for repetitive tasks that could be done through automation. I can say that I am proud of the fact that, after discussing with me, they all start embracing Infrastructure As Code (IaC) through ARM. Well most of them that is hahaha! Side note, if you are doing ARM, checkout Bicep; this will save you so much time in writing your ARM templates and there’s great linting and code completion when used in Visual Studio Code with the extension!

But what happens post provisioning, when the resource is created? Most will login to the machine and start doing the work automation could do. In the Windows world, such automation is done through PowerShell DSC.

PowerShell DSC is not complicated, but it can be overwhelming if you do not know where to start. In this post, I go through the basics to give you a head start. If you want to create a DSC extension, I guide you on how you can do that in my DSC extension post!

Note that DSC can also be used on Linux, but this won’t be covered here.

The basics

PowerShell DSC is an Infrastructure as Code (IaC) technology that uses PowerShell to create Managed Object Format (MOF) files, which Windows Management Instrumentation (WMI) can use to configure a machine. In other words, PowerShell DSC uses PowerShell to programmatically configure your Windows-based computers1.

A basic PowerShell configuration looks like this:

Lets discuss the code above:

The configuration is called HelloWorld. In order to use any DSC resources, you need to import them. This is what is done with Import-DscResource -ModuleName PsDesiredStateConfiguration. The Node is the computer on which the configuration will be applied. That is because DSC can also be applied to computers in your network. In the node, you have the resource operations. In this case, we have a File resource operation (from the PsDesiredStateConfiguration module), named HelloWorld. It will write the content to the specified destination file.

Before running this DSC, you need to compile it to a MOF. To do that, you do:

This will create a MOF configuration for the Node localhost in the directory name of the script, i.e. C:\Scripts\HelloWorld

Now to run the DSC configuration, you use the Start-DscConfiguration cmdlet. This cmdlet will tell the Local Configuration Manager (LCM), the engine of DSC, to apply the configuration. The LCM does the work of calling the DSC resources to apply the configuration2.

Resource Operations

It is recommended to start with the resources from the PsDesiredStateConfiguration as a base. If certain resource operations aren’t available, you can look into the xPSDesiredStateConfiguration module (where the x stands for extended).

Here are the PsDesiredStateConfiguration resources for Windows and Linux available today.

Windows Linux

Example

Assume we want to download Visual Studio Code, we can use the xRemoteFile from the xPsDesiredStateConfiguration module.

As you can see here, we depend on the File resource, to make sure the directory exists before we download the file. This is done by setting the Ensure parameter to “Present”. We then set the parameters on the xRemoteFile resource to download from a specified URI into the directory specified, in this case C:\downloads.

This is a PowerShell script after all, so you can use variables. If repeat some stuff, you definitely should use variables, in order to remove repeated code. I like to declare them after my Import-Module statements.

Dependencies

You can make a DSC resource operation dependent on another. To do that, using the example above, you can see that there’s the “DependsOn” parameter. This parameter instruct DSC that in order to run this resource operation, the dependent operation needs to have ran. The parameter value is of the form [ResourceOperation]YourResourceOperationName.

Things to remember

Resources can request to reboot

When you install software that uses the MSI format, the MSI installer can set the reboot flag to true. This means that no other MSI can run until the machine is rebooted.

To tell DSC to reboot, through the LCM, you will need to add some additional configuration to configure the LCM. In your DSC Node configuration, you need to allow the LCM to reboot the machine if needed be. This is done by adding as the first instruction in your node configuration:

Once rebooted, the configuration will continue.

How does this tie into Azure?

This is the part that is really interesting for me. Using the PowerShell DSC extension on a virtual machine, we can instruct Azure to run a DSC script on a virtual machine once it is up and running. In order to apply a DSC config on a virtual machine we have 3 choices:

  1. Use the Set-AzVMDscExtension cmdlet
  2. Use az cli: az vm extension set –name Microsoft.Powershell.DSC
  3. Use ARM with the Microsoft.Compute/virtualMachines/extensions provider
    • publisher: Microsoft.Powershell
    • type: DSC

In all 3 cases, as of this writing and to my knowledge, the latest version of this extension is 2.9.

Today, I will focus on the ARM aspect.

Prepping our DSC configuration

To make the configuration ready, we need to package it. This can be done with the Publish-AzVMDscConfiguration cmdlet. Using the OutputArchivePath parameter, we can specify the cmdlet to save the result to a file on disk. This file will be zipped. The zip file will include all the modules that are not native to PowerShell and that were installed by you when you were developing your configuration. They will be copied to the machine before the DSC configuration starts. This is can be seen in the dscmetadata.json file in the zip.

ARM Provisioning

We can use the new DSL (Domain Specific Language) for IaC for Azure, Bicep, to generate the underlying ARM for this extension.

Here is an example. As Bicep is not GA yet (GA as of version 0.3, current version as of this post is 0.2), I have the ARM equivalent below.

Bicep

ARM

By default, the extension will start automatically sending information to Microsoft containing operational and reliability data about the DSC Extension3. If you wish to disable that, under the settings property, add

As you can see, I am using in the property protectedSettings, under the property configurationArguments, a parameter called UserCredentials. That parameter is a parameter declared in my DSC configuration script and is of type PSCredential. The extension will instantiate a new class of this object, and set the username and password to what I have set in my ARM as parameter. Like that, you do not have to pass in usernames and passwords. Everything is secure.

Unfortunately, as of today, the only way to run a DSC configuration in ARM is to pass a SasToken of the storage account where the DSC zip is located. You cannot pass the storage account key, like in some other extensions.

To help you quickly generate a SasToken, use the following PowerShell snippet.

This will use my current context to grab the storage account information. Fill in the blanks with the appropriate values for your environment. Note that you can also use the New-AzStorageAccountContext cmdlet to get a context.

Conclusion

As you can see, you quickly can get up and running to start automating your Windows virtual machines. If you wish, you can complement this with the Azure Automation State Configuration service which can help in your governance. The Azure Automation State Configuration service is to DSC what Azure Automation runbooks are to PowerShell scripting. In other words, in the same way that Azure Automation helps you manage PowerShell scripts, it also helps you manage DSC configurations.

By the way, I’d like to send my kudos to Adam Bertam for a great post on how to apply DSC configs on Azure. Worth a read to get another perspective.