In one of my previous posts I introduced Azure Resource Manager templates, aka ARM templates. These templates are great for deploying and configuring Azure Resources like virtual networks, virtual machines, Azure websites, Azure key vaults, etc. But sometimes you need a little bit more automated configuration that goes beyond ARM template functionality. A good example is provisioning a Virtual Machine with additional components like Active Directory, DNS or even SQL Server instances. In this article I’ll introduce PowerShell Desired State Configuration (DSC) and how to use it with your ARM templates.
PowerShell DSC
DSC is a feature/extension of PowerShell (from version 4.0 and up). As the name says, you can create a configuration (a declarative PowerShell script) that defines the desired state of a Virtual Machine. This way you can maintain consistency across devices. For example, you can create a configuration that your virtual machine running Windows Server needs to have IIS installed. This can be achieved by ensuring the Windows features Web-Server and Web-Mgmt-Tools:
Configuration MyWebServerCfg { Import-DscResource -ModuleName PsDesiredStateConfiguration Node 'localhost' { # The first resource block ensures that the Web-Server (IIS) feature is enabled. WindowsFeature IIS { Ensure = "Present" Name = "Web-Server" } # The second resource block ensures that the Web-Mgmt-Tools feature is enabled. WindowsFeature IIS-Tools { Ensure = "Present" Name = "Web-Mgmt-Tools" DependsOn = "[WindowsFeature]IIS" } } }
Every time you execute this configuration, your machine will be in the desired state of having IIS installed. As you can see in this example I have defined 2 DSC resources based on WindowsFeature. The second one is even depending on the first one, which means that the feature will be installed and enabled after the first one has finished. That’s cool, isn’t it? With each DSC resource you can set properties with required values.
I won’t be explaining writing and using PowerShell DSC in too much details, but you can find good resources over here:
- https://docs.microsoft.com/en-us/powershell/dsc/overview
- https://docs.microsoft.com/en-us/powershell/dsc/builtinresource
Custom PowerShell DSC Resources
Of course, PowerShell DSC also has a great community and they have lots of custom DSC resources available for you. Please take a look at Github and the PowerShell Gallery for some awesome DSC resources!
Wouldn’t it be cool to create some Azure VMs, install and configure a Windows Active Directory Domain, set up DNS, install SQL Server and a SharePoint farm? I think that’s awesome. And yes, the community has some really cool custom DSC Resources available for you to use.
Using DSC Resources in your project
Before you can use the custom DSC resources, you need to install the package. However, when you deploy an Azure VM with a ARM template, then you have a challenge installing that package on the newly created VM while deploying the ARM template. Another approach is to download the source branch of the DSC resource and add it to your project. In my case I have created a folder DSC and added several custom DSC resources to that folder:
Then I create my custom DSC configuration scripts, that need to be executed on one of more Azure VMs. In my case I created a ProvisionDC.ps1 that configures several windows features to enable Active Directory Domain Services, DNS, provisions the attached Azure DataDisk, creates some folders, adds several OUs and user accounts, etc. The other configuration scripts provisions SQL Server instances, adds the VM to the domain, provisions Firewall Rules, etc..
The question is now: how to get these scripts on the Azure VM and execute them?
Azure VM Extensions
To answer the previous question, you can use Azure VM Extensions. There is a PowerShell DSC extension that you can use in your ARM template. I have defined a resource of type extensions and this resource is part of my VM resource.
Take a look at the following snippet from my ARM template:
"resources": [ { "name": "CreateADForest", "type": "extensions", "apiVersion": "2016-03-30", "location": "[parameters('location')]", "dependsOn": [ "[resourceId('Microsoft.Compute/virtualMachines', variables('vmDCName'))]" ], "properties": { "publisher": "Microsoft.Powershell", "type": "DSC", "typeHandlerVersion": "2.19", "autoUpgradeMinorVersion": true, "settings": { "ModulesUrl": "[concat(parameters('locationScripts'), '/ELB_DSC.zip', parameters('locationScriptsSasToken'))]", "ConfigurationFunction": "ProvisionDC.ps1\\ProvisionADDomainController", "Properties": { "DomainName": "[parameters('domainName')]", "AdminCreds": { "UserName": "[parameters('adminUsername')]", "Password": "PrivateSettingsRef:AdminPassword" } } }, "protectedSettings": { "Items": { "AdminPassword": "[parameters('adminPassword')]" } } } } ],
In the properties I define that this extension is of type PowerShell DSC. In the Settings I have a reference to a ZIP file. This ZIP file is stored in an Azure Storage Account. When this Azure VM Extension is deployed by the ARM template, this ZIP file gets downloaded and unzipped on disk in the VM. Then it executes the script ProvisionDC.ps1 and the configuration named ProvisionADDomainController. This is configured by the property ConfigurationFunction. And yes, you can also supply parameters to the configuration. In my case I supply DomainName and the Administrator Credentials.
Uploading your DSC zip file
How does the ZIP file get it in the Azure Storage Account? Well, you could do it manually, but I like automating stuff. Within the PowerShell script that deploys the ARM template, I also have created some PowerShell script to create the ZIP file and upload it to the Azure Storage account:
function Prepare-DSCScripts() { $zipFileName = "ELB_DSC.zip" $sourcePath = Join-Path $PSScriptRoot "..\DSC\*" $destinationPath = Join-Path $PSScriptRoot $zipFileName if( Test-Path $destinationPath ) { Remove-Item -Path $destinationPath -Confirm:$false } Write-Host "`tCompressing to file $zipFileName " -NoNewline Compress-Archive -Path $sourcePath -DestinationPath $destinationPath Write-Host -f Green "[Done]" } function Upload-DSCScripts( $context ) { $containerName = "scripts" $containerState = Get-AzureStorageContainer -Name $containerName -Context $context -ErrorAction SilentlyContinue if ($null -eq $containerState) { $container = New-AzureStorageContainer -Name $containerName -Context $context } $fileName = "ELB_DSC.zip" Write-Host "`tUploading $fileName " -NoNewline $blob = Set-AzureStorageBlobContent -File $fileName -Container $containerName -Blob $fileName -Context $context -Force Write-Host -f Green "[Done]" }
From here I provide a final location of the script and a SASToken to the final ARM template that deploys the VM and DSC extension.
So, what happens on the VM?
During the deployment of the DSC extension, the ZIP gets downloaded and unzipped on the disk. That looks like this:
The PowerShell DSC extension is installed in C:\Packages\Plugins and your custom DSC Resources and configuration scripts are unpacked in a subfolder named DSCWork. From there, your configuration is executed where it uses the custom DSC resources that are compiled in a MOF file located in the subfolder ProvisionADDomainController.
And with that, your ARM template will be deployed successfully ending up with very much completed provisioned Azure VMs.
Summary
Extending your ARM templates with the PowerShell DSC extension gives you a powerful and flexible way of provisioning your Azure VMs. It is possible to completely deploy your SharePoint infrastructure unattended. And if you set up your scripts wisely you can even support DTAP environments this way!