Blog Post

Creating a new forest and multiple domain controllers with PowerShell DSC

We’re continuing our series on automating the installation of GigaCloud using PowerShell DSC. GigaTrust is creating a fully automated solution to set up and install GigaCloud™ in whatever configuration our customers want: on-premises in Hyper-V and VMWare, in their own cloud subscriptions using Microsoft Azure, and even fully managed by GigaTrust as Software as a Service (SaaS).

GigaCloud is deployed as a virtual appliance, with required services deployed across multiple virtual machines to provide redundancy and scale. To keep the deployments self-contained and easy to test and deploy, we install GigaCloud into its own Active Directory forest, which means that we have to create a domain controller for the new forest. As we all know, you never create just one domain controller in production, so we also have to create a backup domain controller. I’ll walk you through how we do that with PowerShell Desired State Configuration.

(I’ve extracted the important parts in the code snippets below. We do a lot more during GigaCloud installation.)

High-level overview

PowerShell DSC has great support for creating forests and domains. Everything we’re doing can be found in the xActiveDirectory module, which is open source and maintained by the PowerShell team. You can install it with:

<code block – monospaced font with source code coloring like StackOverflow>

Install-Module xActiveDirectory

</code block>

Once we’ve installed it, we create the configuration. There’s a lot of code, so let’s go over the process first:

  • Gather some information about the domain we’re going to create.
  • First node: domain controller
    • Make sure DNS and AD DS are installed.
    • Set the IP address of the machine.
    • Create the new domain.
    • Add the alternate administrator account to the new domain.
  • Second node: domain controller
    • Make sure DNS and AD DS are installed.
    • Set the IP address of the machine.
    • Set the DNS Server of the machine to point to the first domain controller.
    • Wait for the new domain to be created.
    • Once the new domain exists, join the machine to the domain.
    • Make the computer into a domain controller.
    • Set the DNS Server back to localhost, as the machine is now a DNS server.

Specifying the DSC configuration

The code for the DSC configuration is below. We’ll define the variables later, before we compile the configuration and run it.

<code block>

Configuration DomainControllers

{

param

(

[Parameter(Mandatory = $true)][PSCredential]$SafeModeCredentials,

[Parameter(Mandatory = $true)][PSCredential]$VMCredentials,

[Parameter(Mandatory = $true)][PSCredential]$DomainAdministratorCredentials,

[Parameter(Mandatory = $true)][string]$AdministratorAccount,

[Parameter(Mandatory = $true)][string]$FirstDomainControllerName,

[Parameter(Mandatory = $true)][string]$FirstDomainControllerIPAddress,
[Parameter(Mandatory = $true)][string]$FirstDomainControllerName,

[Parameter(Mandatory = $true)][string]$SecondDomainControllerIPAddress,

[Parameter(Mandatory = $true)][string]$GatewayAddress,

[Parameter(Mandatory = $true)][string]$SubnetMask,

[Parameter(Mandatory = $true)][string]$DomainName,

[Parameter(Mandatory = $true)][string]$DomainDnsName

)
Import-Module PSDesiredStateConfiguration

Import-Module xActiveDirectory

 

Import-DscResource -ModuleName PSDesiredStateConfiguration

Import-DscResource -ModuleName xActiveDirectory

 

Node $FirstDomainControllerName

{

File ADFiles

{

DestinationPath = 'C:\NTDS'

Type = 'Directory'

Ensure = 'Present'

}

 

WindowsFeature DNS

{

Ensure = "Present"

Name = "DNS"

}




xIPAddress SetIP
{

IPAddress = $FirstDomainControllerIPAddress

InterfaceAlias = 'Ethernet'

DefaultGateway = $GatewayAddress

SubnetMask = $SubnetMask

AddressFamily = 'IPv4'

}




xDnsServerAddress DnsServerAddress

{
Address        = '127.0.0.1'

InterfaceAlias = 'Ethernet'

AddressFamily  = 'IPv4'

DependsOn = "[WindowsFeature]DNS"

}

WindowsFeature AD-Domain-Services

{

Ensure = "Present"

Name = "AD-Domain-Services"

DependsOn = "[File]ADFiles"

}




# These RSAT features are optional, but you may want

#  the admin tools installed locally.

WindowsFeature RSAT-DNS-Server

{

Ensure = "Present"

Name = "RSAT-DNS-Server"

DependsOn = "[WindowsFeature]DNS"

}


WindowsFeature RSAT-AD-Tools

{

Name = 'RSAT-AD-Tools'

Ensure = 'Present'

DependsOn = "[WindowsFeature]AD-Domain-Services"

}

WindowsFeature RSAT-ADDS
{

Ensure = "Present"

Name = "RSAT-ADDS"

DependsOn = "[WindowsFeature]AD-Domain-Services"

}




WindowsFeature RSAT-ADDS-Tools

{

Name = 'RSAT-ADDS-Tools'

Ensure = 'Present'
DependsOn = "[WindowsFeature]RSAT-ADDS"

}




WindowsFeature RSAT-AD-AdminCenter

{

Name = 'RSAT-AD-AdminCenter'

Ensure = 'Present'

DependsOn = "[WindowsFeature]AD-Domain-Services"

}

 

# Here’s where we create the domain.

# No slash at end of folder paths.
xADDomain PrimaryDC

{

DomainName = $DomainDnsName

DomainNetbiosName = $DomainName

DomainAdministratorCredential = $DomainAdministratorCredentials

SafemodeAdministratorPassword = $SafeModeCredentials

DatabasePath = 'C:\NTDS'

LogPath = 'C:\NTDS'

DependsOn = "[WindowsFeature]AD-Domain-Services"

}

 

# Here’s where we create the alternate administrator account,

#  and add it to the appropriate groups.

xADUser AlternateAdminUser

{

DomainName = $DomainDnsName

UserName = $AdministratorAccount

Password = $DomainAdministratorCredentials # Uses just the password

DisplayName = $AdministratorAccount

PasswordAuthentication = 'Negotiate'

DomainAdministratorCredential = $DomainAdministratorCredentials

Ensure = 'Present'

DependsOn = "[xADDomain]PrimaryDC"

}




xADGroup AddAdminToDomainAdminsGroup

{
GroupName = "Domain Admins"

GroupScope = 'Global'

Category = 'Security'

MembersToInclude = @($AdministratorAccount, "Administrator")

Ensure = 'Present'

Credential = $DomainAdministratorCredentials

DependsOn = "[xADUser]AlternateAdminUser"

}

 

xADGroup AddAdminToEnterpriseAdminsGroup

{

GroupName = "Enterprise Admins"

GroupScope = 'Universal'

Category = 'Security'

MembersToInclude = @($AdministratorAccount, "Administrator")
Ensure = 'Present'

Credential = $DomainAdministratorCredentials

DependsOn = "[xADUser]AlternateAdminUser"

}

}




Node $SecondDomainControllerName
{

<# Using a ScriptBlock so I can do string substitution. Also, I'm using xDnsServerAddress below, for after the domain controller is configured, and you can't use the same configuration resource twice in one DSC config (which one should it use? = error), so a ScriptBlock is necessary to allow this machine to find the PDC and its DNS server. #>

[ScriptBlock]$SetScript =

{

Set-DnsClientServerAddress -InterfaceAlias Ethernet -ServerAddresses ("$FirstDomainControllerIPAddress")

}
Script SetDnsServerAddressToFindPDC

{

GetScript = {return @{}}

TestScript = {return $false} # Always run the SetScript for this.

SetScript = $SetScript.ToString().Replace('$FirstDomainControllerIPAddress', $FirstDomainControllerIPAddress)

}

 

File ADFiles

{
DestinationPath = 'C:\NTDS'

Type = 'Directory'

Ensure = 'Present'

}




WindowsFeature DNS

{

Ensure = "Present"

Name = "DNS"

DependsOn = "[File]ADFiles"

}

 

WindowsFeature AD-Domain-Services

{

Ensure = "Present"

Name = "AD-Domain-Services"

DependsOn = "[WindowsFeature]DNS"

}

 

xIPAddress SetIP

{

IPAddress = $SecondDomainControllerIPAddress

InterfaceAlias = 'Ethernet'

DefaultGateway = $GatewayAddress

SubnetMask = $SubnetMask

AddressFamily = 'IPv4'

}

 

# RSAT tools are optional, but you may want them installed locally.

WindowsFeature DnsTools

{

Ensure = "Present"

Name = "RSAT-DNS-Server"

DependsOn = "[WindowsFeature]DNS"

}




WindowsFeature RSAT-AD-Tools

{

Name = 'RSAT-AD-Tools'

Ensure = 'Present'

DependsOn = "[WindowsFeature]AD-Domain-Services"

}

 

WindowsFeature RSAT-ADDS

{

Ensure = "Present"

Name = "RSAT-ADDS"

DependsOn = "[WindowsFeature]AD-Domain-Services"

}

 

WindowsFeature RSAT-ADDS-Tools

{

Name = 'RSAT-ADDS-Tools'

Ensure = 'Present'

DependsOn = "[WindowsFeature]RSAT-ADDS"

}
WindowsFeature RSAT-AD-AdminCenter

{

Name = 'RSAT-AD-AdminCenter'

Ensure = 'Present'

DependsOn = "[WindowsFeature]AD-Domain-Services"

}

 

# Wait for the first domain controller to be set up before we continue.

xWaitForADDomain WaitForPrimaryDC

{

DomainName = $DomainName

DomainUserCredential = $DomainAdministratorCredentials

RetryCount = 600

RetryIntervalSec = 30

RebootRetryCount = 10

DependsOn = @("[Script]SetDnsServerAddressToFindPDC")

}
# Join this computer to the domain; this should cause a reboot.

# Note that we depend on the previous task to wait for the domain.

xComputer JoinDomain

{

Name = ($DCPrefix + $i.ToString().PadLeft(2, '0'))

DomainName = $DomainDnsName

Credential = $DomainAdministratorCredentials

DependsOn = "[xWaitForADDomain]WaitForPrimaryDC"

}

 

# Add this computer as a domain controller

xADDomainController SecondaryDC

{

DomainName = $DomainDnsName

DomainAdministratorCredential = $DomainAdministratorCredentials

SafemodeAdministratorPassword = $SafeModeCredentials

DatabasePath = 'C:\NTDS'

LogPath = 'C:\NTDS'

DependsOn = @("[WindowsFeature]AD-Domain-Services","[xComputer]JoinDomain")

}

 

# Now make sure this computer uses itself as a DNS source

xDnsServerAddress DnsServerAddress

{

Address        = @('127.0.0.1', $FirstDomainControllerIPAddress)

InterfaceAlias = 'Ethernet'

AddressFamily  = 'IPv4'

DependsOn = "[xADDomainController]SecondaryDC"

}
xDnsServerAddress DnsServerAddressv6

{

Address        = '::1'

InterfaceAlias = 'Ethernet'

AddressFamily  = 'IPv6'

DependsOn = "[xADDomainController]SecondaryDC"

}

}

}

</code block>

Compiling and running the configuration

Now that the configuration exists, we need to compile it using the specific values we need. After that, we’ll push the configuration to the nodes.

<code block>

# Here’s where we name the servers and the domain.

$FirstDomainControllerName = ‘DC01’

$FirstDomainControllerIPAddress = ‘www.xxx.yyy.zzz’

$SecondDomainControllerName = ‘DC02’

$SecondDomainControllerIPAddress = ‘www.xxx.yyy.zzz’


$GatewayAddress = ‘www.xxx.yyy.zzz’

$SubnetMask = 16 # Or 8, or 24, etc.


$DomainName = ‘MYAWESOMEDOMAIN’

$DomainDnsName = ‘myawesomedomain.com’

 

# We’ll disable the Administrator account; this is the name of the account that will become the new administrator.

$AdministratorAccount = ‘ADifferentUsernameThanAdministrator

 

$VMCredentials = Get-Credential -Message "Enter the local administrator credentials." -UserName $AdministratorAccount


# This is where we’ll type in the password of the new administrator account.

$DomainAdministratorCredentials = Get-Credential -Message "Enter the domain administrator credentials." -UserName ($DomainName + ‘\’ + $AdministratorAccount)


# This is used just to type in the safe mode password; the username isn’t used.

$SafeModeCredentials = Get-Credential -Message "Enter the new domain's Safe Mode administrator password." -UserName '(Password Only)'

 

DomainControllers -SafeModeCredentials $SafeModeCredentials `

-VMCredentials $VMCredentials `

-DomainAdministratorCredentials $DomainAdministratorCredentials `
-FirstDomainControllerName $FirstDomainControllerName `

-FirstDomainControllerIPAddress $FirstDomainControllerIPAddress `

-SecondDomainControllerName $SecondDomainControllerName `

-SecondDomainControllerIPAddress $SecondDomainControllerIPAddress `

-GatewayAddress $GatewayAddress `

-SubnetMask $SubnetMask `

-DomainName $DomainName`

-DomainDnsName $DomainDnsName`

-AdminstratorAccount $AdministratorAccount

 

</code block>

And now, let’s push to each node:

<code block>

$VMSession1 = New-CimSession -Credential $VMCredentials -ComputerName $FirstDomainControllerName -Verbose

$VMSession2 = New-CimSession -Credential $VMCredentials -ComputerName $SecondDomainControllerName -Verbose


Start-DscConfiguration -Path '.\DomainControllers -CimSession $VMSession1 -Verbose -Wait -Force

Start-DscConfiguration -Path '.\DomainControllers -CimSession $VMSession2 -Verbose -Wait -Force

 

</code block>

 

This should get us going. After the configurations are pushed, it will take some minutes for them to complete. Along the way, both computers will reboot and then resume configuration after the reboots. Once they’re done, we should have a new forest, new domain, and two domain controllers.

More to do

We’re just getting started in sharing our DSC journey. At GigaTrust, we now have an automated solution to install GigaCloud in Azure, and on Hyper-V, with as little administrative effort as possible. In future posts, we’ll talk about how to secure this configuration, how we automated the installation of the Rights Management Server roles and the ADFS roles, and how we create SQL clusters to tie it all together. Please stay tuned!

By Scott Arbeit, Chief Cloud Architect