Migrating my lab domain controller from Server 2022 to Server 2025

29 August 2026 · Luke Gillmore-White

My lab domain has run on a single Server 2022 VM since I first set it up. When Server 2025 came along I wanted to move to it, but not with an in-place upgrade. In production you almost never upgrade a domain controller in place. You build a new one alongside, let it replicate, move the roles across and retire the old one. That’s the process I wanted practice at, so that’s how I did it, even though the lab only has one DC.

Health check before touching anything

Migrating a domain that’s already unhealthy just moves the problems to new hardware, so the first job was confirming the existing DC was clean:

dcdiag /test:dns
nltest /dsgetdc:<domain>
repadmin /replsummary

This turned up one real issue. The DC’s network adapter was set to use only 127.0.0.1 for DNS. That’s a common default after promotion, but it meant the adapter’s network profile came up as Public rather than Domain, which in turn applies the wrong firewall rules. Pointing the primary DNS at the DC’s own LAN address, with loopback as secondary, fixed it on the next reboot.

Before going further I took a Proxmox snapshot of the old DC. Snapshotting a domain controller used to be risky, because rolling one back could cause USN rollback and quietly corrupt replication. Hyper-V and Proxmox now expose a VM Generation ID, which AD checks to detect that it’s been restored and protect itself. I confirmed vmgenid was present in the VM config before relying on the snapshot.

Building the new DC

The new VM was built from the Server 2025 evaluation ISO, with the VirtIO drivers attached as a second CD drive:

Machine:  q35 + OVMF (UEFI)
TPM:      v2.0
CPU:      host, 2 cores
RAM:      8GB
Disk:     80GB VirtIO SCSI single
NIC:      VirtIO
Agent:    QEMU guest agent enabled from the start

I gave it a static IP, pointed its DNS at the existing DC, and named it to match the rest of the lab.

The adprep error

Promoting a 2025 DC into a domain whose schema is still at 2022 level needs the forest and domain to be prepared first. Install-ADDSDomainController will normally run that preparation for you. Mine failed every time with:

Adprep was unable to check the specified user's group membership.
Access is denied.

The account I was passing in with -Credential was a member of Schema Admins and Enterprise Admins. DNS resolved, and the ports were open. Everything that error usually points to checked out.

The cause: adprep doesn’t use the credential you pass to the promotion cmdlet. It runs its group membership checks as the logged-on user. Because the new server wasn’t domain-joined yet, I was signed in as its local Administrator, which has no rights in the domain at all.

One more oddity while I was debugging: Get-Credential never showed its dialog on that box. Building the credential manually works anywhere:

$user = "<DOMAIN>\Administrator"
$pass = Read-Host "Password" -AsSecureString
$cred = New-Object System.Management.Automation.PSCredential($user, $pass)

The clean fix was to run the preparation myself, on the existing DC (where I was signed in with domain rights), straight from the mounted 2025 ISO:

D:\support\adprep\adprep.exe /forestprep
D:\support\adprep\adprep.exe /domainprep

That took the schema from version 88 to 91. This step is irreversible, and the snapshot was the only way back if anything went wrong. With the schema already prepared, the new server had nothing left to do as the logged-on user. After a restart to clear a pending role change left behind by the failed attempts, promotion went through:

Install-ADDSDomainController `
    -DomainName "<domain>" `
    -InstallDns:$true `
    -Credential $cred `
    -SafeModeAdministratorPassword (Read-Host "DSRM password" -AsSecureString)

It warned that a DNS delegation couldn’t be created. That’s expected for an internal-only zone with no Windows parent zone to delegate from, and it’s safe to ignore.

Verifying replication

With two DCs in the domain, I checked replication in both directions before moving anything:

repadmin /replsummary      # 0 failures in both directions
dcdiag /test:replications

Moving the FSMO roles

All five operations master roles moved to the new DC in a single command:

Move-ADDirectoryServerOperationMasterRole -Identity "GL-DC01" `
    -OperationMasterRole SchemaMaster, DomainNamingMaster, PDCEmulator, RIDMaster, InfrastructureMaster

netdom query fsmo

My Entra Cloud Sync agent was running on the old DC, so I installed a second agent on the new one first. Both showed as active in the tenant, which meant the sync to Entra never had a gap.

Demoting the old DC

Demotion stopped on one last problem. The old DC insisted it held the last copy of the AD-integrated DNS zones and refused to continue. It didn’t: the new DC had both zones loaded and answering. I confirmed that directly on the new DC first, then told the demotion to proceed:

Uninstall-ADDSDomainController `
    -IgnoreLastDNSServerForZone `
    -LocalAdministratorPassword (Read-Host "New local admin password" -AsSecureString)

Check the zones exist elsewhere before using that switch. It’s there for exactly this false alarm, but if the warning had been true, it would have deleted the domain’s DNS.

Clean-up

  • Unregister the old Cloud Sync agent from the tenant
  • Repoint anything with a hard-coded DNS server at the new DC (in my case, the homelab dashboard’s links)
  • Delete the old VM and the pre-migration snapshot once everything has been stable for a while

Result

The lab domain now runs on Server 2025, with all five FSMO roles on the new DC, clean replication history, and an Entra sync that never dropped. More usefully, I’ve now worked through the full side-by-side replacement process, including the adprep behaviour that isn’t obvious from the error message. That’s exactly the kind of thing that would otherwise surface for the first time during a real client migration.