Create a Replica Domain Controller using Desired State Configuration

Welcome back. In this blog we will continue with our new Active Directory Forest and use Desired State Configuration (DSC) to add a replica domain controller, for redundancy.

If you have not read the first part of this blog series, I would recommend doing that before continuing (even if you need a refresher). The first blog can be found at Create a new Active Directory Forest using Desired State Configuration

Whenever you create an Active Directory forest/domain, it is always a good idea to have at a minimum two domain controllers. This ensures that domain services are available even if one domain controller goes down.

To create a replica domain controller we will be using the xActiveDirectory and xPendingReboot experimental DSC modules. Download these from

https://gallery.technet.microsoft.com/scriptcenter/xActiveDirectory-f2d573f3

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 (unless you have changed your systemroot folder, this will be located at C:\ProgramFiles\WindowsPowerShell\Modules )

With the above done, open Windows Powershell ISE to get started.

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

Configuration CreateADReplicaDC 
{ 
   param 
    ( 
        [Parameter(Mandatory)][String]$DomainName,
        [Parameter(Mandatory)]
        [System.Management.Automation.PSCredential]$Admincreds,
        [Parameter(Mandatory)]
        [System.Management.Automation.PSCredential]$SafemodeAdminCreds,
        [Int]$RetryCount=20,
        [Int]$RetryIntervalSec=30
    )

The above declares the parameters that need to be passed to the CreateADReplicaDC configuration function ($DomainName , $Admincreds, $SafemodeAdminCreds). There are also some variables declared that will be used within the DSC configuration script ($RetryCount and $RetryIntervalSec)

Now we have to import the experimental DSC modules that we had downloaded (unless the DSC modules are present out-of-the-box, they have to be imported in the DSC configuration script, before being used)

Import-DscResource -ModuleName xActiveDirectory, xPendingReboot

The $Admincreds need to be converted into a domain\username format. This is accomplished using the following (the result is held in a variable called $DomainCreds)

[System.Management.Automation.PSCredential]$DomainCreds = New-Object System.Management.Automation.PSCredential ("${DomainName}\$($Admincreds.UserName)", $Admincreds.Password)

Next, we need to tell DSC that the command need to be run on the local computer. This is done by the following line (localhost refers to the local computer)

Node localhost

As mentioned in the first blog, the DSC instructions are passed to the LocalConfigurationManager which carries out the commands. We need to tell the LocalConfigurationManager it should continue with the configuration after a reboot, reboot the server is needed and to just apply the settings only once (DSC can apply a setting and constantly monitor it to check that it has not been changed. If the setting is found to be changed, DSC can re-apply the setting. In our case we will not do this, we will apply the setting just once).

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

Next, we will install the Remote Server Administration Tools

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

Now, lets install Active Directory Domain Services Role

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

Before continuing on, we need to check if the Active Directory Domain that we are going to add the domain controller to is available. This is achieved by using the xWaitForADDomain function in the xActiveDirectory module

xWaitForADDomain DscForestWait 
{ 
   DomainName = $DomainName 
   DomainUserCredential= $DomainCreds
   RetryCount = $RetryCount
   RetryIntervalSec = $RetryIntervalSec
   DependsOn = "[WindowsFeature]ADDSInstall"
}

where
$DomainName is the name of the domain the domain controller will be joined to
$DomainCreds is the Administrator username and password for the domain (the username is in the format domain\username since we had converted this previously)

The DependsOn in the above code means that this section depends on the Active Directory Domain Services role to have been successfully installed.

So far, our script has successfully installed the Active Directory Domain Services role and has checked to ensure that the domain we will join the domain controller to is available.

So, lets proceed. We will now promote the virtual machine to a replica domain controller of our new Active Directory domain.

xADDomainController ReplicaDC 
{ 
   DomainName = $DomainName 
   DomainAdministratorCredential = $DomainCreds 
   SafemodeAdministratorPassword = $SafeModeAdminCreds
   DatabasePath = "C:\NTDS"
   LogPath = "C:\NTDS"
   SysvolPath = "C:\SYSVOL"
   DependsOn = "[xWaitForADDomain]DScForestWait"
}

The above will only run after DscForestWait successfully signals that the Active Directory domain is available (denoted by DependsOn ). The SafeModeAdministratorPassword is set to the supplied password. The DatabasePath, LogPath and SysvolPath are all set to a location on C:\. This might not be suitable for everyone. For those that at are uncomfortable with this, add another data disk to your virtual machine and point the location to this new volume.

The only thing left now is to restart the server. So lets go ahead and do this

xPendingReboot Reboot1
{ 
   Name = "RebootServer"
   DependsOn = "[xADDomainController]ReplicaDC"
}

Keep in mind that the reboot will only happen if the ReplicaDC completed successfully (the DependsOn ensures this).

That’s it. We now have a DSC configuration script that will add a replica domain controller to our Active Directory domain

The virtual machine that will be promoted to a replica domain controller needs to have the first domain controller’s ip address added to its dns settings and it also needs to have network connectivity to the first domain controller. These are required for the DSC configuration script to succeed.

The full DSC Configuration script is pasted below

Configuration CreateADReplicaDC 
{ 
   param 
    ( 
        [Parameter(Mandatory)][String]$DomainName,
        [Parameter(Mandatory)]
        [System.Management.Automation.PSCredential]$Admincreds,
        [Parameter(Mandatory)]
        [System.Management.Automation.PSCredential]$SafemodeAdminCreds,
        [Int]$RetryCount=20,
        [Int]$RetryIntervalSec=30
    )
    Import-DscResource -ModuleName xActiveDirectory, xPendingReboot
    [System.Management.Automation.PSCredential]$DomainCreds = New-Object System.Management.Automation.PSCredential ("${DomainName}\$($Admincreds.UserName)", $Admincreds.Password)

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

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

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

       xWaitForADDomain DscForestWait 
       { 
          DomainName = $DomainName 
          DomainUserCredential= $DomainCreds
          RetryCount = $RetryCount
          RetryIntervalSec = $RetryIntervalSec
          DependsOn = "[WindowsFeature]ADDSInstall"
      }

      xADDomainController ReplicaDC 
      { 
         DomainName = $DomainName 
         DomainAdministratorCredential = $DomainCreds 
         SafemodeAdministratorPassword = $SafeModeAdminCreds
         DatabasePath = "C:\NTDS"
         LogPath = "C:\NTDS"
         SysvolPath = "C:\SYSVOL"
         DependsOn = "[xWaitForADDomain]DScForestWait"
      }

      xPendingReboot Reboot1
      { 
         Name = "RebootServer"
         DependsOn = "[xADDomainController]ReplicaDC"
      }
   }
}

If you would like to bootstrap the above to your Azure Resource Manager template, create a DSC extension and link it to the above DSC Configuration script (the script, together with the experimental DSC modules has to be packaged into a zip file and uploaded to a public location which ARM can access. I used GitHub for this)

One thing to note when bootstrapping is that after you create your first domain controller, you will need to inject its ip address into the dns settings of the virtual machine that will become your replica domain controller. This is required so that the virtual machine can locate the Active Directory domain.

I accomplished this by using the following lines of code in my ARM template, right after the Active Directory Domain had been created.


{
"name": "UpdateDC02NIC",
"type": "Microsoft.Resources/deployments",
"apiVersion": "2015-01-01",
"dependsOn": [],
"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')]"
]
}
}
}
},

where

DC01 is the name of the first domain controller that was created in the new Active Directory domain
DC01NicIPAddress is the ip address of DC01
DC02 is the name of the virtual machine that will be added to the domain as a replica Domain Controller
DC02NicName is the name of the network card on DC02
DC02NicIPAddress is the ip address of DC02
DC02SubnetRef is a reference to the subnet DC02 is in
nicTemplateUri is the url of the ARM deployment template that will update the network interface settings for DC02 (it will inject the DNS settings). This file has to be uploaded to a location that ARM can access (for instance GitHub)

Below are the contents of the deployment template file that nicTemplateUri refers to


{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"nicName": {
"type": "string",
"metadata": {
"Description": "The name of the NIC to Create or Update"
}
},
"ipConfigurations": {
"type": "array",
"metadata": {
"Description": "The IP configurations of the NIC"
}
},
"dnsServers": {
"type": "array",
"metadata": {
"Description": "The DNS Servers of the NIC"
}
}
},
"resources": [
{
"name": "[parameters('nicName')]",
"type": "Microsoft.Network/networkInterfaces",
"location": "[resourceGroup().location]",
"apiVersion": "2015-05-01-preview",
"properties": {
"ipConfigurations": "[parameters('ipConfigurations')]",
"dnsSettings": {
"dnsServers": "[parameters('dnsServers')]"
}
}
}
]
}

After the DNS settings have been updated, the following can be used to create a DSC extension to promote the virtual machine to a replica domain controller (it uses the DSC configuration script we had created earlier)

{
      "type": "Microsoft.Compute/virtualMachines/extensions",
      "name": "[concat(parameters('DC02VMName'),'/CreateReplicaDC')]",
      "apiVersion": "2015-05-01-preview",
      "location": "[resourceGroup().location]",
      "dependsOn": [
        "[concat('Microsoft.Compute/virtualMachines/', parameters('DC02VMName'))]",
        "[concat('Microsoft.Compute/virtualMachines/',parameters('DC01VMName'),'/extensions/CreateADForest')]"
      ],
      "properties": {
        "publisher": "Microsoft.Powershell",
        "type": "DSC",
        "typeHandlerVersion": "2.19",
        "autoUpgradeMinorVersion": true,
        "settings": {
          "ModulesUrl": "[variables('CreateReplicaDCPackageURL')]",
          "ConfigurationFunction": "[variables('CreateReplicaDCConfigurationFunction')]",
          "Properties": {
            "DomainName": "[parameters('domainName')]",
            "AdminCreds": {
              "UserName": "[parameters('adminUserName')]",
              "Password": "PrivateSettingsRef:AdminPassword"
            }
            "SafemodeAdminCreds": {
              "UserName": "[parameters('safemodeAdminUserName')]",
              "Password": "PrivateSettingsRef:safemodeAdminPassword"
            }
          }
        },
        "protectedSettings": {
          "Items": {
            "AdminPassword": "[parameters('adminPassword')]",
            "safemodeAdminPassword": "[parameters('safemodeAdminPassword')]",
          }
        }
      }
    },

The variables used in the json file above are listed below (I have pointed repoLocation to my GitHub repository)

"repoLocation": "https://raw.githubusercontent.com/nivleshc/arm/master/",
"CreateReplicaDCPackageURL": "[concat(parameters('repoLocation'), 'CreateADReplicaDC.zip')]",
"CreateReplicaDCConfigurationFunction": "CreateADReplicaDC.ps1\\CreateADReplicaDC",

This concludes the DSC series for creating a new Active Directory domain and adding a replica domain controller to it.

Hope you enjoyed this blog post.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.