This lab builds a working Active Directory environment on Hyper-V: two domain controllers, a member server, and a client, on an isolated network. It is the foundation for practising Group Policy, certificate services, replication troubleshooting, and every AD-adjacent topic worth knowing.

Everything runs on a single machine with 32 GB of RAM. Evaluation ISOs from Microsoft are free: 180 days for Windows Server 2022, 90 days for Windows 11 Enterprise.

What you will build#

graph TB subgraph "Hyper-V host" subgraph "LAB-Internal switch — 10.10.10.0/24" DC1[DC01<br/>10.10.10.10<br/>AD DS + DNS] DC2[DC02<br/>10.10.10.11<br/>AD DS + DNS] SRV[FS01<br/>10.10.10.20<br/>File server] CLI[WS01<br/>DHCP<br/>Windows 11] end RTR[RTR01<br/>10.10.10.1<br/>NAT gateway] end RTR --- INET[Host network]

Domain: lab.internal. Never use a domain name you do not control, and never .local — it collides with mDNS.

Prerequisites#

  • Windows 10/11 Pro or Windows Server with Hyper-V enabled
  • 32 GB RAM (16 GB works if you run three VMs instead of four)
  • 200 GB free on an SSD
  • Windows Server 2022 and Windows 11 evaluation ISOs

Enable Hyper-V if it is not already:

PowerShell
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All
# Reboot required

Step 1 — Networking#

Create an internal switch. Internal, not private: the host gets an adapter on it, which lets you provide NAT for updates.

PowerShell
New-VMSwitch -Name "LAB-Internal" -SwitchType Internal

# The host's adapter on that switch becomes the lab's gateway
$ifIndex = (Get-NetAdapter -Name "vEthernet (LAB-Internal)").ifIndex
New-NetIPAddress -IPAddress 10.10.10.1 -PrefixLength 24 -InterfaceIndex $ifIndex

New-NetNat -Name "LAB-NAT" -InternalIPInterfaceAddressPrefix 10.10.10.0/24

Verify:

PowerShell
Get-NetNat
Get-NetIPAddress -InterfaceIndex $ifIndex | Format-Table IPAddress, PrefixLength

Use a checkpoint after each major stage. Checkpoint-VM -Name DC01 -SnapshotName "pre-dcpromo" costs seconds and saves you rebuilding when an experiment goes wrong. That is the entire point of a lab.

Step 2 — Create the VMs#

PowerShell
$vmPath = "D:\Hyper-V"
$isoServer = "D:\ISO\WindowsServer2022.iso"

function New-LabVM {
    param([string]$Name, [int64]$MemoryGB, [string]$Iso, [switch]$Windows11)

    New-VM -Name $Name -MemoryStartupBytes ($MemoryGB * 1GB) `
           -Generation 2 -Path $vmPath `
           -NewVHDPath "$vmPath\$Name\$Name.vhdx" -NewVHDSizeBytes 80GB `
           -SwitchName "LAB-Internal"

    # Windows 11 needs 4 GB, so it gets no dynamic-memory floor below that
    $minMemory = if ($Windows11) { $MemoryGB * 1GB } else { 1GB }
    Set-VM -Name $Name -DynamicMemory `
           -MemoryMinimumBytes $minMemory -MemoryMaximumBytes ($MemoryGB * 1GB) `
           -AutomaticCheckpointsEnabled $false -CheckpointType Production
    Set-VMProcessor -VMName $Name -Count 2

    Add-VMDvdDrive -VMName $Name -Path $Iso
    $dvd = Get-VMDvdDrive -VMName $Name
    Set-VMFirmware -VMName $Name -FirstBootDevice $dvd `
                   -EnableSecureBoot On -SecureBootTemplate MicrosoftWindows

    if ($Windows11) {
        # Windows 11 setup refuses to install without TPM 2.0
        Set-VMKeyProtector -VMName $Name -NewLocalKeyProtector
        Enable-VMTPM -VMName $Name
    }
}

New-LabVM -Name "DC01" -MemoryGB 4 -Iso $isoServer
New-LabVM -Name "DC02" -MemoryGB 4 -Iso $isoServer
New-LabVM -Name "FS01" -MemoryGB 4 -Iso $isoServer
New-LabVM -Name "WS01" -MemoryGB 4 -Iso "D:\ISO\Windows11.iso" -Windows11

The Windows 11 client is the one VM with hard requirements. Microsoft's VM requirements for Windows 11 on Hyper-V are Generation 2, Secure Boot, a virtual TPM, at least 4 GB of memory, two or more virtual processors and 64 GB of disk. Without -Windows11 the script creates no vTPM, and Windows 11 setup will refuse to install. The virtual TPM is emulated, so the host does not need a physical TPM of its own. The local key protector ties the VM to this host: move it to another machine and it may not start until its key protector is updated.

Why AutomaticCheckpointsEnabled $false? This is advice, not a correctness fix. Every checkpoint is an .avhdx differencing disk beside the .vhdx, so checkpoints Hyper-V takes on its own cost disk space. They also clutter the list with entries you did not choose to make. In a lab you want only the checkpoints you took deliberately, with names you chose. -CheckpointType Production makes those checkpoints use VSS inside the guest. That gives you a data-consistent image rather than a snapshot of running memory. It is already the default for new VMs, but the script states it so nobody has to guess.

Rolling a domain controller back is less dangerous than it used to be. Windows Server 2012 and later DCs read the VM-Generation ID that Hyper-V exposes. When a checkpoint is applied, that ID changes. The DC notices the mismatch, resets its invocation ID and discards its RID pool. Its partners then treat it as a new replication source and replicate back what it lost. That prevents both USN reuse and duplicate SIDs. The old failure, where AD detects USN rollback and quarantines the DC, applies only to DCs that cannot see a Generation ID: Windows Server 2008 R2 and earlier, or a hypervisor that does not provide one.

Safe restore is not a backup strategy. Microsoft's own checkpoint documentation warns that a checkpoint is not a full backup and can cause consistency problems for replicated systems such as Active Directory. Safe restore only works if another DC still holds the changes. Roll back your only DC and everything since that checkpoint is gone. Applying checkpoints in this lab is fine — that is what a lab is for — but protect real DCs with Windows Server Backup or a system-state backup, not hypervisor checkpoints.

Step 3 — Promote the first domain controller#

Install Windows Server on DC01, then configure networking:

PowerShell
Rename-Computer -NewName "DC01" -Restart

New-NetIPAddress -InterfaceAlias "Ethernet" -IPAddress 10.10.10.10 `
                 -PrefixLength 24 -DefaultGateway 10.10.10.1

# Points at itself — corrected after promotion
Set-DnsClientServerAddress -InterfaceAlias "Ethernet" -ServerAddresses 127.0.0.1

Promote it:

PowerShell
Install-WindowsFeature AD-Domain-Services, DNS -IncludeManagementTools

Install-ADDSForest `
    -DomainName "lab.internal" `
    -DomainNetbiosName "LAB" `
    -ForestMode "WinThreshold" `
    -DomainMode "WinThreshold" `
    -InstallDns:$true `
    -SafeModeAdministratorPassword (Read-Host -AsSecureString "DSRM password") `
    -NoRebootOnCompletion:$false `
    -Force:$true

After reboot, verify:

PowerShell
Get-ADDomain
Get-ADForest
dcdiag /v /c

dcdiag should pass every test. Investigate any failure now — a broken forest root only gets harder to diagnose once you add a second DC.

Step 4 — Second domain controller#

Configure DC02 with 10.10.10.11 and DNS pointing at 10.10.10.10 — it must resolve the domain to find it.

PowerShell
Install-WindowsFeature AD-Domain-Services, DNS -IncludeManagementTools

Install-ADDSDomainController `
    -DomainName "lab.internal" `
    -Credential (Get-Credential "LAB\Administrator") `
    -InstallDns:$true `
    -SafeModeAdministratorPassword (Read-Host -AsSecureString "DSRM password") `
    -Force:$true

Once up, fix DNS on both so each prefers its partner and falls back to itself. This avoids the classic "island" problem where a DC that only knows itself cannot start AD services cleanly after a restart:

PowerShell
# On DC01
Set-DnsClientServerAddress -InterfaceAlias "Ethernet" -ServerAddresses 10.10.10.11,127.0.0.1
# On DC02
Set-DnsClientServerAddress -InterfaceAlias "Ethernet" -ServerAddresses 10.10.10.10,127.0.0.1

Verify replication:

PowerShell
repadmin /replsummary
repadmin /showrepl
Get-ADReplicationPartnerMetadata -Target "lab.internal" -Scope Domain |
    Format-Table Server, Partner, LastReplicationSuccess

Step 5 — DHCP and OU structure#

Install DHCP on DC01 for the client subnet:

PowerShell
Install-WindowsFeature DHCP -IncludeManagementTools
Add-DhcpServerInDC -DnsName "DC01.lab.internal" -IPAddress 10.10.10.10

Add-DhcpServerv4Scope -Name "LAB" -StartRange 10.10.10.100 `
                      -EndRange 10.10.10.200 -SubnetMask 255.255.255.0
Set-DhcpServerv4OptionValue -ScopeId 10.10.10.0 `
                            -DnsServer 10.10.10.10,10.10.10.11 `
                            -Router 10.10.10.1 -DnsDomain "lab.internal"

Build an OU structure worth practising against — mirroring how real environments separate by function rather than dumping everything in Users:

PowerShell
$base = "DC=lab,DC=internal"

New-ADOrganizationalUnit -Name "LAB" -Path $base
New-ADOrganizationalUnit -Name "Users"    -Path "OU=LAB,$base"
New-ADOrganizationalUnit -Name "Servers"  -Path "OU=LAB,$base"
New-ADOrganizationalUnit -Name "Clients"  -Path "OU=LAB,$base"
New-ADOrganizationalUnit -Name "Groups"   -Path "OU=LAB,$base"
New-ADOrganizationalUnit -Name "Service Accounts" -Path "OU=LAB,$base"

# Populate with test users
1..25 | ForEach-Object {
    $name = "user{0:D2}" -f $_
    New-ADUser -Name $name -SamAccountName $name `
        -UserPrincipalName "$name@lab.internal" `
        -Path "OU=Users,OU=LAB,$base" `
        -AccountPassword (ConvertTo-SecureString "LabP@ssw0rd!" -AsPlainText -Force) `
        -Enabled $true -ChangePasswordAtLogon $false
}

Step 6 — Join and verify#

Join FS01 and WS01 to the domain:

PowerShell
Add-Computer -DomainName "lab.internal" `
             -Credential (Get-Credential "LAB\Administrator") `
             -OUPath "OU=Servers,OU=LAB,DC=lab,DC=internal" `
             -Restart

Verification checklist — all of these should succeed:

PowerShell
nltest /dsgetdc:lab.internal          # locates a DC
gpresult /r                            # policy applies
Test-ComputerSecureChannel -Verbose    # secure channel healthy
Resolve-DnsName _ldap._tcp.lab.internal -Type SRV   # SRV records present

What to practise next#

The lab exists so you can break things safely. In rough order of value:

  1. Group Policy — build a baseline GPO, apply security filtering, use WMI filters, and work out precedence with gpresult /h report.html.
  2. Fine-grained password policies — a PSO applied to a group, overriding the domain default.
  3. AD Certificate Services on FS01, then issue certificates via autoenrolment.
  4. Break replication deliberately — take DC02 offline for longer than the tombstone lifetime and learn what recovery involves.
  5. Seize FSMO roles after simulating the permanent loss of DC01. ntdsutil is not a tool you want to learn during an outage.
  6. Delegated administration — grant a helpdesk group password-reset rights on the Users OU only, then verify the boundary holds.

Checkpoint before each. Roll back after. That loop is worth more than any amount of reading.

Sources#