Revisit – Deploying a DC to Azure IaaS with ARM and DSC


Introduction

In an earlier post I provided a walkthrough for the deployment of a Domain Controller to Azure IaaS using an ARM template and DSC. Since that post, I’ve had several questions due to changes in the way the templates and deployment code do their work.

Today, I’m going to walk through this again with that hope that it clears things up.

Tools

Building ARM templates requires nothing more than a text editor but in my experience, you can’t go past Visual Studio with the Azure ADK. For this post, I’ll be using one of the pre-canned Azure VMs available to me via my MSDN subscription. It includes everything I need and I don’t have to spend time setting it all up –

ARM01

The other thing you’ll need is the xActiveDirectory PowerShell DSC Module. At the time of writing, there was a bug in the latest version (2.13.0.0) that prevented a deployment such as this from working on Windows Server 2016. There’s a specific issue fix on GitHub that does work with Windows Server 2016 and is available at

https://github.com/slapointe/xActiveDirectory/tree/Issue73

This should be downloaded and installed to

%ProgramFiles%\WindowsPowerShell\Modules\xActiveDirectory\2.11.0.0

Lastly, it’s been my observation that the %PSModulePath% environment variable often contains a duplicate entry for the PowerShell modules residing under %ProgramFiles%

C:\Program Files\WindowsPowerShell\Modules\xActiveDirectory\2.11.0.0

and

%ProgramFiles%\WindowsPowerShell\Modules\xActiveDirectory\2.11.0.0

This double-up of the path can cause deployment issues. I’d editing the %PSModulePath% environment variable and removing one of these entries if you have both.

Create an Azure Resource Group Solution

These steps are well discussed elsewhere but I’ll include them here for completeness –

  1. Open Visual Studio
  2. Choose New Project
  3. Expand Installed -> Templates -> Visual C# -> Cloud and choose Azure Resource Group in the centre pane
     ARM02
  4. Provide a Name and Solution Name and click OK

Use the Sample VM Template

After creating the new solution, you’ll be prompted to select from a series of base templates. You’re free to choose a blank template but for the purposes of this blog, I’ll select Windows Virtual Machine

ARM03

Exploring What I Have

Let’s start by expanding Scripts and Templates in Solution Explorer

ARM04_2

You’ll see Deploy-AzureResourceGroup.ps1. This script does all the heavy lifting when Visual Studio is instructed to deploy the solution. In my earlier post, we had to make some edits to this script but with the latest updates, it takes care of everything perfectly.

WindowsVirtualMachine.parameters.json is used to feed per-deployment configuration data into the ARM template.

WindowsVirtualMachine.json is the template that describes the resources deployed to the resource group. Opening this file displays the JSON ready for editing but also opens the JSON Outline in the left-hand pane. Expanding Resources in the JSON Outline gives us an idea of what I get with the sample template –

ARM05_2

So I’m getting a storage account, a public IP address, a virtual network, a network interface and a virtual machine with an Azure diagnostics extension. All I really need to add is some PowerShell Desired State Configuration that turns the VM into a Domain Controller.

Adding Desired State Configuration

In order to add DSC to the ARM template, right-click the VirtualMachine and select Add New Resource

ARM06_2

From the resource list, select PowerShell DSC Extension, provide a name for the extension and select the VM it applies to –

ARM07_2

After doing so, the DSC Extension appears in the JSON Outline, the JSON itself is added to the ARM template and a new DSC configuration script is added to the solution.

ARM08_2

Now that I have DSC added to the ARM template, I need to set it up to install and configure the Domain Controller role. In this example, I want to deploy the role, the administration tools and configure the new forest root domain along with administrator credentials. To do this, I’ll edit the dscDC.ps1 script as follows –

Configuration Main
{

[CmdletBinding()]

Param (
	[string] $NodeName,
	[string] $domainName,
	[System.Management.Automation.PSCredential]$domainAdminCredentials
)

Import-DscResource -ModuleName PSDesiredStateConfiguration, xActiveDirectory

Node $AllNodes.Where{$_.Role -eq "DC"}.Nodename
    {
        LocalConfigurationManager
		{
			ConfigurationMode = 'ApplyAndAutoCorrect'
			RebootNodeIfNeeded = $true
			ActionAfterReboot = 'ContinueConfiguration'
			AllowModuleOverwrite = $true
		}

		WindowsFeature DNS_RSAT
		{ 
			Ensure = "Present" 
			Name = "RSAT-DNS-Server"
		}

		WindowsFeature ADDS_Install 
		{ 
			Ensure = 'Present' 
			Name = 'AD-Domain-Services' 
		} 

		WindowsFeature RSAT_AD_AdminCenter 
		{
			Ensure = 'Present'
			Name   = 'RSAT-AD-AdminCenter'
		}

		WindowsFeature RSAT_ADDS 
		{
			Ensure = 'Present'
			Name   = 'RSAT-ADDS'
		}

		WindowsFeature RSAT_AD_PowerShell 
		{
			Ensure = 'Present'
			Name   = 'RSAT-AD-PowerShell'
		}

		WindowsFeature RSAT_AD_Tools 
		{
			Ensure = 'Present'
			Name   = 'RSAT-AD-Tools'
		}

		WindowsFeature RSAT_Role_Tools 
		{
			Ensure = 'Present'
			Name   = 'RSAT-Role-Tools'
		}      

		WindowsFeature RSAT_GPMC 
		{
			Ensure = 'Present'
			Name   = 'GPMC'
		} 
		xADDomain CreateForest 
		{ 
			DomainName = $domainName            
			DomainAdministratorCredential = $domainAdminCredentials
			SafemodeAdministratorPassword = $domainAdminCredentials
			DatabasePath = "C:\Windows\NTDS"
			LogPath = "C:\Windows\NTDS"
			SysvolPath = "C:\Windows\Sysvol"
			DependsOn = '[WindowsFeature]ADDS_Install'
		}
    }
}

The first thing I’ve done is add some parameters for the domain name and the domain administrator credentials. This allows me to pass them in from the ARM template –

Param (
	[string] $NodeName,
	[string] $domainName,
	[System.Management.Automation.PSCredential]$domainAdminCredentials
)

Next I’m importing the PowerShell modules I need –

Import-DscResource -ModuleName PSDesiredStateConfiguration, xActiveDirectory

I’ve then applied a filter so that only nodes of role “DC” will be configured as Domain Controllers. This is less important when I’m deploying just one server but in larger deployments where multiple server roles are being deployed, it’s useful –

Node $AllNodes.Where{$_.Role -eq "DC"}.Nodename

The rest of the script installs the required Windows features and finally creates the forest using –

		xADDomain CreateForest 
		{ 
			DomainName = $domainName            
			DomainAdministratorCredential = $domainAdminCredentials
			SafemodeAdministratorPassword = $domainAdminCredentials
			DatabasePath = "C:\Windows\NTDS"
			LogPath = "C:\Windows\NTDS"
			SysvolPath = "C:\Windows\Sysvol"
			DependsOn = '[WindowsFeature]ADDS_Install'
		}

Configuration Data for DSC
When credentials are used with DSC, encryption certificates are necessary to protect passwords. Setting this up is beyond what I want to cover here so I’ll use a PowerShell data file added to my solution as follows –

ARM10

And then add a PowerShell data file –

ARM11

For this PowerShell data file to be included in the build, right-click on it in the Solution Explorer and select Properties. Configure as follows –

ARM13_2

I add the following contents to the PowerShell data file –

# Configuration Data for AD  
@{
	AllNodes = @(
		@{
			NodeName="*"
			RetryCount = 20
			RetryIntervalSec = 30
			PSDscAllowPlainTextPassword=$true
			PSDscAllowDomainUser = $true
		},
		@{ 
			Nodename = "localhost" 
			Role = "DC" 
		}
	)
}

ARM Template Changes to Support DSC

The next step is to add some parameters for the domain name and admin credentials. The parameters file – WindowsVirtualMachine.parameters.json already contains –

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "adminUsername": {
      "value": null
    },
    "dnsNameForPublicIP": {
      "value": null
    }
  }
}

I update it to contain –

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "adminUsername": {
      "value": "mark"
    },
    "adminPassword": {
      "value": "P@ssw0rd123!"
    },
    "domainName": {
      "value": "contoso.com"
    },
    "dnsNameForPublicIP": {
      "value": "blogdc01"
    },
    "windowsOSVersion": {
      "value": "2012-R2-Datacenter"
    }
  }
}

adminPassword and windowsOSVersion are already a defined parameters in the WindowsVirtualMachine.json template file. All I need to do is add domainName to the parameters section using –

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "adminUsername": {
      "type": "string",
      "minLength": 1,
      "metadata": {
        "description": "Username for the Virtual Machine."
      }
    },
    "adminPassword": {
      "type": "securestring",
      "metadata": {
        "description": "Password for the Virtual Machine."
      }
    },
    "domainName": {
      "type": "string",
      "minLength": 1,
      "metadata": {
        "description": "Domain Name for the Forest."
      }
    },
    "dnsNameForPublicIP": {
      "type": "string",
      "minLength": 1,
      "metadata": {
        "description": "Globally unique DNS Name for the Public IP used to access the Virtual Machine."
      }
    },
    "windowsOSVersion": {
      "type": "string",
      "defaultValue": "2012-R2-Datacenter",
      "allowedValues": [
        "2008-R2-SP1",
        "2012-Datacenter",
        "2012-R2-Datacenter"
      ],
      "metadata": {
        "description": "The Windows version for the VM. This will pick a fully patched image of this given Windows version. Allowed values: 2008-R2-SP1, 2012-Datacenter, 2012-R2-Datacenter."
      }
    },

Lastly, I’ll modify the DSC extension in the WindowsVirtualMachine.json template file from –

        {
              "name": "Microsoft.Powershell.DSC",
              "type": "extensions",
              "location": "[resourceGroup().location]",
              "apiVersion": "2015-06-15",
              "dependsOn": [
                  "[resourceId('Microsoft.Compute/virtualMachines', variables('vmName'))]"
              ],
              "tags": {
                  "displayName": "dscDC"
              },
              "properties": {
                  "publisher": "Microsoft.Powershell",
                  "type": "DSC",
                  "typeHandlerVersion": "2.9",
                  "autoUpgradeMinorVersion": true,
                  "forceUpdateTag": "[parameters('dscDCUpdateTagVersion')]",
                  "settings": {
                      "configuration": {
                          "url": "[concat(parameters('_artifactsLocation'), '/', variables('dscDCArchiveFolder'), '/', variables('dscDCArchiveFileName'))]",
                          "script": "dscDC.ps1",
                          "function": "Main"
                      },
                      "configurationArguments": {
                          "nodeName": "[variables('vmName')]"
                      }
                  },
                  "protectedSettings": {
                      "configurationUrlSasToken": "[parameters('_artifactsLocationSasToken')]"
                  }
              }
          }

to –

        {
              "name": "Microsoft.Powershell.DSC",
              "type": "extensions",
              "location": "[resourceGroup().location]",
              "apiVersion": "2015-06-15",
              "dependsOn": [
                  "[resourceId('Microsoft.Compute/virtualMachines', variables('vmName'))]"
              ],
              "tags": {
                  "displayName": "dscDC"
              },
              "properties": {
                "publisher": "Microsoft.Powershell",
                "type": "DSC",
                "typeHandlerVersion": "2.9",
                "autoUpgradeMinorVersion": true,
                "forceUpdateTag": "[parameters('dscDCUpdateTagVersion')]",
                "settings": {
                  "configuration": {
                    "url": "[concat(parameters('_artifactsLocation'), '/', variables('dscDCArchiveFolder'), '/', variables('dscDCArchiveFileName'))]",
                    "script": "dscDC.ps1",
                    "function": "Main"
                  },
                  "configurationArguments": {
                    "nodeName": "[variables('vmName')]",
                    "domainName": "[parameters('domainName')]",
                    "domainAdminCredentials": {
                      "UserName": "[parameters('adminUserName')]",
                      "Password": "PrivateSettingsRef:Password"
                    }
                  },
                  "configurationData": {
                    "url": "[concat(parameters('_artifactsLocation'), '/DSC/dscDCConfigData.psd1')]"
                  }
                },
                "protectedSettings": {
                  "configurationUrlSasToken": "[parameters('_artifactsLocationSasToken')]",
                  "items": {
                    "Password": "[parameters('adminPassword')]"
                  }
                }
              }
          }

Here I’ve modified the modulesUrl to match the name of the DSC archive that will be used for the DC, I’ve added domainName and domainAdminCredentials properties that will be passed to the DSC script and I’ve added the adminPassword and the DataBlobUri to protectedSettings. The DataBlobUri is the location for the PowerShell data file used for DSC config data.

Deployment

At this stage I’m ready to deploy my DC to Azure. All I need to do is right-click the solution name, select Deploy and then New Deployment.

ARM12_thumb3

Following the wizard kicks off the deployment and after a short wait, the deployment is complete.

Conclusion

My hope is that this post clears up a few questions around Azure Resource Manager (ARM) template deployments and integration with DSC. This is only the start of what’s possible with ARM template deployments that permit multi-VM builds with any number of customisations.

Comments (25)

  1. Monterey Harris says:

    I keep getting errors when changing properties on Powershell Data Filemismatched PageRule with the itemtype

    1. Mark Renoden says:

      Hi Monterey

      Try changing “Copy to Output Directory” to “Copy always” first, hit “Apply” and then change “Build Action” to “Content” and hit “Apply”. After that, I think you should be OK.

      Cheers

      Mark

  2. Oliver says:

    Commenting here too –
    Could you point me towards something that explains how passing credentials into DSC works? I don’t think the fist DSC config I have is actually using any credentials at all when standing up that first domain controller; I think it’s using the first user’s (local admin) account. I am working on an ARM template with two domain controllers, and adding the second isn’t working.

    The reason I think this is because now that I am working on adding a second domain controller (which checks for a domain and so would need domain credentials), I can’t get the DSC config to work. So I guess I am not sure how to check for what is actually being passed into this DSC config and what credentials it is actually using to check for the domain, but it’s definitely wrong and I have followed a ton of other people’s examples.

    To see if maybe there was something wrong with the master xActiveDirectory module, I grabbed the -dev branch one and used that and I do get another error letting me know for sure that either the credentials are wrong or the domain is wrong (it isn’t).

    1. Mark Renoden says:

      I haven’t tried that myself. Remember that DSC is a capability separate to ARM templates and you’re trying to put them together to achieve the outcome. I think this blog post is a good reference for DSC and credentials on its own –

      https://blogs.msdn.microsoft.com/powershell/2014/01/31/want-to-secure-credentials-in-windows-powershell-desired-state-configuration/

      You could also take a look at the 2 DC ARM template example on GitHub – https://github.com/Azure/azure-quickstart-templates/tree/master/active-directory-new-domain-ha-2-dc

      Note that my configuration data file tells DSC to ignore unencrypted credentials. If you’re going to protect credentials, you’re going to need to include certificates in your workflow …

  3. Kasun Rajapakse says:

    Can I deploy this Using Azure Automation Account. I like to distribute this in DSC configuration file in Azure automation.

    1. Mark Renoden says:

      My understanding is yes. I haven’t put any time into this myself but I know a colleague has successfully applied DSC to Azure VMs via Azure Automation … which of course provides you with ongoing configuration drift prevention.

  4. Adam Cosby says:

    Hi,

    Sorry do you have some clearer steps on what you need to do to get this working on Server 2016?

    1. Mark Renoden says:

      I’m not sure what you’re missing. For 2016, go to the GitHub link I provided, download everything and dump it in the %ProgramFiles%\WindowsPowerShell\Modules\xActiveDirectory\2.11.0.0 folder.

      I don’t think I checked when I wrote this. You probably have to add the 2016 OS as an allowed value in the parameters section of your ARM template.

      1. Adam Cosby says:

        Hi, yes but where do I install that?

        Do I need to let the VM build and then hack and modify it after? The other issue I have is that is just doesn’t build for me at the moment. It is saying it cannot find the zip file on the DSC Powershell bits. I have check the artefact storage location and the zip isn’t being created. Where should it be generating it from?

        Thanks

      2. Adam Cosby says:

        VM has reported a failure when processing extension ‘Microsoft.Powershell.DSC’. Error message: \”The DSC Extension failed to execute: Error downloading https://xxxxx.core.windows.net/xxxx/DSC/VMDC.zip after 29 attempts: (I removed the storage name and resource group but they were correct – just the vmdc.zip didn’t exist.

      3. Adam Cosby says:

        Sorry – figured it out…

        Might be an oversight but I didn’t realise my machine that was running VS had to have the xActiveDirectory on…
        Install-Module -Name xActiveDirectory -RequiredVersion 2.15.0.0

        Is it this directory on my laptop I need to update with that issue 73 for the 2016 server?

        1. Mark Renoden says:

          When I wrote this post, 2.12 was the current version of xActiveDirectory. Issue 73 was not included in 2.12. From your comment, it seems 2.15 is the current version which may work successfully on Server 2016. As I said, all of this is a moving target. You can try 2.15. If that doesn’t work, use the fix as I’ve described.

        2. Jappie says:

          Hi Adam,

          you said you figured out why you received the error during your deployment (The DSC Extension failed to execute: Error downloading https://xxxxx.core.windows.net/xxxx/DSC/VMDC.zip after 29 attempts).

          I have the same error, although in my case the zip is copied succesfully.

          I was wondering what you did to fix this issue?

          kr
          Jasper.

  5. GhassanHariz says:

    Hi Mark,

    Here is a slightly different way to create a DC using ARM and DSC. I created a second hard disk on the DC VM (data disk with no caching) because that the SYSVOL and ADDS database have to be on non-cached non OS disk. I am also passing parameters as secure strings so the passwords are requested at deployment time instead of being entered in the code. The steps are posted here: http://ghassanhariz7.azurewebsites.net/2016/12/07/building-adds-dc-azure-iaas-using-arm-dsc/. The one issue I had was with the .psd1 file not being read at deployment time. I worked around that by putting the .psd1 file on Github. Any comments or feedback are appreciated.

    1. Mark Renoden says:

      Great! This post was really just serving as an example to get folks up and running. Addressing things like additional disks and passing secured parameters are certainly valid and useful. Thanks for adding value!

    2. Hi GhassanHariz,

      In your article, which is great, the problem you are getting where it can’t find the resource – you’re putting the files on GITHUB to workaround the problem, but if you create an azure storage location, let it create it once…it will fail again. However, if you switch that storage “container” from Private to BLOB – it will fix the problem and you can continually deploy to that location for your artifacts.

  6. Kyle says:

    Great writeup, though I get errors when deploying complaining that my .PS1 file is not a .ZIP file.

    I get the same if uploading a .zip to GitHub and referencing that, except it complains that my .zip is invalid!

    Any suggestions?

    1. Mark Renoden says:

      Hi Kyle

      It might help if you provided the specific error message. Do you mean ps1 or psd1?

      Cheers

      Mark

  7. Ajinkya Apte says:

    Hi Mark,
    Great writeup!
    Any thoughts on how to deploy a “Read-Only” domain controller?

    1. Mark Renoden says:

      Looking at the current xADDomainController schema (here – https://github.com/PowerShell/xActiveDirectory/blob/dev/DSCResources/MSFT_xADDomainController/MSFT_xADDomainController.schema.mof), it doesn’t look like you can … there’s no parameter for it

      1. Ajinkya Apte says:

        Yep, I saw a closed issue with regards to “adding support to RODC” on git page. Can you advise me on some different approach?

        1. for rodc, i’m about to confirm this, but just add the promoted domain controller to the domain, should be enough…worse case, add a powershell line to a deployed vm right in your dsc ps1 script.

  8. Hossein says:

    I keep getting error:
    xADDomain Converting and storing encrypted passwords as plain text is not recommended.
    For more information on securing credentials in MOF file, please refer to MSDN blog: http://go.microsoft.com/fwlink/?LinkId=393729

    1. Mark Renoden says:

      Hi Hossein

      The steps under Configuration Data for DSC should address this. You really should read the references provided in the error message and use a certificate to protect the password but if you’re just setting up a proof of concept, you can bypass the protection of your admin credentials using the configuration data file as I’ve described. I’ve seen the error you’re getting when the configuration data file wasn’t uploaded to Azure storage.

      1. Hossein says:

        Hi Mark
        I checked, the configuration data file is uploaded to azure storage. If I change the Node name to ‘localhost’ it will work, otherwise it throw the error

Skip to main content