Jump to content

Recommended Posts

Posted

Our organisation currently has 3x Server 2008 R2 Domain Controllers. We are looking to start rolling out Windows 10, so before we do this we want to make sure all of our DC's are at Server 2016 with a domain functional level of 2016.

 

 

Here are the two options we are thinking, what do people prefer/recommend/suggest? What are the advantages and disadvantages of each method?

 

 

Method 1 - Upgrade

We would essentially take a DC, get a Server 2016 ISO and use the upgrade method to get the server running Server 2016. Do we still have to AD Prep for this and are there any other things to consider? We can just replicate this process for all 3x DCs to get them running Server 2016 and then update the functional level.

 

 

Method 2 - New installs

Provision 3x new DCs on server 2016 and different static IP addresses with different names. Add them all into the existing domain as member servers. At this point we can promote them to domain controllers (would we need to run ADPrep for this, or is this automated now?)

Once they are DC's, we can migrate our FSMO roles and other services on the old DCs to the new ones. We can then demote a 2008 DC and power it off, and move one of the new 2016 DCs onto the old static IP that the previous 2008 DC was on. Do this for all 3 servers one at a time. Then raise the functional level.

 

Have I missed anything here, is there anything we should consider. What are the advantages and disadvantages of each method here?

Any advice and suggestions will be highly appreciated here.

Thanks.

Posted

Always best to do a clean install when possible. In place upgrades can be problematic. I have done them in the past though.

You would need to install the ADDS role and following the configuration steps to promote to a domain controlled. The days of dcpromo are gone now.

 

It's easy enough and you seem to have in planned out. Don't forget dhcp too.

Posted
Provision 3x new DCs on server 2016 and different static IP addresses with different names. Add them all into the existing domain as member servers. At this point we can promote them to domain controllers (would we need to run ADPrep for this, or is this automated now?) Once they are DC's, we can migrate our FSMO roles and other services on the old DCs to the new ones. We can then demote a 2008 DC and power it off, and move one of the new 2016 DCs onto the old static IP that the previous 2008 DC was on. Do this for all 3 servers one at a time. Then raise the functional level.

 

We did pretty much exactly that a couple of years ago to upgrade to Server 2012 R2. The process was actually smoother than I thought it might be - I don't think we had any major issues, we just took our time and double-checked everything as we went along.

Posted
Thanks for this, has anyone had any issues or problems changing the IP address of a domain controller? Just because once the new DC's are installed, we will decommission the old DCs one at a time and use the old IPs for the new servers just because we have a lot of services and systems that will rely on our DCs being on those previous IP addresses to look for RADIUS, DHCP, DNS etc etc.
Posted

Dhcp and dns won't matter too much if the address is changed anyway.

But to answer your question, as long as there are no conflicts of ip then it's no issue.

  • 2 weeks later...
Posted

that is exactly how i have always done it in the past all the way from 2003 to 2008r2 to 2012 to 2016!

Prepare the new DC windows and updates... Demote the old one... remove from the domain...

change ip of new one to the old one... Rename the new one to the DCs name... add to domain... Promote..

i just moved DHCP and FSMO roles around the other DCs before i demoted the one that had those roles..

  • 3 weeks later...
Posted

Always do a clean install of a DC on the correct hardware config.

 

Then add it to the domain, add AD/DNS and then follow these steps for moving your FSMO roles from your primary DC to the new DC. May I ask why you have 3 DC's? I onlt have two so that data never gets correupt and for 0 downtime and DHCP load share/fallover

 

https://www.petri.com/transferring_fsmo_roles

 

https://www.interworks.com/en-gb/blog/ijahanshahi/2015/11/30/migrating-dhcp-part-2-migrate-dhcp-new-windows-server

Posted

Just some helpful bits...

 

Unless you absolutely know what connects to your DC's and where from then the following is worth a look before you dive in. (sometimes it hard to sell break fix on something as important as your DC's and AD)

 

https://blogs.technet.microsoft.com/askpfeplat/2013/12/15/domain-and-dc-migrations-how-to-monitor-ldap-kerberos-and-ntlm-traffic-to-your-domain-controllers/

 

This maybe helpful around the question of what is supported for in place OS upgrades https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/deploy/upgrade-domain-controllers-to-windows-server-2016

I know this is a topic that people don't like but it is supported and is a perfectly valid approach if you take out opinion and experience (just recently I talked about this very approach with a premier field engineer)

 

the following is a screen from a pester test that I wrote for testing the state of DC's and AD and is what it returned in my dev environment

 

PesterDCScreenShot.PNG

 

The code for the pester is here:-

 

[CmdletBinding()]
Param(
   [string]$ADFile = (Read-Host "Please enter the path to your ADreport-*.xml file"),
   [string]$ADGoldFile = $(Get-ChildItem ('ADGoldConfig-*.xml') | Select-Object name -last 1).name
)

   $ADSnapshot = Import-Clixml $ADFile

   Write-Verbose "Loading the AD Gold Config from: $ADGoldFile"
   $ADGoldConfig = Import-Clixml $ADGoldFile

#Begin testing
Describe 'Active Directory Forest Operational Readiness checks' -Tags 'Forest' {

   Context 'Verifying Forest Configuration'{
       it "Forest FQDN $($ADGoldConfig.ForestInformation.RootDomain)" {
           $ADGoldConfig.ForestInformation.RootDomain | 
           Should be $ADSnapshot.ForestInformation.RootDomain
       }
       it "ForestMode $($ADGoldConfig.ForestInformation.ForestMode.ToString())"{
           $ADGoldConfig.ForestInformation.ForestMode.ToString() | 
           Should be $ADSnapshot.ForestInformation.ForestMode.ToString()
       }
       it "Schema Version $($ADGoldConfig.SchemaVersion.objectVersion)"{
           $ADGoldConfig.SchemaVersion.objectVersion |
           Should be $ADSnapshot.SchemaVersion.objectVersion
       }
       it "Server $($ADGoldConfig.ForestInformation.DomainNamingMaster) is Domain Naming Master" {
           $ADGoldConfig.ForestInformation.DomainNamingMaster | 
           Should be $ADSnapshot.ForestInformation.DomainNamingMaster
       }
       it "Server $($ADGoldConfig.ForestInformation.SchemaMaster) is Schema Master" {
           $ADGoldConfig.ForestInformation.SchemaMaster | 
           Should be $ADSnapshot.ForestInformation.SchemaMaster
       }
   }

   Context 'Verifying GlobalCatalogs'{
       $ADGoldConfig.ForestInformation.GlobalCatalogs | 
       ForEach-Object{
           it "Server $($_) is a GlobalCatalog"{
               $ADSnapshot.ForestInformation.GlobalCatalogs.Contains($_) | 
               Should be $true
           }
       }
   }
}

Describe 'Active Directory Domain Operational Readiness checks' -Tags 'Domain' {
   Context 'Verifying Domain Configuration'{
       it "Total Domain Controllers $($ADGoldConfig.DomainControllers.Count)" {
           $ADGoldConfig.DomainControllers.Count | 
           Should be $ADSnapshot.DomainControllers.Count
       }

       $ADGoldConfig.DomainControllers.Name | 
       ForEach-Object{
           it "DomainController $($_) exists"{
               $ADSnapshot.DomainControllers.Name.Contains($_) | 
               Should be $true
           }
       }
       it "DNSRoot $($ADGoldConfig.DomainInformation.DNSRoot)"{
           $ADGoldConfig.DomainInformation.DNSRoot | 
           Should be $ADSnapshot.DomainInformation.DNSRoot
       }
       it "NetBIOSName $($ADGoldConfig.DomainInformation.NetBIOSName)"{
           $ADGoldConfig.DomainInformation.NetBIOSName | 
           Should be $ADSnapshot.DomainInformation.NetBIOSName
       }
       it "DomainMode $($ADGoldConfig.DomainInformation.DomainMode.ToString())"{
           $ADGoldConfig.DomainInformation.DomainMode.ToString() | 
           Should be $ADSnapshot.DomainInformation.DomainMode.ToString()
       }
       it "DistinguishedName $($ADGoldConfig.DomainInformation.DistinguishedName)"{
           $ADGoldConfig.DomainInformation.DistinguishedName | 
           Should be $ADSnapshot.DomainInformation.DistinguishedName
       }
       it "Server $($ADGoldConfig.DomainInformation.RIDMaster) is RIDMaster"{
           $ADGoldConfig.DomainInformation.RIDMaster | 
           Should be $ADSnapshot.DomainInformation.RIDMaster
       }
       it "Server $($ADGoldConfig.DomainInformation.PDCEmulator) is PDCEmulator"{
           $ADGoldConfig.DomainInformation.PDCEmulator | 
           Should be $ADSnapshot.DomainInformation.PDCEmulator
       }
       it "Server $($ADGoldConfig.DomainInformation.InfrastructureMaster) is InfrastructureMaster"{
           $ADGoldConfig.DomainInformation.InfrastructureMaster | 
           Should be $ADSnapshot.DomainInformation.InfrastructureMaster
       }
   }
}

Describe 'Active Directory Default Password Policy Operational Readiness checks' -Tags 'Password' {
   Context 'Verifying Default Password Policy'{
       it 'ComplexityEnabled'{
           $ADGoldConfig.DefaultPassWordPoLicy.ComplexityEnabled | 
           Should be $ADSnapshot.DefaultPassWordPoLicy.ComplexityEnabled
       }
       it 'Password History count'{
           $ADGoldConfig.DefaultPassWordPoLicy.PasswordHistoryCount | 
           Should be $ADSnapshot.DefaultPassWordPoLicy.PasswordHistoryCount
       }
       it "Lockout Threshold equals $($ADGoldConfig.DefaultPassWordPoLicy.LockoutThreshold)"{
           $ADGoldConfig.DefaultPassWordPoLicy.LockoutThreshold | 
           Should be $ADSnapshot.DefaultPassWordPoLicy.LockoutThreshold
       }
       it "Lockout duration equals $($ADGoldConfig.DefaultPassWordPoLicy.LockoutDuration)"{
           $ADGoldConfig.DefaultPassWordPoLicy.LockoutDuration | 
           Should be $ADSnapshot.DefaultPassWordPoLicy.LockoutDuration.ToString()
       }
       it "Lockout observation window equals $($ADGoldConfig.DefaultPassWordPoLicy.LockoutObservationWindow)"{
           $ADGoldConfig.DefaultPassWordPoLicy.LockoutObservationWindow | 
           Should be $ADSnapshot.DefaultPassWordPoLicy.LockoutObservationWindow.ToString()
       }
       it "Min password age equals $($ADGoldConfig.DefaultPassWordPoLicy.MinPasswordAge)"{
           $ADGoldConfig.DefaultPassWordPoLicy.MinPasswordAge | 
           Should be $ADSnapshot.DefaultPassWordPoLicy.MinPasswordAge.ToString()
       }
       it "Max password age equals $($ADGoldConfig.DefaultPassWordPoLicy.MaxPasswordAge)"{
           $ADGoldConfig.DefaultPassWordPoLicy.MaxPasswordAge | 
           Should be $ADSnapshot.DefaultPassWordPoLicy.MaxPasswordAge.ToString()
       }
   }
}

Describe 'Active Directory Sites,subnets & sublinks Operational Readiness' -Tags 'Sites','Subnets','Sitelinks' {
   Context 'Verifying Active Directory Sites'{
       $ADGoldConfig.Sites.Name | 
       ForEach-Object{
           it "Site $($_)" {
               $ADSnapshot.Sites.Name.Contains($_) | 
               Should be $true
           } 
       }
   }

   Context 'Verifying Active Directory Sitelinks'{
       $lookupSiteLinks = $ADSnapshot.Sitelinks | Group-Object -AsHashTable -Property Name 
       $ADGoldConfig.Sitelinks | 
       ForEach-Object{
           it "Sitelink $($_.Name)" {
               $_.Name | 
               Should be $($lookupSiteLinks.$($_.Name).Name)
           } 
           it "Sitelink $($_.Name) costs $($_.Cost)" {
               $_.Cost | 
               Should be $lookupSiteLinks.$($_.Name).Cost
           }
           it "Sitelink $($_.Name) replication interval $($_.ReplicationFrequencyInMinutes)" {
               $_.ReplicationFrequencyInMinutes | 
               Should be $lookupSiteLinks.$($_.Name).ReplicationFrequencyInMinutes
           }
       }
   }

   Context 'Verifying Active Directory Subnets'{
       $lookupSubnets = $ADSnapshot.SubNets | Group-Object -AsHashTable -Property Name 
       $ADGoldConfig.Subnets | 
       ForEach-Object{
           it "Subnet $($_.Name)" {
               $_.Name | 
               Should be $lookupSubnets.$($_.Name).Name
           }
           it "Site $($_.Site)" {
               $_.Site | 
               Should be $lookupSubnets.$($_.Name).Site
           }
       } 
   }
}

Describe 'Active Directory health checks' -Tags 'ADHC' {
   
   Context 'Checking the output of NLTest /Query'{
       $NLTest = NLTest.exe /Query
       
       it 'NLTest.exe /Query Result' {
           ($NLTest | out-string).contains('Success') | Should be $true   
       }     
   }
}    

Describe 'Checking the output of DCDiag for issues on all DCs'{
       $ADGoldConfig.DomainControllers.Name | ForEach-Object {
           $DCToTest = $_
           $DCDiag = dcdiag.exe /a /s:$DCToTest
               
               Context "DCDiag tests on $DCToTest"{
                   it 'DCDiag.exe -a Result' {
                       ($DCDiag | out-string).contains('failed') | Should be $false   
                   }
               }
           $DCToTest = $null         
           }
       }
   
Describe 'Checking the output of RepAdmin /showrepl for replication issues'{
       (Repadmin.exe /showrepl * /csv | convertfrom-csv) | Sort-Object 'Source DSA' | Where-Object{$_.'Number of Failures' -ge 0} |
       ForEach-Object{ 
           it "Replication from $($_.'Source DSA') to $($_.'Destination DSA') has $($_.'Number of Failures') failures" {
               $_.'Number of Failures' | Should Not BeGreaterThan 0  
           } 
       }     
   }     
   
Describe 'Pinging each Domain Controller'{
       $ADGoldConfig.DomainControllers.Name | Sort-Object |
       ForEach-Object{
           it "Ping result for Domain Controller $($_)"{
               Test-Connection $_ -Quiet | Should be $true
           }
       }
   }

Describe 'Testing local Active Directory TCP ports respond' -Tag 'TCP Tests' {
       # AD Ports: https://technet.microsoft.com/en-us/library/dd772723(v=ws.10).aspx
       $Ports = @(53,88,135,139,389,445,464,636,3268,3269,9389)

       $ADGoldConfig.DomainControllers.Name | ForEach-Object{
           $DCToTest = $_
               Context "Testing TCP Ports on $DCToTest"{
                   $Ports | foreach-object{
                       it "Port test for TCP $_" {
                           (Test-netconnection -ComputerName $DCToTest -Port $_).TcpTestSucceeded | Should be $true
                       }
                   }
               }
           $DCToTest = "$null"
           }
   
}

Describe 'Checking local Active Directory Windows services are running'-Tag 'Service Tests'{
       $Services = @('ADWS','CertPropSvc','CryptSvc','Dfs','DFSR','DNS','Dnscache','eventlog','gpsvc','kdc',`
                     'LanmanServer','LanmanWorkstation','Netlogon','NTDS','RpcEptMapper','RpcSs','SamSs',`
                     'W32Time')
       
       $ADGoldConfig.DomainControllers.Name | ForEach-Object{
           $DCToTest = $_
       
               Context "Testing TCP Ports on $DCToTest"{
                   $Services | foreach-object{
                       $Svc = get-service -ComputerName $DCToTest $_
                           it "Service: $($Svc.DisplayName)" {
                               $Svc.status | Should be 'Running'
                           }
                   }
               }
           $DCToTest = $null
       }
}

 

The pester relies on a script that I found on GIT and is as follows:-

 

[cmdletbinding()]
Param($ExportToXML = $True)

Import-Module ActiveDirectory

#HashTable to save ADReport
$ADSnapshot = @{}

$ADSnapshot.RootDSE = $(Get-ADRootDSE)
$ADSnapshot.ForestInformation = $(Get-ADForest)
$ADSnapshot.DomainInformation = $(Get-ADDomain)
$ADSnapshot.SchemaVersion = (Get-ADObject (Get-ADRootDSE).schemaNamingContext -Property objectVersion)
$ADSnapshot.DomainControllers = $(Get-ADDomainController -Filter *)
$ADSnapshot.DomainTrusts = (Get-ADTrust -Filter *)
$ADSnapshot.DefaultPassWordPoLicy = $(Get-ADDefaultDomainPasswordPolicy)
$ADSnapshot.AuthenticationPolicies = $(Get-ADAuthenticationPolicy -LDAPFilter '(name=AuthenticationPolicy*)')
$ADSnapshot.AuthenticationPolicySilos = $(Get-ADAuthenticationPolicySilo -Filter 'Name -like "*AuthenticationPolicySilo*"')
$ADSnapshot.CentralAccessPolicies = $(Get-ADCentralAccessPolicy -Filter *)
$ADSnapshot.CentralAccessRules = $(Get-ADCentralAccessRule -Filter *)
$ADSnapshot.ClaimTransformPolicies = $(Get-ADClaimTransformPolicy -Filter *)
$ADSnapshot.ClaimTypes = $(Get-ADClaimType -Filter *)
$ADSnapshot.DomainAdministrators =$( Get-ADGroup -Identity $('{0}-512' -f (Get-ADDomain).domainSID) | Get-ADGroupMember -Recursive)
$ADSnapshot.OrganizationalUnits = $(Get-ADOrganizationalUnit -Filter *)
$ADSnapshot.OptionalFeatures =  $(Get-ADOptionalFeature -Filter *)
$ADSnapshot.Sites = $(Get-ADReplicationSite -Filter *)
$ADSnapshot.Subnets = $(Get-ADReplicationSubnet -Filter *)
$ADSnapshot.SiteLinks = $(Get-ADReplicationSiteLink -Filter *)

#Export to XML
If ($ExportToXML -eq $True) {
   $ADSnapshot | Export-Clixml "E:\Local\AD_Pester\ADReport-$(get-date -format yyyy-MM-dd).xml" -Encoding UTF8
   Return $True
}Else{
   Return $ADSnapshot
}

 

To run the tests and I used the following invoke-OperationValidation available from here:- https://github.com/PowerShell/Operation-Validation-Framework (Invoke-OperationValidation -testFilePath E:\Local\AD_Pester\ -IncludePesterOutput | export-csv .\ADTestResultsPreSchemaUpd.csv)

 

Using the ADreport that the second script creates the tests are run against you DC/ AD configuration, the idea behind this is that I was able to test a lot of individual settings and check a lot of log files and the like after each step of the upgrade process. If you do not update the ADReport you can the see what has changed after each step as these will be marked as failed tests.

Posted

oh and this is useful:-

 

A way of just holding back a new DC from becoming a fully functional DC in a domain, gives you some options around testing your apps/ clients and the like before letting it go wild in your Domains :-

$DataValue = "Dc","Pdc","Gc","GcAtSite","GenericGc","GenericGcAtSite","GcIpAddress","Kdc","KdcAtSite","Ldap","LdapAtSite","LdapIpAddress","Rfc1510Kdc","Rfc1510KdcAtSite","Rfc1510UdpKdc","Rfc1510Kpwd","Rfc1510UdpKpwd"

New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters" -Name DnsAvoidRegisterRecords -Value $DataValue -PropertyType Multistring

Restart-Service -Name Netlogon

 

Hope some of this will help.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now



×
×
  • Create New...