Originally posted on Nivlesh’s blog @ nivleshc.wordpress.com

Desired State Configuration (DSC) is a declarative language in which you state “what” you want done instead of going into the nitty gritty level to describe exactly how to get it done. Jeffrey Snover (the inventor of PowerShell) quotes Jean-Luc Picard from Star Trek: The Next Generation to describe DSC – it tells the servers to “Make it so”.

In this blog, I will show you how to use DSC to create a brand new Active Directory Forest. In my next blog post, for redundancy, we will add another domain controller to this new Active Directory Forest.

The DSC configuration script can be used in conjunction with Azure Resource Manager to deploy a new Active Directory Forest with a single click, how good is that!

A DSC Configuration script uses DSC Resources to describe what needs to be done. DSC Resources are made up of PowerShell script functions, which are used by the Local Configuration Manager to “Make it so”.

Windows PowerShell 4.0 and Windows PowerShell 5.0, by default come with a limited number of DSC Resources. Don’t let this trouble you, as you can always install more DSC modules! It’s as easy as downloading them from a trusted location and placing them in the PowerShell modules directory! 

To get a list of DSC Resources currently installed, within PowerShell, execute the following command

Get-DscResource

Out of the box, there are no DSC modules available to create an Active Directory Forest. So, to start off, lets go download a module that will do this for us.

For our purpose, we will be installing the xActiveDirectory PowerShell module (the x in front of ActiveDirectory means experimental), which can be downloaded from https://gallery.technet.microsoft.com/scriptcenter/xActiveDirectory-f2d573f3  (updates to the xActiveDirectory module is no longer being provided at the above link, instead these are now published on GitHub at https://github.com/PowerShell/xActiveDirectory  . For simplicity, we will use the TechNet link above)

Download the following DSC modules as well

https://gallery.technet.microsoft.com/scriptcenter/xNetworking-Module-818b3583

https://gallery.technet.microsoft.com/scriptcenter/xPendingReboot-PowerShell-b269f154

After downloading the zip files, extract the contents and place them in the PowerShell modules directory located at $env:ProgramFiles\WindowsPowerShell\Modules folder

($env: is a PowerShell reference  to environmental variables).

For most systems, the modules folder is located at C:\ProgramFiles\WindowsPowerShell\Modules

However, if you are unsure, run the following PowerShell command to get the location of $env:ProgramFiles 

Get-ChildItem env:ProgramFiles

Tip: To get a list of all environmental variables, run Get-ChildItem env:

With the pre-requisites taken care of, let’s start creating the DSC configuration script.

Open your Windows PowerShell IDE and let’s get started.

Paste the following into your PowerShell editor and save it using a filename of your choice (I have saved mine as CreateNewADForest.ps1)

Configuration CreateNewADForest {
param(
      [Parameter(Mandatory)]
      [String]$DomainName,

      [Parameter(Mandatory)]
      [System.Management.Automation.PSCredential]$AdminCreds,

      [Parameter(Mandatory)]
      [System.Management.Automation.PSCredential]$SafeModeAdminCreds,

      [Parameter(Mandatory)]
      [System.Management.Automation.PSCredential]$myFirstUserCreds,

      [Int]$RetryCount=20,
      [Int]$RetryIntervalSec=30
)

The above declaration defines the parameters that will be passed to our  configuration function.  There are also two “constants” defined as well. I have named my configuration function CreateNewADForest which coincides with the name of the file it is saved in – CreateNewADForest.ps1

Here is some explanation regarding the above parameters and constants

We now have to import the DSC modules that we had downloaded, so add the following line to the above Configuration Script

Import-DscResource -ModuleName xActiveDirectory, xNetworking, xPendingReboot

We now need to convert $AdminCreds the format domain\username. We will store the result in a new object called $DomainCreds

The next line states where the DSC commands will run. Since we are going to run this script from within the newly provisioned virtual machine, we will use localhost as the location

So enter the following

Node localhost
{

Next, we need to tell the Local Configuration Manager to apply the settings only once, reboot the server if needed (during our setup) and most importantly, to continue on with the configuration after reboot. This is done by using the following lines

LocalConfigurationManager
{
     ActionAfterReboot = 'ContinueConfiguration'
     ConfigurationMode = 'ApplyOnly'
     RebootNodeIfNeeded = $true
}

If you have worked with Active Directory before, you will be aware of the importance of DNS. Unfortunately, during my testing, I found that a DNS Server is not automatically deployed when creating a new Active Directory Forest. To ensure a DNS Server is present before we start creating a new Active Directory Forest, the following lines are needed in our script

WindowsFeature DNS
{
     Ensure = "Present"
     Name = "DNS"
}

The above is a declarative statement which nicely shows the “Make it so” nature of DSC. The above lines are asking the DSC Resource WindowsFeature to Ensure DNS (which refers to DNS Server) is Present. If it is not Present, then it will be installed, otherwise nothing will be done. This is the purpose of the Ensure = “Present” line. The word DNS after WindowsFeature is just a name I have given to this block of code.

Next, we will leverage the DSC function xDNSServerAddress (from the xNetworking module we had installed) to set the local computer’s DNS settings, to its loopback address. This will make the computer refer to the newly installed DNS Server.

xDnsServerAddress DnsServerAddress
{
     Address        = '127.0.0.1'
     InterfaceAlias = 'Ethernet'
     AddressFamily  = 'IPv4'
     DependsOn = "[WindowsFeature]DNS"
}

Notice the DependsOn = “[WindowsFeature]DNS” in the above code. It tells the function that this block of code depends on the [WindowsFeature]DNS section. To put it another way, the above code will only run after the DNS Server has been installed. There you go, we have specified our first dependency.

Next, we will install the Remote Server Administration Tools

WindowsFeature RSAT
{
     Ensure = "Present"
     Name = "RSAT"
}

Now, we will install the Active Directory Domain Services feature in Windows Server. Note, this is just installation of the feature. It does not create the Active Directory Forest.

WindowsFeature ADDSInstall
{
     Ensure = "Present"
     Name = "AD-Domain-Services"
}

So far so good. Now for the most important event of all, creating the new Active Directory Forest. For this we will use the xADDomain function from the xActiveDirectory module.

Notice above, we are pointing the DatabasePath, LogPath and SysvolPath all on to the C: drive. If you need these to be on a separate volume, then ensure an additional data disk is added to your virtual machine and then change the drive letter accordingly in the above code. This section has a dependency on the server’s DNS settings being changed.

If you have previously installed a new Active Directory forest, you will remember how long it takes for the configuration to finish. We have to cater for this in our DSC script, to wait for the Forest to be successfully provisioned, before proceeding. Here, we will leverage on the xWaitForADDomain DSC function (from the xActiveDirectoy module we had installed).

xWaitForADDomain DscForestWait
{
     DomainName = $DomainName
     DomainUserCredential = $DomainCreds
     RetryCount = $RetryCount
     RetryIntervalSec = $RetryIntervalSec
     DependsOn = "[xADDomain]FirstDC"
}

Viola! The new Active Directory Forest has been successfully provisioned! Let’s add a new user to Active Directory and then restart the server.

xADUser FirstUser
{
     DomainName = $DomainName
     DomainAdministratorCredential = $DomainCreds
     UserName = $myFirstUserCreds.Username
     Password = $myFirstUserCreds
     Ensure = "Present"
     DependsOn = "[xWaitForADDomain]DscForestWait"
}

The username and password are what had been supplied in the parameters to the configuration function.

That’s it! Our new Active Directory Forest is up and running. All that is needed now is a reboot of the server to complete the configuration!

To reboot the server, we will use the xPendingReboot module (from the xPendingReboot package that we installed)

xPendingReboot Reboot1
{
     Name = "RebootServer"
     DependsOn = "[xWaitForADDomain]DscForestWait"
}

Your completed Configuration Script should look like below (I have taken the liberty of closing off all brackets and parenthesis)

You can copy the above script to a newly create Azure virtual machine and deploy it manually.

However, a more elegant method will be to bootstrap it to your Azure Resource Manager virtual machine template. One caveat is, the DSC configuration script has to be packaged into a zip file and then uploaded to a location that Azure Resource Manager can access (that means it can’t live on your local computer). You can upload it to your Azure Storage blob container and use a shared access token to give access to your script or upload it to a public repository like GitHub.

I have packaged and uploaded the script to my GitHub repository at https://raw.githubusercontent.com/nivleshc/arm/master/CreateNewADForest.zip

The zip file also contains the additional DSC Modules that were downloaded. To use the above in your Azure Resource Manager template, create a PowerShell DSC Extension and link it to the Azure virtual machine that will become your Active Directory Domain Controller. Below is an example of the extension you can create in your ARM template (the server that will become the first domain controller is called DC01 in the code below).

Below is an except of the relevant variables

And here are the relevant parameters

That’s it folks! Now you have a new shiny Active Directory Forest, that was created for you using a DSC configuration script.

In the second part of this two-part series, we will add an additional domain controller to this Active Directory Forest using DSC configuration script.

Let me know what you think of the above information.

Category:
Azure Infrastructure, Azure Platform, PowerShell
Tags:
, , ,

Join the conversation! 9 Comments

  1. Hello, I am basically doing the same thing as you, but am running into an issue with the wait for domain bit, getting back basically that the domain can’t be found after 20 tries. Have you run into this at all?

    • Hi Oliver. I haven’t run across that error when setting up the first DC however did get it when I was adding additional computers to the new domain. This was due to the NIC on these machines not having the first DC as one of the dns servers. To fix this I had to update the NIC dns settings once the first DC was created. However this doesn’t seem to be what you are seeing. For domainname, are you using the FQDN? the waitforaddomain function uses Get-ADDomain function. The two things I can recommend is to ensure you are using the FQDN for your AD Domain for $Domain and the DomainCreds passed to the xWaitForADDomain function is in the format domain\user

      • Hey! Yep, I was having that issue before, but I actually just removed the DomainUserCredentials from what I was passing into the resource and it started working.. I was using the FQDN of the domain. Now I am actually running into the issue you were having where I have to update the vnet DNS after the DSC script runs and promotes the DC. Do you have any tips on that? I am currently trying to use a separate json file with updated settings for the vnet but I keep getting an error that says the provided value for DNSServerAddress is not valid, but I believe it is, it’s a variable that defines the IP of the DC.

  2. Actually, I got it to work! Now my vNet has the updated DNS settings, pointing to my current DC and future RODC, but the machines other than the DC need to reboot before they see that they should be using the custom DNS. Did you get around that? Or did your VMs just see the new DNS settings without doing anything extra?

    • Firstly, big thumbs up for persistence. Extremely impressed that you managed to resolve your previous issue. In my implementation, I didn’t have to reboot the machine before it saw the dns. When using ARM templates, the speed gain in implementing the machines comes from running as many parallel installs as you can. In my case, got the vms for svr01 and svr02 created in parallel. once svr01 became dc01, i updated vnet so it had dc01 as dns. then i updated nic of svr02 so that it had dc01 as dns. below is a deployment i created to update the nic. let me know if it helps

      {
      “name”: “UpdateDC02NIC”,
      “type”: “Microsoft.Resources/deployments”,
      “apiVersion”: “2015-01-01”,
      “dependsOn”: [
      “Microsoft.Resources/deployments/UpdateVNetDNS1”
      ],
      “properties”: {
      “mode”: “Incremental”,
      “templateLink”: {
      “uri”: “[variables(‘nicTemplateUri’)]”,
      “contentVersion”: “1.0.0.0”
      },
      “parameters”: {
      “nicName”: {
      “value”: “[parameters(‘DC02NicName’)]”
      },
      “ipConfigurations”: {
      “value”: [
      {
      “name”: “ipconfig1”,
      “properties”: {
      “privateIPAllocationMethod”: “Static”,
      “privateIPAddress”: “[parameters(‘DC02NicIPAddress’)]”,
      “subnet”: {
      “id”: “[variables(‘DC02SubnetRef’)]”
      }
      }
      }
      ]
      },
      “dnsServers”: {
      “value”: [
      “[parameters(‘DC01NicIPAddress’)]”
      ]
      }
      }
      }
      },

  3. Hello, I wonder if anyone can help out. I am trying to deploy the above template. I am finding that the InterFaceAlias for the XDnsServerAddress is not always Ethernet. We have been finding variations such as Ethernet2, Ethernet3 even up to 26. These are all deployments where the templates are only deploying the ARM VM’s with 1 VMNic. This causes the ‘createADForest’ extension to fail because ”Interface \”Ethernet\” is not available”. Do you know of a way to check the alias and pass it into a variable for the InterFaceAlias string, also how this could be done.

    • I have ran into the same problem as you have mentioned, trying to find solutions! Lemme know if you find any. Thanks!

      • Morning Ajinkya. The below works for this.

        In the dsc, I normally add this just after import-dscresources, and the $system.management.automation.PSCredential piece

        $Interface=Get-NetAdapter|Where Name -Like “Ethernet*”|Select-Object -First 1
        $InterfaceAlias=$($Interface.Name)

        Then in the xDNSServerAddress.

        xDNSServerAddress DnsServerAddress
        {
        Address = ‘127.0.0.1’
        InterfaceAlias = $InterfaceAlias
        AddressFamily = ‘IPv4’
        DependsOn = “[WindowsFeature]DNS”
        }

        Andy M

  4. Sorry to bother you after 2 Years. First of all I have to say, after searching so many tutorials/guides, you were the only one to describe the commands. So thank you for that.
    My question is: Can I run this script also on Windows Server 2016 Standard or just on Azure?

Comments are closed.