Turning on Windows features using Powershell DSC extension and Azure CLI

Posted on May 26, 2016

Software Engineer, AzureCAT patterns & practices

DevOps engineers who want to use custom script extensions on Azure have a variety of techniques available, such as using Desired State Configuration (DSC) or custom Powershell scripts. But what options do you have if you want to perform a simple task such as installing IIS Server on existing Windows Virtual Machines in an Azure Resource Group? You have a variety of possible approaches varying in complexity - such as using ARM templates, Azure Automation DSC and other methods. But, in some cases, a script may be a better approach - especially instances where you’re not setting up a production level infrastructure that needs to be maintained and updated.

For this latter scenario, DSC automation using a third party approach such as Chef, Puppet - or internal Azure tools, e.g. Azure automation, may serve your needs better. These solutions employ a server-based pull model for managing the desired configuration state of installed software on an ongoing basis.

My scenario is quite straightforward, and virtually a one-time configuration task that I’m looking to accomplish with minimum of fuss. Here’s the scenario:

  • A few virtual machines are deployed in an Azure Resource Group, using a CLI script.
  • Virtual machines are part of a three-tier architecture that includes web tier, business tier and a database tier.
  • Network Security Group (NSG) rules are provisioned by the script that allow/deny communication across tiers based on prerequisites.
  • Web tier virtual machines need an IIS server to allow testing and validating access to business tier via HTTP requests.
  • Virtual machines are provisioned following a recommended naming convention for Azure resources that allows easy access via automation.

NOTE: Though the scenario in this post is limited to installation of IIS infrastructure on a set of virtual machines, with this approach it's possible to accomplish several other WMF based tasks such as setting up a domain controller, a DNS server, a SQL server, a failover cluster (WSFC) and so on. Similarly DSC extensions can be used on Linux based virtual machines.

Now that we understand the scenario, let’s look at how we can accomplish this without traversing through flaming hoops! Below is a quick screenshot of my Azure Resource Group:


From the screenshot above, a couple of points may be noted:

  • Virtual machines follow a naming pattern e.g. bc-svc2-vm1, bc-svc2-vm2 and so on.
  • Resource group is named bc-dev-rg.

Here are the steps I followed to install IIS on the virtual machines shown in the above screenshot:

  • Create a Powershell file with the following content and save it as IISConfig.ps1:
Configuration ConfigureWeb
	node ("localhost")
		WindowsFeature InstallWebServer 
			Ensure = "Present"
			Name = "Web-Server" 
  • Package and publish the module to a publically accessible blob container URL using the following steps:
    • Open a Powershell x861 command window in administrative mode.
    • Login to your Azure account and select your subscription.
      Select-AzureRmSubscription –SubscriptionId my-subscription-id
    • Publish2 the script to your storage account. Following command packages the script into  IISConfig.zip file and publishes to the storage container under given storage account.
Publish-AzureRmVMDscConfiguration -ConfigurationPath .\IISConfig.ps1 -ContainerName mycontainer -StorageAccountName mystorageaccount -ResourceGroupName myresourcegroup
  • Create a text file with the following content and save it as TurnOnIIS.cmd:

:: Set up variables.
:: Change these variables to match your deployment.

:: Number of Virtual Machines (VMs) to configure. Set according to your scenario.

:: Loop through all the VMs and call subroutine that installs IIS on each VM.
:: Loop counter and the service tier name are passed as parameters.
FOR /L %%I IN (1,1,%NUM_VMS%) DO CALL :ConfigureIIS %%I svc2

GOTO :eof

:: Subroutine that configures IIS


ECHO Turning on IIS configuration for: %VM_NAME% under resource group: %RESOURCE_GROUP%

:: Following assumes that you have
::  1. Logged into your Azure subscription using "azure login"
::  2. Set the active subscription using "azure account set <subscription-name>"

CALL azure vm extension set --resource-group %RESOURCE_GROUP% --vm-name %VM_NAME% ^
	--name DSC --publisher-name Microsoft.Powershell --version 2.9 ^
	--public-config "{\"ModulesUrl\": \"https://mystorage.blob.core.windows.net/scripts/IISConfig.ps1.zip\", \"ConfigurationFunction\": \"IISConfig.ps1\\ConfigureWeb\" }"
GOTO :eof

Basically all we’re doing is:

  1. Iterating through the VMs based on the given naming convention
  2. Calling a subroutine ConfigureIIS on each VM
  3. Invoking azure vm extension set cmdlet on each VM passing in the following parameters:
--resource-group      Name of the resource group    
--vm-name             Name of the virtual machine    
--name                Type of the custom script extension    
--publisher-name      Name of the extension publisher    
--version             Version of the extension    
--public-config       JSON text specifying 
                      ModuleUrl    Packaged configuration location
                      ConfigurationFunction    Name of the configuration function in the script including path
  • Open a command prompt, CD to the folder containing the command script and run it.

Similar approach could be used to turn on or off any of the Windows features on your virtual machines . For details on writing a DSC configuration to manipulate multiple Windows features simultaneously, please check this article.

1 Do not use x64 build of Powershell since the target virtual machines use the x86 build of Powershell by default. Failure to comply with this leads to:

  • Failure of publishing process to Azure storage container with a vague message such as Resource group not found though the resource group does exists!
  • Failure of unzipping process on the target virtual machines which is really hard to debug since the error message is least helpful!

2 An alternative method involves creating the package locally and uploading it to a GitHub account. In this case you substitute the storage container URL with a publically accessible GitHub URL. Below is the modified command to use in this case:

Publish-AzureRmVMDscConfiguration -ConfigurationPath .\IISConfig.ps1 -OutputArchivePath IISConfig.zip