Build an on-premises Domain Controller (DC) using Desired State Configuration

In my last post I described how to create a DC, in Azure, using DSC and ARM Templates.  In this post, we will discuss how to automate the creation of a local – on premises – Acitve Directory Domain Services (ADDS) Domain Controller (DC) using DSC.

Overview

DSC has two modes: push mode and pull mode.

In push mode, you will author the configuration.  You will then stage the configuration by creating MOF files.  And finally, you will manually push the desired configuration onto the target server or node.  The target server can be the local server or a remote server.

On the other hand, in DSC PULL mode, you author the configuration and stage it onto a designated Pull server.  The target nodes contact the central pull server at regular intervals to obtain their desired configuration.

In our scenario, we will be using DSC in push mode.  We will author the configuration and push it onto the local Windows server (not remotely).

Details

Prerequisites

On the target Windows Server (Windows Server 2008 R2 SP1 and Windows Server 2012 or 2012 R2):

  1. Download and install Windows Management Framework (WMF).  This WMF 5.0 is currently available and is the recommended version.
  2. Copy the script below to the target server
  3. Open the script below in Powershell ISE as administrator
  4. Install the required Powershell modules using install-module: xActiveDirectory, xComputerManagement, xNetworking and xStorage.

Run the script

Run the script in Powershell ISE.  The first command creates the .mof files which contain the desired configuration.  The second command actually applies the configuration to the local server.  After about half an hour and one reboot, you will have a fully functional Domain Controller with a new user (domain admin).

# Configure all of the settings we want to apply for this configuration
$ConfigData = @{
    AllNodes = @(
        @{
            NodeName = 'localhost'
            MachineName = 'spfarm-ad'
            IPAddress = '10.0.0.4'
            InterfaceAlias = 'Ethernet'
            DefaultGateway = '10.0.0.1'
            PrefixLength = '24'
            AddressFamily = 'IPv4'
            DNSAddress = '127.0.0.1', '10.0.0.4'
            PSDscAllowPlainTextPassword = $true
            PSDscAllowDomainUser = $true
        }
    )
}

Configuration BuildADDC {

    param (
        [Parameter(Mandatory)]
        [String]$FQDomainName,

        [Parameter(Mandatory)]
        [PSCredential]$DomainAdminstratorCreds,

        [Parameter(Mandatory)]
        [PSCredential]$AdmintratorUserCreds,

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

    Import-DscResource -ModuleName PSDesiredStateConfiguration
    Import-DscResource -ModuleName xActiveDirectory, `
                                    xComputerManagement, `
                                    xNetworking,
									xStorage
 
    Node $AllNodes.NodeName 
    {
        LocalConfigurationManager 
        {
            ActionAfterReboot = 'ContinueConfiguration'            
            ConfigurationMode = 'ApplyOnly'            
            RebootNodeIfNeeded = $true  
        }

        # Change Server Name
        xComputer SetName { 
          Name = $Node.MachineName 
        }

        # Networking
        xDhcpClient DisabledDhcpClient
        {
            State          = 'Disabled'
            InterfaceAlias = $Node.InterfaceAlias
            AddressFamily  = $Node.AddressFamily
        }

        xIPAddress NewIPAddress
        {
            IPAddress      = $Node.IPAddress
            InterfaceAlias = $Node.InterfaceAlias
            PrefixLength   = $Node.PrefixLength
            AddressFamily  = $Node.AddressFamily
        }

        xDefaultGatewayAddress SetDefaultGateway
        {
            Address        = $Node.DefaultGateway
            InterfaceAlias = $Node.InterfaceAlias
            AddressFamily  = $Node.AddressFamily
            DependsOn = '[xIPAddress]NewIPAddress'
        }
       
        xDNSServerAddress SetDNS {
            Address = $Node.DNSAddress
            InterfaceAlias = $Node.InterfaceAlias
            AddressFamily = $Node.AddressFamily
        }

        # Install the Windows Feature for AD DS
        WindowsFeature ADDSInstall {
            Ensure = 'Present'
            Name = 'AD-Domain-Services'
        }

        # Make sure the Active Directory GUI Management tools are installed
        WindowsFeature ADDSTools            
        {             
            Ensure = 'Present'             
            Name = 'RSAT-ADDS'             
        }           

        # Create the ADDS DC
        xADDomain FirstDC {
            DomainName = $FQDomainName
            DomainAdministratorCredential = $DomainAdminstratorCreds
            SafemodeAdministratorPassword = $DomainAdminstratorCreds
            DependsOn = '[xComputer]SetName','[xDefaultGatewayAddress]SetDefaultGateway','[WindowsFeature]ADDSInstall'
        }   
        
        $domain = $FQDomainName.split('.')[0] 
        xWaitForADDomain DscForestWait
        {
            DomainName = $domain
            DomainUserCredential = $DomainAdminstratorCreds
            RetryCount = $RetryCount
            RetryIntervalSec = $RetryIntervalSec
            DependsOn = '[xADDomain]FirstDC'
        } 

        #
        xADRecycleBin RecycleBin
        {
           EnterpriseAdministratorCredential = $DomainAdminstratorCreds
           ForestFQDN = $domain
           DependsOn = '[xADDomain]FirstDC'
        }
        
        # Create an admin user so that the default Administrator account is not used
        xADUser FirstUser
        {
            DomainAdministratorCredential = $DomainAdminstratorCreds
            DomainName = $domain
            UserName = $AdmintratorUserCreds.UserName
            Password = $AdmintratorUserCreds
            Ensure = 'Present'
            DependsOn = '[xADDomain]FirstDC'
        }
        
        xADGroup AddToDomainAdmins
        {
            GroupName = 'Domain Admins'
            MembersToInclude = $AdmintratorUserCreds.UserName
            Ensure = 'Present'
            DependsOn = '[xADUser]FirstUser'
        }
        
    }
}

# Build MOF (Managed Object Format) files based on the configuration defined above 
# (in folder under current dir) 
# Local Admin is assigned 
BuildADDC -ConfigurationData $ConfigData `
          -FQDomainName 'spdomain.local' `
          -DomainAdminstratorCreds (get-credential -Message "Enter Admin Credentials" -UserName "Administrator" ) `
          -AdmintratorUserCreds (get-credential -Message "Enter New Admin User Credentials" -UserName "admin1" ) 

# We now enforce the configuration using the command syntax below
Start-DscConfiguration -Wait -Force -Path .\BuildADDC -Verbose -Debug

Lessons Learned

Since the Powershell xActiveDirectory module is being updated all the time, a DSC script that worked a year ago needs to be updated to work with WMF 5.0 (in the last quarter of 2016).

WMF 5.0 is included in the latest version of Windows 10 and included on Windows Server 2016.

Issues and Solutions

With some of the xActiveDirectory resources, the use of the fully qualified domain name (FQDN) produced an error: “Could not find mandatory property DomainName. Add this property and try again.”

Solution: use the first part of the domain name