Windows Server for beginners. Part 19: Replacing and retiring domain controllers
All parts of the series “Windows Server for beginners”A domain controller lives as long as the Windows Server version on it, five to ten years, and during that time it is moved at least once to new hardware, a new hypervisor or a new OS version. Active Directory has a handy property for this: a controller is not a unique entity but one of the copies of the directory, and it can be replaced by adding a new copy and removing the old one. That is how hardware is changed, how a new OS version is adopted, and how you get rid of a controller that died, went back in time or missed half a year of replication (part 18). This part is about what has to move along with the controller, and about what is left of it in the directory if it is removed the wrong way.
The commands run from the workstation ws1 in PowerShell 7 (part 15); whatever has to run on the controller itself goes through Invoke-Command (part 14). The domain is the same as in part 18: corp.example.com, dc1 (192.168.1.10) and dc2 (192.168.1.11) in the site Office, dc3 (192.168.2.10) and dc4 (192.168.2.11) in the site Branch, all FSMO roles on dc1. dc3 is the only controller on Windows Server 2019, and it is the one we replace: dc5 (192.168.2.12) on Windows Server 2025 goes up next to it, and dc3 is demoted and leaves.
A new version means a new controller
Moving controllers from Windows Server 2019 to 2025 is done not by upgrading but by replacement: a controller on 2025 goes up next to the old one, everything moves to it, and the old one is demoted and switched off. This is called a swing migration, and it is what Microsoft recommends: a clean install of a new server, promotion to a controller and demotion of the old one are preferable to an in-place OS upgrade. An in-place upgrade keeps everything that has piled up over the years: drivers, agents, registry keys of products long gone; an error in the middle of the upgrade leaves the controller in a state with no way forward or back; and during the upgrade the controller is unavailable, while with a replacement it never is.
Windows Server 2025 adds an argument that didn't exist before. A controller installed afresh, as a new one or promoted with replication over the network, gets a database in the 32 KB page format (until the forest switches to it, it runs in 8 KB compatibility mode). A controller upgraded in place keeps its 8 KB database, and the forest can move to 32 KB pages only when every controller has such a database; more on that below, under functional levels.
In-place upgrade of controllers is supported (from 2019 and 2022 to 2025), and it has one honest use: the only controller of a small domain on a physical server with nowhere to put a second one next to it. But then the schema and the domain are prepared by hand, before the first controller is upgraded.
The first controller of a new version in a forest requires extending the schema and preparing the domain. The adprep tool from the installation media does this in two steps: adprep /forestprep, once per forest, with Schema Admins, Enterprise Admins and Domain Admins rights and a connection to the Schema Master; adprep /domainprep, in each domain, with Domain Admins rights and a connection to the Infrastructure Master. When a new server is promoted, Install-ADDSDomainController does both preparations itself if the account has enough rights, or with separate credentials from the -ADPrepCredential parameter. The schema version is the objectVersion attribute of the schema partition:
PS C:\> (Get-ADObject (Get-ADRootDSE).schemaNamingContext -Properties objectVersion).objectVersion
91
87 is Windows Server 2016, 88 is 2019 and 2022 (2022 has no schema changes of its own), 91 is 2025: preparation for 2025 applies the files sch89.ldf, sch90.ldf and sch91.ldf, and each one ends by writing its number into objectVersion. 91 in our forest means the schema has already been extended for 2025, when the first controller of that version was added. Who did it and when shows in the replication metadata of the attribute:
PS C:\> repadmin /showobjmeta dc1 "CN=Schema,CN=Configuration,DC=corp,DC=example,DC=com" | Select-String objectVersion
24616288 Office\DC2 28910296 2025-01-12 12:30:31 45 objectVersion
The change originated on dc2: only the Schema Master writes the schema, in January 2025 the role was on dc2, and it was moved to dc1 later (moving roles and the traces it leaves in the directory are in part 17). Attribute version 45 is how many times it has been written over the forest's life: once for each schNN.ldf file applied since the forest was created. A schema change is irreversible (part 5), so it is done in a maintenance window: not because it breaks anything, but because it can't be undone.
When the schema and the domain are already prepared, promotion extends nothing: the wizard only checks the preparation marks. This is how it looks in the promotion log of dc4, %SystemRoot%\debug\dcpromoui.log:
PS C:\> Invoke-Command dc4 { Select-String -Path C:\Windows\debug\dcpromoui.log -Pattern 'ForestPrepIsDone|DomainPrepIsDone' | Select-Object -Last 2 | ForEach-Object Line }
dcpromoui 10D0.EDC 1404 09:29:56.362 testForestPrepIsDone <- "yes"
dcpromoui 10D0.EDC 1415 09:29:56.364 testDomainPrepIsDone <- "yes"
So dc5 doesn't need Schema Admins rights: an additional controller in an existing domain is installed by a member of Domain Admins.
What to check before retiring
We are retiring dc3. First the overall picture:
PS C:\> Get-ADDomainController -Filter * | Sort-Object Name | Format-Table Name, IPv4Address, Site, OperatingSystem, IsGlobalCatalog
Name IPv4Address Site OperatingSystem IsGlobalCatalog
---- ----------- ---- --------------- ---------------
DC1 192.168.1.10 Office Windows Server 2025 Standard True
DC2 192.168.1.11 Office Windows Server 2025 Standard True
DC3 192.168.2.10 Branch Windows Server 2019 Standard True
DC4 192.168.2.11 Branch Windows Server 2025 Standard True
And the list of what the departing controller has and where it moves.
FSMO roles. netdom query fsmo: dc3 holds no roles, all five are on dc1. If they are on the departing controller, they are moved in advance, by transfer, not by seizure (part 17), and after moving the PDC, the time source follows (part 12). netdom doesn't show the Infrastructure role twins in the DNS partitions; their owners are checked separately:
PS C:\> 'DomainDnsZones', 'ForestDnsZones' | ForEach-Object { Get-ADObject "CN=Infrastructure,DC=$_,DC=corp,DC=example,DC=com" -Properties fSMORoleOwner } | Format-List DistinguishedName, fSMORoleOwner
DistinguishedName : CN=Infrastructure,DC=DomainDnsZones,DC=corp,DC=example,DC=com
fSMORoleOwner : CN=NTDS Settings,CN=DC2,CN=Servers,CN=Office,CN=Sites,CN=Configuration,DC=corp,DC=example,DC=com
DistinguishedName : CN=Infrastructure,DC=ForestDnsZones,DC=corp,DC=example,DC=com
fSMORoleOwner : CN=NTDS Settings,CN=DC2,CN=Servers,CN=Office,CN=Sites,CN=Configuration,DC=corp,DC=example,DC=com
Ours are on dc2. If we were retiring dc2, they would have to be moved before demotion: otherwise the attribute keeps a reference to a deleted object, and the next demotion or adprep /rodcprep trips over it (part 17).
Global catalog. At least one GC must remain in the site, otherwise the site's clients go for one across the link between sites. dc4 is a GC, and dc5 becomes a GC by default.
DNS. Four things. The zones in the directory move by themselves, but forwarders are a server setting: during promotion the wizard puts into them the DNS server the new server was using (after promoting dc4, ours ended up with dc3 there), and they are replaced with the real ones. Scavenging of stale records, if it is enabled on the departing server, is enabled on another one. The DNS clients of neighbouring controllers point to their site partner first (part 10), and on dc4 the first one is exactly dc3:
PS C:\> Get-DnsClientServerAddress -CimSession dc4 -AddressFamily IPv4 | Where-Object ServerAddresses | Format-Table InterfaceAlias, ServerAddresses
InterfaceAlias ServerAddresses
-------------- ---------------
Ethernet {192.168.2.10, 127.0.0.1}
And everyone who asks dc3 directly: DHCP option 6, static server settings, /etc/resolv.conf on Linux, routers, printers. Option 6 in the site's scope is visible right away:
PS C:\> Invoke-Command dc4 { Get-DhcpServerv4OptionValue -ScopeId 192.168.2.0 -OptionId 6 } | Format-Table OptionId, Name, Value
OptionId Name Value
-------- ---- -----
6 DNS Servers {192.168.2.10, 192.168.2.11}
The rest, below.
DHCP. The site's scope is served by dc3 and dc4 as a failover pair (part 8):
PS C:\> Invoke-Command dc4 { Get-DhcpServerv4Failover } | Format-List Name, PartnerServer, Mode, LoadBalancePercent, ScopeId, State
Name : dc3-dc4
PartnerServer : dc3.corp.example.com
Mode : LoadBalance
LoadBalancePercent : 50
ScopeId : {192.168.2.0}
State : Normal
A failover relationship doesn't move to another server; it is taken apart and created again with dc5. The account for DNS updates on behalf of clients is a server setting and is set again on dc5.
Time. If the PDC is leaving, it is the domain's time source. If devices outside the domain pointed at the departing controller (option 42, NTP on switches and cameras), they are repointed to another one.
Everything else that shouldn't be on a controller but is. NPS, Entra Connect, backup agents, certificates issued to the name dc3, and applications with dc3.corp.example.com written in as their LDAP server: ticketing systems, VPN gateways, Linux machines with sssd, scanners with "LDAP authentication". They won't find another controller through the DC Locator, because they never used it.
The last item is the most laborious, and there is only one way to close it: look at who talks to dc3. First, the Security log: Kerberos ticket events 4768 (TGT) and 4769 (service ticket), by client address. The client address is in different fields: Properties[9] in 4768 and Properties[6] in 4769, and it is written as an IPv4 address mapped into IPv6 (::ffff:192.168.2.42):
PS C:\> Invoke-Command dc3 {
$ids = 4768, 4769
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=$ids; StartTime=(Get-Date).AddDays(-7)} |
ForEach-Object { $ip = if ($_.Id -eq 4768) { $_.Properties[9].Value } else { $_.Properties[6].Value }; $ip -replace '^::ffff:' } |
Group-Object -NoElement | Sort-Object Count -Descending | Select-Object Count, Name -First 5
} | Format-Table Count, Name
Count Name
----- ----
962 192.168.2.42
958 192.168.2.43
944 192.168.2.41
810 192.168.2.21
738 192.168.2.23
The Id array sits in a variable for a reason: in a remote Windows PowerShell 5.1 session, a literal array in -FilterHashtable with output going straight into the pipeline answers "No events were found…". The Security log on a controller is overwritten quickly (ours keeps a little over a week), so collect the addresses several times on different days.
These are the ones who authenticate on dc3. For domain members that is normal: they picked it through the Locator, and the Locator will move them to another controller. More dangerous are those who reach dc3 over LDAP without Kerberos and those who ask it for DNS. For DNS the server has a packet debug log: according to the documentation, to record incoming queries you enable one parameter from each pair: direction, transport, content and packet type:
PS C:\> Set-DnsServerDiagnostics -ComputerName dc3 -ReceivePackets $true -UdpPackets $true -TcpPackets $true -Queries $true -QuestionTransactions $true -EnableLoggingToFile $true
# a day later
PS C:\> Set-DnsServerDiagnostics -ComputerName dc3 -ReceivePackets $false -UdpPackets $false -TcpPackets $false -Queries $false -QuestionTransactions $false
PS C:\> Invoke-Command dc3 {
Select-String -Path C:\Windows\System32\dns\dns.log -Pattern ' Rcv (\S+)' | ForEach-Object { $_.Matches[0].Groups[1].Value } |
Group-Object -NoElement | Sort-Object Count -Descending | Select-Object Count, Name -First 20
}
By default the log goes to %SystemRoot%\System32\dns\dns.log. It is switched off with the same parameters, not with -All $false: according to the documentation, -All covers every setting except the path, file size, event level, address filter and rollover, which includes EnableLoggingToFile, on by default on the server. Every address in this list that isn't a DHCP workstation is a device or server with dc3 in its DNS settings. The same goes for LDAP: event 2889 from part 18 lists clients with insecure binds (with diagnostics enabled), and pktmon filtered on ports 389 and 636 for an hour shows the rest.
The new controller
dc5 on Windows Server 2025, prepared as in part 1 and joined to the domain, is promoted as in part 10, into the site Branch, with dc4 as the replication source:
PS C:\> Install-ADDSDomainController -DomainName corp.example.com -Credential (Get-Credential CORP\alice.admin) -SiteName Branch -ReplicationSourceDC dc4.corp.example.com -InstallDns -SafeModeAdministratorPassword (Read-Host -AsSecureString 'DSRM password') -Force
The command runs on dc5 itself: at the console or through Invoke-Command with an explicit -Credential, otherwise the promotion runs into the second hop (part 14), since it has to talk to other controllers. If this were the first 2025 controller in the forest, the account would need membership in Schema Admins and Enterprise Admins, or the -ADPrepCredential parameter with such an account; the Schema Admins membership is removed afterwards.
After the reboot comes the check from part 18: dcdiag /s:dc5 (from a workstation the event log tests are skipped with /skip:), repadmin /showrepl dc5, the SYSVOL state (DfsrReplicatedFolderInfo, State = 4) and DFS Replication event 4604, SYSVOL initialized.
From here on, everything that moves moves to dc5 while dc3 is alive. The DNS server and the DNS clients:
PS C:\> Set-DnsServerForwarder -ComputerName dc5 -IPAddress (Get-DnsServerForwarder -ComputerName dc4).IPAddress
PS C:\> Set-DnsClientServerAddress -CimSession dc4 -InterfaceAlias Ethernet -ServerAddresses 192.168.2.12, 127.0.0.1
PS C:\> Set-DnsClientServerAddress -CimSession dc5 -InterfaceAlias Ethernet -ServerAddresses 192.168.2.11, 127.0.0.1
If the PDC were leaving, the roles and the time source would move here too, roles first, then time: the new PDC takes time from outside, and the old one returns to the domain hierarchy (part 12):
PS C:\> Move-ADDirectoryServerOperationMasterRole -Identity dc5 -OperationMasterRole SchemaMaster, DomainNamingMaster, PDCEmulator, RIDMaster, InfrastructureMaster -Confirm:$false
PS C:\> Invoke-Command dc5 { w32tm /config /manualpeerlist:"0.pool.ntp.org,0x8 1.pool.ntp.org,0x8" /syncfromflags:manual /reliable:yes /update }
PS C:\> Invoke-Command dc1 { w32tm /config /syncfromflags:domhier /reliable:no /update }
DHCP is a separate sequence, because failover isn't moved but recreated. The role is installed and authorized on dc5 as in part 8, then the scope leaves the old relationship and joins the new one:
PS C:\> Install-WindowsFeature -ComputerName dc5 -Name DHCP -IncludeManagementTools
PS C:\> Invoke-Command dc5 { Add-DhcpServerSecurityGroup; Restart-Service DHCPServer }
PS C:\> Add-DhcpServerInDC -DnsName dc5.corp.example.com -IPAddress 192.168.2.12
PS C:\> Set-DhcpServerDnsCredential -ComputerName dc5 -Credential (Get-Credential CORP\svc-dhcp-dns)
PS C:\> Remove-DhcpServerv4FailoverScope -ComputerName dc4 -Name dc3-dc4 -ScopeId 192.168.2.0 -Force
PS C:\> Remove-DhcpServerv4Failover -ComputerName dc4 -Name dc3-dc4 -Force
PS C:\> Remove-DhcpServerInDC -DnsName dc3.corp.example.com -IPAddress 192.168.2.10
PS C:\> Add-DhcpServerv4Failover -ComputerName dc4 -PartnerServer dc5.corp.example.com -Name dc4-dc5 -ScopeId 192.168.2.0 -LoadBalancePercent 50 -SharedSecret (Read-Host 'Shared secret') -Force
PS C:\> Set-DhcpServerv4OptionValue -ComputerName dc4 -ScopeId 192.168.2.0 -DnsServer 192.168.2.11, 192.168.2.12
PS C:\> Invoke-DhcpServerv4FailoverReplication -ComputerName dc4 -Name dc4-dc5 -Force
Remove-DhcpServerv4FailoverScope run on dc4, according to the documentation, deletes the scope from the partner, dc3, and keeps it with its leases on dc4; the empty relationship is deleted next. Once its authorization is removed, dc3 no longer hands out addresses. Add-DhcpServerv4Failover copies the scope with leases, reservations and options to dc5, and after that option 6 is changed on one server and replicated to the partner. The DhcpServer module works in PowerShell 7 through the compatibility layer (part 15), which doesn't get in the way of -ComputerName. The DHCPv6 scope (part 9), if dc3 had one, is created on dc5 again. And after all that, the addresses from the "who asks dc3" list, each by hand.
A week between this moment and the demotion is the right pause: whatever didn't make it onto the list will surface during it.
Demotion
Demotion is the reverse of promotion, with the same Test- and Uninstall- pair. Uninstall-ADDSDomainController has two parameter sets, graceful demotion and forced:
PS C:\> Invoke-Command dc3 { Get-Command Uninstall-ADDSDomainController -Syntax }
Uninstall-ADDSDomainController [-SkipPreChecks] [-LocalAdministratorPassword <securestring>] [-Credential <pscredential>] [-DemoteOperationMasterRole] [-DnsDelegationRemovalCredential <pscredential>] [-IgnoreLastDCInDomainMismatch] [-IgnoreLastDnsServerForZone] [-LastDomainControllerInDomain] [-NoRebootOnCompletion] [-RemoveApplicationPartitions] [-RemoveDnsDelegation] [-RetainDCMetadata] [-Force] [-WhatIf] [-Confirm] [<CommonParameters>]
Uninstall-ADDSDomainController -ForceRemoval [-SkipPreChecks] [-LocalAdministratorPassword <securestring>] [-Credential <pscredential>] [-DemoteOperationMasterRole] [-NoRebootOnCompletion] [-Force] [-WhatIf] [-Confirm] [<CommonParameters>]
Graceful demotion of dc3, again through Invoke-Command with explicit credentials, since demotion, like promotion, has to talk to other controllers:
PS C:\> $cred = Get-Credential CORP\alice.admin
PS C:\> $pass = Read-Host -AsSecureString 'New local Administrator password'
PS C:\> Invoke-Command dc3 { Test-ADDSDomainControllerUninstallation -Credential $using:cred -LocalAdministratorPassword $using:pass }
PS C:\> Invoke-Command dc3 { Uninstall-ADDSDomainController -Credential $using:cred -LocalAdministratorPassword $using:pass -Force }
Test-ADDSDomainControllerUninstallation runs the same prerequisite checks as the demotion without changing anything. -WhatIf on Uninstall-ADDSDomainController is something else: it only shows the explicit and implicit parameter values the demotion would run with. -LocalAdministratorPassword is the password of the built-in local Administrator, who reappears on the server together with the local SAM database; without the parameter the cmdlet asks for it. The rest, per the documentation:
-DemoteOperationMasterRole: continue a forced demotion even if FSMO roles are found on the controller; they are then seized on another controller. In a graceful demotion we move the roles in advance ourselves, so that we choose who gets them;-IgnoreLastDnsServerForZone: demote even if the controller is the last DNS server for one of the zones in the directory;-LastDomainControllerInDomainand-RemoveApplicationPartitions: demoting the last controller, that is, removing the domain (Enterprise Admins rights needed), with removal of the application partitions;-RemoveDnsDelegation: remove the delegation to this DNS server in the parent zone;-RetainDCMetadata: keep the controller's metadata in the directory after demotion.
What demotion does: the controller hands its unreplicated changes to a partner, its NTDS Settings object is deleted, its SYSVOL replication membership is removed, Netlogon removes its DNS records, and the DC3$ account becomes an ordinary server account: userAccountControl 4096 instead of 532480, primary group Domain Computers instead of Domain Controllers. The details go to %SystemRoot%\debug\dcpromo.log and dcpromoui.log; at the end the server reboots by itself. After the reboot the roles are removed:
PS C:\> Uninstall-WindowsFeature -ComputerName dc3 -Name AD-Domain-Services, DNS, DHCP -IncludeManagementTools
The AD DS role can't be removed before demotion and the reboot: the cmdlet refuses. Removing it with DISM isn't supported: the server won't boot normally afterwards. From there the server is either used as a member server or removed from the domain with Remove-Computer (in PowerShell 7 it comes through the compatibility layer, so it's simpler to run it on the server itself) and switched off.
Forced demotion is for when the controller can't reach any partner (network, broken replication, a USN rollback or a missed tombstone lifetime from part 18) and graceful demotion fails:
PS C:\> Uninstall-ADDSDomainController -ForceRemoval -DemoteOperationMasterRole -LocalAdministratorPassword (Read-Host -AsSecureString 'New local Administrator password') -Force
It runs at the console of the controller itself: it has no connection to other controllers anyway. -ForceRemoval does everything locally and tells no one: for the other controllers of the forest this controller still exists, and all its unreplicated changes, new accounts and changed passwords, are lost forever. Microsoft warns that such orphaned metadata is the root cause of a significant share of support cases for AD DS, Exchange and SQL Server. A controller that died and is simply gone stays in the directory the same way. In both cases metadata cleanup is needed right away.
Metadata cleanup
What a controller has in the forest can be seen on the live dc3. Three objects make it a controller: the computer account, the server object in the site and the NTDS Settings under it:
PS C:\> Get-ADDomainController dc3 | Format-List ComputerObjectDN, ServerObjectDN, NTDSSettingsObjectDN
ComputerObjectDN : CN=DC3,OU=Domain Controllers,DC=corp,DC=example,DC=com
ServerObjectDN : CN=DC3,CN=Servers,CN=Branch,CN=Sites,CN=Configuration,DC=corp,DC=example,DC=com
NTDSSettingsObjectDN : CN=NTDS Settings,CN=DC3,CN=Servers,CN=Branch,CN=Sites,CN=Configuration,DC=corp,DC=example,DC=com
The connection objects through which partners replicate from dc3 have its NTDS Settings in fromServer:
PS C:\> $ntds = (Get-ADDomainController dc3).NTDSSettingsObjectDN
PS C:\> Get-ADObject -SearchBase (Get-ADRootDSE).configurationNamingContext -LDAPFilter "(&(objectClass=nTDSConnection)(fromServer=$ntds))" | Select-Object DistinguishedName
DistinguishedName
-----------------
CN=DC3,CN=NTDS Settings,CN=DC1,CN=Servers,CN=Office,CN=Sites,CN=Configuration,DC=corp,DC=example,DC=com
CN=DC3,CN=NTDS Settings,CN=DC2,CN=Servers,CN=Office,CN=Sites,CN=Configuration,DC=corp,DC=example,DC=com
CN=8b9c0d1e-2f3a-4b5c-9d6e-7f8091a2b3c4,CN=NTDS Settings,CN=DC4,CN=Servers,CN=Branch,CN=Sites,CN=Configuration,DC=corp,DC=example,DC=com
The connection with a GUID name was created by the KCC (part 10), and the KCC will remove it once dc3 is gone. The two connections named DC3 were created by hand at some point, and the KCC neither modifies nor deletes manually created connections: they live until an administrator deletes them. Everything named DC3 in the configuration and domain partitions:
PS C:\> Get-ADObject -SearchBase (Get-ADRootDSE).configurationNamingContext -LDAPFilter '(cn=DC3)' | Format-Table ObjectClass, DistinguishedName
ObjectClass DistinguishedName
----------- -----------------
server CN=DC3,CN=Servers,CN=Branch,CN=Sites,CN=Configuration,DC=corp,DC=example,DC=com
nTDSConnection CN=DC3,CN=NTDS Settings,CN=DC1,CN=Servers,CN=Office,CN=Sites,CN=Configuration,DC=corp,DC=example,DC=com
nTDSConnection CN=DC3,CN=NTDS Settings,CN=DC2,CN=Servers,CN=Office,CN=Sites,CN=Configuration,DC=corp,DC=example,DC=com
PS C:\> Get-ADObject -SearchBase (Get-ADRootDSE).defaultNamingContext -LDAPFilter '(cn=DC3)' | Format-Table ObjectClass, DistinguishedName
ObjectClass DistinguishedName
----------- -----------------
computer CN=DC3,OU=Domain Controllers,DC=corp,DC=example,DC=com
msDFSR-Member CN=DC3,CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,CN=System,DC=corp,DC=example,DC=com
msDFSR-Member is the SYSVOL replication member. Add the DNS records (more on them below) and possibly FSMO roles. As long as NTDS Settings exists, the other controllers consider dc3 a partner: replication with it is attempted and fails, repadmin /replsummary and dcdiag show errors, and the KCC builds the topology around it.
Metadata cleanup removes from the directory what the replication system knows a controller by, the FRS and DFS Replication connections, and tries to transfer or seize the FSMO roles the removed controller held. It is run on a controller of the same domain, preferably a direct partner of the removed one. There are two ways, both described in the documentation.
Snap-ins. Since Windows Server 2008, deleting a controller's account from the Domain Controllers OU in Active Directory Users and Computers performs the cleanup automatically: the snap-in asks you to confirm the controller is permanently offline, warns if it was a GC, and offers the controller its roles will go to. The same works in Active Directory Sites and Services, but there you delete NTDS Settings first and then the server object. If deletion answers "Access is denied", the object is protected from accidental deletion; clear that protection.
ntdsutil does the same from the command line on a controller or a machine with RSAT:
C:\> ntdsutil
ntdsutil: metadata cleanup
metadata cleanup: connections
server connections: connect to server dc4.corp.example.com
server connections: quit
metadata cleanup: remove selected server CN=DC3,CN=Servers,CN=Branch,CN=Sites,CN=Configuration,DC=corp,DC=example,DC=com
metadata cleanup: quit
ntdsutil: quit
connect to server goes to a replication partner of the removed controller; remove selected server opens a confirmation dialog, after which ntdsutil reports that the server was removed or, if the object is already gone, that it can't be found. If nothing is left under the server object after cleanup, it is deleted; if child objects remain, the server object is left alone: another application uses it.
The ActiveDirectory module has no separate cmdlet for cleanup. Afterwards repeat the searches above: Get-ADObject for (cn=DC3) in both partitions and the connections by fromServer must return nothing except what has to be deleted by hand (manually created connections, an empty server object), and Get-ADDomainController dc3 must answer with an error.
DNS. The controller's DNS records are a separate story. In a graceful demotion Netlogon should remove its SRV and A records, but the NS records in zones where the controller was a name server, and the PTR, are worth checking in any case; after a forced demotion or a controller's death no one removes anything. Find everything pointing to dc3 by name or address in all the server's primary zones:
PS C:\> $pattern = '^dc3\.corp\.example\.com\.$|^192\.168\.2\.10$'
PS C:\> $records = Get-DnsServerZone -ComputerName dc1 | Where-Object { -not $_.IsAutoCreated -and $_.ZoneType -eq 'Primary' } | ForEach-Object {
$zone = $_.ZoneName
Get-DnsServerResourceRecord -ComputerName dc1 -ZoneName $zone |
Where-Object { $_.RecordData.CimInstanceProperties.Value -match $pattern } |
Select-Object @{n='Zone'; e={$zone}}, HostName, RecordType, @{n='Data'; e={$_.RecordData.CimInstanceProperties.Value -join ' '}}
}
PS C:\> $records | Where-Object Zone -eq '_msdcs.corp.example.com' | Format-Table HostName, RecordType, Data
HostName RecordType Data
-------- ---------- ----
@ NS dc3.corp.example.com.
6e5d4c3b-2a19-4f08-8e7d-6c5b4a392817 CNAME dc3.corp.example.com.
_kerberos._tcp.Branch._sites.dc SRV dc3.corp.example.com. 88 0 100
_ldap._tcp.Branch._sites.dc SRV dc3.corp.example.com. 389 0 100
_kerberos._tcp.dc SRV dc3.corp.example.com. 88 0 100
_ldap._tcp.dc SRV dc3.corp.example.com. 389 0 100
_ldap._tcp.7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f.domains SRV dc3.corp.example.com. 389 0 100
gc A 192.168.2.10
_ldap._tcp.Branch._sites.gc SRV dc3.corp.example.com. 3268 0 100
_ldap._tcp.gc SRV dc3.corp.example.com. 3268 0 100
The record data comes from the CimInstanceProperties of the RecordData object: each record type has its own properties (IPv4Address for A, DomainName, Port, Priority, Weight for SRV, NameServer for NS), while RecordData.PSObject.Properties would also return the CIM object's service properties. The pattern is anchored to the start and end of the value so that a dc3 from someone else's zone (say, a name server in a stub zone of a trusted forest) doesn't make the list. In total the live dc3 has a few dozen such records: 20 SRV, the CNAME <GUID>._msdcs from part 18, five A records (the domain name, dc3, gc._msdcs, DomainDnsZones, ForestDnsZones), a PTR and an NS in every zone where it is a name server. Remove-DnsServerResourceRecord deletes them, and the found objects can be piped into it; for example, all of dc3's SRV records in the domain zone:
PS C:\> Get-DnsServerResourceRecord -ComputerName dc4 -ZoneName corp.example.com -RRType Srv |
Where-Object { $_.RecordData.DomainName -eq 'dc3.corp.example.com.' } |
Remove-DnsServerResourceRecord -ComputerName dc4 -ZoneName corp.example.com -Force
The final check is repadmin /replsummary and dcdiag /s:dc4 /e /q with no mention of dc3.
The old name
Applications with dc3.corp.example.com in their settings are the reason you want to keep the name. Three ways.
Reuse the name. After the demotion or metadata cleanup and replication, the DC3 account is deleted (Remove-Computer only disables it), and the new server can be named dc3 and promoted: the account is gone, there is no conflict. The old address can be reused too. This is the cleanest option if there is a pause between retiring the old one and bringing in the new one. If a controller account with that name still exists, Install-ADDSDomainController refuses to continue without -AllowDomainControllerReinstall.
An additional name. A controller can answer to several DNS names: netdom computername dc5.corp.example.com /add:dc3.corp.example.com writes the second name into the account's msDS-AdditionalDnsHostName attribute, adds SPNs for it and registers DNS records for it, so Kerberos to ldap/dc3.corp.example.com starts working on dc5. The old DC3 account must be deleted by then: otherwise the same SPNs end up on two accounts. It is the standard mechanism designed for renaming controllers (/add, then /makeprimary and, after a reboot, /remove of the old name), and it works for everything that speaks Kerberos. What names a controller has right now is shown by /enumerate and /verify:
PS C:\> Invoke-Command dc3 { netdom computername dc3.corp.example.com /enumerate; netdom computername dc3.corp.example.com /verify }
All of the names for the computer are:
dc3.corp.example.com
The command completed successfully.
All of the computer's names have A records that are properly registered
with DNS.
All of the computer's names have properly registered host Service Principal
Names in the Active Directory Domain Services.
The command completed successfully.
A CNAME dc3 → dc5 works only for what speaks LDAP with a simple bind or goes by IP: the Kerberos client asks for a ticket for the name dc3, no one has an SPN with that name, and authentication falls back to NTLM (part 6). The worst of the three.
Functional levels
The domain and forest functional level determines two things: which directory features are enabled, and controllers of which versions can run in that domain and forest. The level each controller understands is recorded in the msDS-Behavior-Version attribute of its NTDS Settings: 7 is Windows Server 2016 (2019 and 2022 have no level of their own and stay on 2016), 10 is 2025. In the site Branch before the replacement:
PS C:\> Get-ADObject -SearchBase "CN=Branch,CN=Sites,$((Get-ADRootDSE).configurationNamingContext)" -LDAPFilter '(objectClass=nTDSDSA)' -Properties 'msDS-Behavior-Version', 'msDS-JetDBPageSize' |
Format-Table @{n='Server'; e={($_.DistinguishedName -split ',')[1] -replace 'CN='}}, 'msDS-Behavior-Version', 'msDS-JetDBPageSize'
Server msDS-Behavior-Version msDS-JetDBPageSize
------ --------------------- ------------------
DC3 7
DC4 10 32768
msDS-JetDBPageSize 32768 on dc4 is that very 32 KB page database: dc4 was promoted on a clean Windows Server 2025. A 2025 controller upgraded in place would show 8192 here, and dc3 on 2019 has no such attribute at all. The level of the domain and forest themselves is the same attribute on the domain root and on CN=Partitions:
PS C:\> (Get-ADDomain).DomainMode
Windows2016Domain
PS C:\> (Get-ADForest).ForestMode
Windows2016Forest
PS C:\> (Get-ADObject "CN=Partitions,$((Get-ADRootDSE).configurationNamingContext)" -Properties 'msDS-Behavior-Version').'msDS-Behavior-Version'
7
A Windows Server 2025 controller runs at the 2016 and 2025 levels, but not lower (2012 R2 is no longer supported for it); controllers on 2016, 2019 and 2022 run at the 2012 R2 and 2016 levels. So as long as dc3 is in the forest, the 2025 level is unavailable, and once it is retired, the levels can be raised. What the levels today's controllers can live at give:
| Level | What it gives |
|---|---|
| 2012 R2 | controller-side protection of the Protected Users group (no NTLM, no DES or RC4 in pre-authentication, no delegation, no TGT renewal beyond four hours), authentication policies and authentication policy silos |
| 2016 | for the forest, privileged access management (PAM) with Microsoft Identity Manager; for the domain, automatic rolling of NTLM secrets on accounts that require smart card logon, network NTLM for users restricted to specific computers, the fresh public key identity SID with PKInit Freshness |
| 2025 | the Database 32k pages optional feature: 32 KB database pages instead of 8 |
Windows Server 2025's delegated managed service accounts (dMSA) are not on the level's feature list. The AD Recycle Bin, PAM and 32 KB pages are optional forest features, and each requires its own level:
PS C:\> Get-ADOptionalFeature -Filter * | Format-Table Name, RequiredForestMode
Name RequiredForestMode
---- ------------------
Recycle Bin Feature Windows2008R2Forest
Privileged Access Management Feature Windows2016Forest
Database 32k Pages Feature Windows2025Forest
Raising the level takes two commands, the domain first and then the forest, because the domain level can't be lower than the forest level:
PS C:\> Set-ADDomainMode -Identity corp.example.com -DomainMode Windows2025Domain
PS C:\> Set-ADForestMode -Identity corp.example.com -ForestMode Windows2025Forest
Each changes one msDS-Behavior-Version attribute, on the domain root and on CN=Partitions, to the value 10, and the change spreads by ordinary replication. The level can't be lowered back (the exceptions are specific rollbacks to 2008 R2 and 2012 R2), and a controller of an older version can no longer join such a forest. Enabling 32 KB pages is a separate step after that: every controller of the forest must be on 2025 with a database that supports 32 KB, that is, installed afresh rather than upgraded in place; enabling it is irreversible, and backups with an 8 KB database can no longer be used for restore afterwards. Backup is the topic of part 20.
Hands-on
How a controller appears in the forest configuration. The NTDS Settings object of a new controller is created not on the controller itself but on the replication source the promotion goes through. The metadata of NTDS Settings of dc4, which was promoted from dc3:
PS C:\> repadmin /showobjmeta dc1 "CN=NTDS Settings,CN=DC4,CN=Servers,CN=Branch,CN=Sites,CN=Configuration,DC=corp,DC=example,DC=com" | Select-String '^Loc.USN|objectClass|invocationId|1 options|Behavior-Version|JetDBPageSize'
Loc.USN Originating DSA Org.USN Org.Time/Date Ver Attribute
56534580 Branch\DC3 37019648 2026-09-22 19:30:06 1 objectClass
56534580 Branch\DC3 37019648 2026-09-22 19:30:06 1 invocationId
56534580 Branch\DC3 37019648 2026-09-22 19:30:06 1 options
56534580 Branch\DC3 37019648 2026-09-22 19:30:06 1 msDS-Behavior-Version
56534735 Branch\DC4 24586 2026-09-22 19:31:53 1 msDS-JetDBPageSize
objectClass, invocationId, options (the GC bit) and the controller's level were written by dc3, at the moment the wizard on dc4 asked it to create the object. Two minutes later, already with its own database, dc4 added the page size: a change with a small USN of dc4 itself (24586), which had only just started its history. With dc5 it will be the same, only the source will be dc4. Demotion walks this path in reverse: NTDS Settings is deleted, while the server object stays, because objects of other applications may live under it.
Controller accounts differ from other computers by primary group 516 (Domain Controllers) and by their flags:
PS C:\> Get-ADComputer -Filter 'primaryGroupID -eq 516' -Properties userAccountControl | Format-Table Name, userAccountControl, DistinguishedName
Name userAccountControl DistinguishedName
---- ------------------ -----------------
DC2 532480 CN=DC2,OU=Domain Controllers,DC=corp,DC=example,DC=com
DC1 532480 CN=DC1,OU=Domain Controllers,DC=corp,DC=example,DC=com
DC3 532480 CN=DC3,OU=Domain Controllers,DC=corp,DC=example,DC=com
DC4 532480 CN=DC4,OU=Domain Controllers,DC=corp,DC=example,DC=com
532480 is SERVER_TRUST_ACCOUNT (8192, a controller account) plus TRUSTED_FOR_DELEGATION (524288). After a graceful demotion DC3 will have 4096, WORKSTATION_TRUST_ACCOUNT, like any member server, primary group 515, and it won't be on this list; after a forced demotion without cleanup it stays here as if nothing happened.
Sources
- Upgrade domain controllers to a newer version of Windows Server — the recommendation to replace rather than upgrade,
adprep /forestprepand/domainprep, required rights and FSMO roles, the order of adding the new controller and retiring the old one; - Windows Server Active Directory schema updates — the
schNN.ldffiles and schema versions 87, 88 and 91; - Install-ADDSDomainController —
-ADPrepCredential,-ReplicationSourceDC,-AllowDomainControllerReinstall, automatic configuration of the DNS client and forwarders; - Uninstall-ADDSDomainController and Test-ADDSDomainControllerUninstallation — parameter sets,
-DemoteOperationMasterRole,-ForceRemoval, checks versus-WhatIf; - Demote domain controllers and domains — demotion, forced demotion and its consequences, logs, removing the role after the reboot;
- Uninstall-WindowsFeature — removing roles with
-IncludeManagementTools; - Clean up AD DS server metadata — metadata cleanup with snap-ins and
ntdsutil, protection from accidental deletion; - How Active Directory Replication Topology Works — the KCC neither modifies nor deletes manually created connections;
- Move-ADDirectoryServerOperationMasterRole — moving FSMO roles;
- 4768 and 4769 — fields of the Kerberos ticket events, client address;
- Set-DnsServerDiagnostics — the DNS server debug log, required parameter pairs and
-All; - Set-DnsServerForwarder and Set-DnsClientServerAddress — forwarders and the controllers' DNS client;
- Get-DnsServerResourceRecord and Remove-DnsServerResourceRecord — finding and deleting records, pipeline input;
- Remove-DhcpServerv4FailoverScope, Remove-DhcpServerv4Failover, Add-DhcpServerv4Failover and Invoke-DhcpServerv4FailoverReplication — recreating the failover relationship, what happens to the scope on the partner;
- Netdom computername and Rename a domain controller using Netdom — additional controller names, SPNs and DNS records for them;
- ms-DS-Additional-Dns-Host-Name — the additional names attribute;
- Active Directory Domain Services functional levels — compatibility of levels and controller versions, features of the 2012 R2, 2016 and 2025 levels;
- How to raise Active Directory domain and forest functional levels and ms-DS-Behavior-Version — the order of raising, irreversibility, where the level is stored;
- Set-ADDomainMode and Set-ADForestMode — raising the levels from PowerShell;
- Database 32k pages for Active Directory and What's new in Windows Server 2025 — the 32 KB page database on new and upgraded controllers, requirements and irreversibility;
- Delegated Managed Service Accounts overview — dMSA in Windows Server 2025;
- Use the UserAccountControl flags — values of the
userAccountControlflags.
We can now replace controllers and clean up after them. One operations scenario is left, the most unpleasant one: not a single controller but the whole directory, or objects in it that no controller has anymore.
Next — part 20, backup and recovery. What system state is and how a directory backup differs from a disk copy; wbadmin and Windows Server Backup; the AD Recycle Bin and its limits; non-authoritative restore of a controller and authoritative restore of objects deleted everywhere; SYSVOL restore in D2 and D4 modes; and virtual machine snapshots: what they give, what they don't, and what VM-GenerationID does with them.
Only registered users can leave a comment.
Sign in or register to leave a comment.