Automate ADFS Farm Installation and Configuration

Introduction

In this multi-part blog, I will be showing how to automatically install and configure a new ADFS Farm. We will accomplish this using Azure Resource Manager templates, Desired State Configuration scripts and Custom Script Extensions.

Overview

We will use Azure Resource Manager to create a virtual machine that will become our first ADFS Server. We will then use a desired state configuration script to join the virtual machine to our Active Directory domain and then to install the ADFS role. Finally, we will use a Custom Script Extension to install our first ADFS Farm.

Install ADFS Role

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, unzip the file and  place the contents in the Powershell modules directory located at $env:ProgramFiles\WindowsPowerShell\Modules (unless you have changed your systemroot folder, this will be located at C:\ProgramFiles\WindowsPowerShell\Modules )

Open your Windows PowerShell ISE and lets create a DSC script that will join our virtual machine to the domain and also install the ADFS role.

Copy the following into a new Windows Powershell ISE file and save it as a filename of your choice (I saved mine as InstallADFS.ps1)

In the above, we are declaring some mandatory parameters and some variables that will be used within the script

$MachineName is the hostname of the virtual machine that will become the first ADFS server

$DomainName is the name of the domain where the virtual machine will be joined

$AdminCreds contains the username and password for an account that has permissions to join the virtual machine to the domain

$RetryCount and $RetryIntervalSec hold values that will be used to  check if the domain is available

We need to import the experimental DSC modules that we had downloaded. To do this, add the following lines to the DSC script

Import-DscResource -Module xActiveDirectory, xPendingReboot

Next, we need to convert the supplied $AdminCreds into a domain\username format. This is accomplished by the following lines (the converted value is held in $DomainCreds )

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

Node localhost

We need to tell the LocalConfigurationManager that it should continue with the configuration after a reboot, reboot the server if 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).

Next, we need to check if the Active Directory domain is ready. For this, we will use the xWaitForADDomain function from the xActiveDirectory experimental DSC module.

Once we know that the Active Directory domain is available, we can go ahead and join the virtual machine to the domain.

the JoinDomain function depends on xWaitForADDomain to finish successfully. If xWaitForADDomain fails, JoinDomain will not run

Once the virtual machine has been added to the domain, it needs to be restarted. We will use xPendingReboot function from the xPendingReboot experimental DSC module to accomplish this

Next, we will install the ADFS role on the virtual machine

Our script has now successfully added the virtual machine to the domain and installed the ADFS role on it. You now have to create a zip file with InstallADFS.ps1 and upload it to a location that Azure Resource Manager can access (I would recommend uploading to GitHub). Include the xActiveDirectory and xPendingReboot experimental DSC modules. Also add a folder called Certificates inside the zip file and put the ADFS certificate and the encrypted password files (discussed in the next section) inside the folder.

In the next section, we will configure the ADFS Farm.

The full InstallADFS.ps1 DSC script is pasted below

Create ADFS Farm

Once the ADFS role has been installed, we will use Custom Script Extensions (CSE) to create the ADFS farm.

One of the requirements to configure ADFS is a signed certificate. I used a 90 day trial certificate from Comodo.

There is a trick that I am using to make my certificate available on the virtual machine. If you bootstrap a DSC script to your virtual machine in an Azure Resource Manager template, the script along with all the non out-of-box DSC modules have to be packaged into a zip file and uploaded to a location that ARM can access. Then, before ARM uses the script, it downloads the zip file and unzips it, and copies the directories inside the zip file to $env:ProgramFiles\WindowsPowerShell\Modules ( C:\ProgramFiles\WindowsPowerShell\Modules )

I am using this feature to sneak my certificate on to the virtual machine. I create a folder called Certificates inside the zip file containing the DSC script and put the certificate inside it. Also, I am not too fond of passing plain passwords from my ARM template to the CSE, so I created two files, one to hold the encrypted password for the domain administrator account and the other to contain the encrypted password of the adfs service account. These two files are named adminpass.key and adfspass.key and will be placed in the Certificates folder within the zip file.

I used the following to generate the encrypted password files

$AdminSecurePassword = ConvertTo-SecureString {AdminPlainTextPassword} -AsPlainText -Force
ConvertFrom-SecureString $AdminSecurePassword -Key $key > adminpass.key

$ADFSSecurePassword = ConvertTo-SecureString {ADFSPlainTextPassword} -AsPlainText -Force
ConvertFrom-SecureString $ADFSSecurePassword -Key $key > adfspass.key

The $key  is used to convert the secure string into an encrypted standard string. Valid key lengths are 16, 24, 32

For this blog, we will use

$Key = (3,4,2,3,56,34,254,222,1,1,2,23,42,54,33,233,1,34,2,7,6,5,35,43)

Open Windows Powershell ISE and paste the following (save the file with a name of your choice. I saved mine as ConfigureADFS.ps1)

param (
 $DomainName,
 $DomainAdminUsername,
 $AdfsSvcUsername
)

These are the parameters that will be passed to the CSE

$DomainName is the name of the Active Directory domain
$DomainAdminUsername is the username of the domain administrator account
$AdfsSvcUsername is the username of the ADFS service account

Next, we will define the value of the Key that was used to encrypt the password and the location where the certificate and the encrypted password files will be placed

$localpath = "C:\Program Files\WindowsPowerShell\Modules\Certificates\"
$Key = (3,4,2,3,56,34,254,222,1,1,2,23,42,54,33,233,1,34,2,7,6,5,35,43)

Now, we have to read the encrypted passwords from the adminpass.key and adfspass.key file and then convert them into a domain\username format

Next, we will import the certificate into the local computer certificate store. We will mark the certificate exportable and set the password same as the domain administrator password.

In the above after the certificate is imported,  $cert is used to hold the certificate thumbprint

Next, we will configure the ADFS Farm

The ADFS Federation Service displayname is set to Active Directory Federation Service and the Federation Service Name is set to fs.adfsfarm.com

Upload the CSE to a location that Azure Resource Manager can access (I uploaded my script to GitHub)

The full ConfigureADFS.ps1 CSE is shown below

Azure Resource Manager Template Bootstrapping

Now that the DSC and CSE scripts have been created, we need to add them in our ARM template, straight after the virtual machine is provisioned.

To add the DSC script, create a DSC extension and link it to the DSC Package that was created to install ADFS. Below is an example of what can be used

The extension will run after the ADFS virtual machine has been successfully created (referred to as ADFS01VMName)

The MachineName, DomainName and domain administrator credentials are passed to the DSC extension.

Below are the variables that have been used in the json file for the DSC extension (I have listed my GitHub repository location)

Next, we have to create a Custom Script Extension to link to the CSE for configuring ADFS. Below is an example that can be used

The CSE depends on the ADFS virtual machine being successfully provisioned and the DSC extension that installs the ADFS role to have successfully completed.

The DomainName, Domain Administrator Username and the ADFS Service Username are passed to the CSE script

The following contains a list of the variables being used by the CSE (the example below shows my GitHub repository location)

"repoLocation": "https://raw.githubusercontent.com/nivleshc/arm/master/",
"ConfigureADFSScriptUrl": "[concat(parameters('repoLocation'),'ConfigureADFS.ps1')]",

That’s it Folks! You now have an ARM Template that can be used to automatically install the ADFS role and then configure a new ADFS Farm.

In the next blog, we will explore how to add another node to the ADFS Farm and we will also look at how we can automatically create a Web Application Proxy server for our ADFS Farm.

Advertisements

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.

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

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.

Usable IP Addresses in an Azure {and AWS} Subnet

Over the last week I got asked an interesting question.

“Why is it that you always give x.x.x.4 ip address to you first Azure virtual machine? Why don’t you start with x.x.x.1 ?”

This is a very interesting question, and needs to be kept in mind whenever transitioning from On-Premise to Azure. In Azure, there are a few IP addresses that are reserved for system use and cannot be allocated to virtual machines.

The first and last IP addresses of a subnet have always been unavailable for machine addressing because the first IP address is the network address and the last is the broadcast address for the subnet.

In addition to the above, the next 3 IP addresses from the beginning are used by Azure for internal use.

Always keep the above in mind when allocating IP addresses in Azure.

Below are some helpful links that can provide more information

https://azure.microsoft.com/en-us/documentation/articles/virtual-networks-faq/

https://www.petri.com/understanding-ip-addressing-microsoft-azure

I came across a similar article for AWS. AWS also removes 5 IP addresses from the pool, for internal use. However, this article was more informative in regards to why these IP addresses are unavailable. I have a suspicion that Azure has the same reasons, however I couldn’t find any article on it.

Here is the AWS article http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Subnets.html

and below is the section that describes why the IP addresses are unavailable in AWS

The first four IP addresses and the last IP address in each subnet CIDR block are not available for you to use, and cannot be assigned to an instance. For example, in a subnet with CIDR block 10.0.0.0/24, the following five IP addresses are reserved:

  • 10.0.0.0: Network address.
  • 10.0.0.1: Reserved by AWS for the VPC router.
  • 10.0.0.2: Reserved by AWS for mapping to the Amazon-provided DNS. (Note that the IP address of the DNS server is the base of the VPC network range plus two. For more information, see Amazon DNS Server.)
  • 10.0.0.3: Reserved by AWS for future use.
  • 10.0.0.255: Network broadcast address. We do not support broadcast in a VPC, therefore we reserve this address.

Stream Xbox games to your Hololens

I was fortunate enough to borrow Hololens for a few days. I had read all about this amazing device, however having it all to myself for a few days was a dream come true. The stars had finally lined up in my favour!

Being an amateur astronomer, the first app I installed was Galaxy Explorer. If you don’t know much about this gorgeous app, I would highly suggest you checkout this YouTube video.  This app takes you on an amazing journey, of our Solar System and of some nearly star systems. The graphics are amazing and even more amazing is the realism that mixed reality brings, it’s as if you are there, hovering in space and looking at the stars. A totally out of this world (pun intended), stunning and breathtaking experience.

While using Hololens, I wondered how good it would be to play Xbox games on it. It would be amazing, to just choose any wall and use it as the screen for your games. However, a search of the Hololens store proved futile as there was no Xbox app for Hololens 😦

Next stop was to find a universal Xbox app, as Hololens is running a variant of Windows 10. I found the beta version of the Xbox app, downloaded and installed it on the Hololens.

pic_app_xboxbeta

Next, I powered up my Xbox One and made sure it was enabled to stream games to a Windows 10 device. If you are unsure how to enable this,  click on this link

Both your Hololens and Xbox One have to be on the same network for this to work.

I logged into my Xbox One console using my Xbox Live account. Next I started the Xbox app on Hololens and logged in using the same Xbox Live account that I used on my Xbox One console.

pic_app_xboxbeta_signin

Once logged into the Xbox app, I scrolled down on the left menu till I saw the Connect option.

pic_app_xboxbeta_connect

When I air tapped on this, the app started a discovery of all Xbox consoles on the network and showed them on the right. I saw my Xbox One console listed, and I air tapped the Connect button underneath it (if your Xbox console is not detected, enter its ip address to find it).

pic_app_xboxbeta_console_connect

Yay! My Xbox One console was now connected to Hololens. I got an option to either start the  Stream or Test Streaming. Test Streaming is used to check the quality of the stream over your local network.

pic_app_xboxbeta_console_streamoptions

I air tapped on Stream and Viola! My Xbox One screen was now be visible inside Hololens.

I got a warning that there were no controllers attached to the PC (it was actually referring to Hololens).  Since Hololens does not have any usb connectors, a wireless controller, connected to the Xbox One console, needs to be used.

pic_app_xboxbeta_controllerwarning

I air tapped Continue as I already had a wireless controller connected to my console.

I was now streaming my Xbox One screen on Hololens and could use my wireless controller to move around and play games! And I was doing this on a wall in my living room! Who said I needed a TV to play Xbox.

pic_app_xboxbeta_streamstarted

The streaming quality is dependant on your local network speed. I didn’t notice any lags when playing Thief or Forza 5. However, I would not recommend being far from your Xbox console, as the wireless controller can get disconnected.

Hololens is amazing. It can be used for serious work and for fun. I hope this post shows you yet another possibility that Hololens opens up to us.

 

Create a new Active Directory Forest using Desired State Configuration

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 the next blog, 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 an 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 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 to extend this library! 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 is 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, 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 contants

$DomainName         -  FQDN for the Active Directory Domain to create
$AdminCreds         -  a PSCredentials object that contains username and password 
                       that will be assigned to the Domain Administrator account
$SafeModeAdminCreds -  a PSCredentials object that contains the password that will
                       be assigned to the Safe Mode Administrator account
$myFirstUserCreds   -  a PSCredentials object that contains the username and
                       password for the first domain user account to create
$RetryCount         -  defines how many retries should be performed while waiting
                       for the domain to be provisioned
$RetryIntervalSec   -  defines the seconds between each retry to check if the 
                       domain has been provisioned 

 

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 

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

 

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 on 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 refers 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.

xADDomain FirstDC
{
     DomainName = $DomainName
     DomainAdministratorCredential = $DomainCreds
     SafemodeAdministratorPassword = $SafeModeAdminCreds
     DatabasePath = "C:\NTDS"
     LogPath = "C:\NTDS"
     SysvolPath = "C:\SYSVOL"
     DependsOn = "[WindowsFeature]ADDSInstall","[xDnsServerAddress]DnsServerAddress"
}

 

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)

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
  )
  Import-DscResource -ModuleName xActiveDirectory, xNetworking, 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 DNS
     {
         Ensure = "Present"
         Name = "DNS"
     }

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

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

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

     xADDomain FirstDC
     {
          DomainName = $DomainName
          DomainAdministratorCredential = $DomainCreds
          SafemodeAdministratorPassword = $SafeModeAdminCreds
          DatabasePath = "C:\NTDS"
          LogPath = "C:\NTDS"
          SysvolPath = "C:\SYSVOL"
          DependsOn = "[WindowsFeature]ADDSInstall","[xDnsServerAddress]DnsServerAddress"
     }

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

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

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

 

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 (referred to as DC01 in the code below).

"resources": [
  {
    "type": "Microsoft.Compute/virtualMachines/extensions",
    "name": "[concat(parameters('DC01Name'),'/CreateNewADForest')]",
    "location": "[resourceGroup().location]",
    "apiVersion": "2015-06-15",
    "dependsOn": [
         "[concat('Microsoft.Compute/virtualMachines/', parameters('DC01Name'))]"
    ],
    "tags": {
         "displayName": "CreateNewADForest"
    },
    "properties": {
         "publisher": "Microsoft.Powershell",
         "type": "DSC",
         "typeHandlerVersion": "2.19",
         "autoUpgradeMinorVersion": true,
         "settings": {
              "Modulesurl": "[variables('CreateNewADForestPackageURL')]",
              "ConfigurationFunction":"[variables('CreateNewADForestConfigurationFunction')]",
              "Properties": {
                   "DomainName": "[parameters('domainName')]",
                   "AdminCreds": {
                        "UserName": "[parameters('adminUserName')]",
                        "Password": "PrivateSettingsRef:AdminPassword"
                   },
                   "SafeModeAdminCreds": {
                        "UserName": "[parameters('safemodeAdminUserName')]",
                        "Password": "PrivateSettingsRef:SafeModeAdminPassword"
                   },
                   "myFirstUserCreds": {
                        "UserName": "[parameters('myFirstUserName')]",
                        "Password": "PrivateSettingsRef:MyFirstUserPassword"
                   }

              }
         },
         "protectedSettings": {
               "Items": {
               "AdminPassword": "[parameters('adminPassword')]",
               "SafeModeAdminPassword": "[parameters('safemodeadminPassword')]",
               "MyFirstUserPassword": "[parameters('myFirstUserPassword')]"
         }
    }
  }
]

 

Below is an except of the relevant variables

"repoLocation": "https://raw.githubusercontent.com/nivleshc/arm/master/",
"CreateNewADForestPackageURL": "[concat(variables('repoLocation'), 'CreateNewADForest.zip')]",
"CreateNewADForestConfigurationFunction": "CreateNewADForest.ps1\\CreateNewADForest",

And these 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.

Read More »