# Lab: Build an Active Directory Environment on Hyper-V

> Two domain controllers, DHCP, and an OU structure worth practising against

Canonical: https://everettkildare.com/blog/lab-build-an-active-directory-environment-on-hyper-v
Author: Everett Kildare
Published: 2026-07-16
Updated: 2026-11-01

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

```mermaid
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
```

> [!TIP]
> 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.

> [!CAUTION]
> 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

- [Windows Server 2022 | Microsoft Evaluation Center](https://www.microsoft.com/en-us/evalcenter/evaluate-windows-server-2022) — the 180-day evaluation period for Windows Server 2022.
- [Windows 11 Enterprise | Microsoft Evaluation Center](https://www.microsoft.com/en-us/evalcenter/evaluate-windows-11-enterprise) — the 90-day evaluation period for Windows 11 Enterprise.
- [Install Hyper-V in Windows and Windows Server](https://learn.microsoft.com/en-us/windows-server/virtualization/hyper-v/get-started/install-hyper-v) — Pro/Enterprise requirement and the `Enable-WindowsOptionalFeature` command.
- [Set up a NAT network](https://learn.microsoft.com/en-us/windows-server/virtualization/hyper-v/setup-nat-network) — internal switch, gateway address and `New-NetNat` procedure.
- [Install-ADDSForest (ADDSDeployment)](https://learn.microsoft.com/en-us/powershell/module/addsdeployment/install-addsforest) — `WinThreshold` functional level and the loopback DNS default on the first DC.
- [Virtualized Domain Controller Architecture](https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/get-started/virtual-dc/virtualized-domain-controller-architecture) — VM-Generation ID safe restore (invocation ID reset, RID pool discarded) and quarantine only where no Generation ID is provided.
- [Windows 11 requirements](https://learn.microsoft.com/en-us/windows/whats-new/windows-11-requirements) — VM requirements: Generation 2, Secure Boot and vTPM on Hyper-V, 4 GB memory, two virtual processors, 64 GB storage; vTPM emulated independently of the host TPM.
- [Hyper-V Generation 2 Virtual Machine Security Features](https://learn.microsoft.com/en-us/windows-server/virtualization/hyper-v/generation-2-virtual-machine-security-features) — Secure Boot on by default, the `Microsoft Windows` template, and the key protector needing an update after a move to another host.
- [Set-VMKeyProtector (Hyper-V)](https://learn.microsoft.com/en-us/powershell/module/hyper-v/set-vmkeyprotector) — `-NewLocalKeyProtector`.
- [Enable-VMTPM (Hyper-V)](https://learn.microsoft.com/en-us/powershell/module/hyper-v/enable-vmtpm) — enabling the virtual TPM.
- [Set-VMFirmware (Hyper-V)](https://learn.microsoft.com/en-us/powershell/module/hyper-v/set-vmfirmware) — `-EnableSecureBoot` and `-SecureBootTemplate`.
- [Set-VMProcessor (Hyper-V)](https://learn.microsoft.com/en-us/powershell/module/hyper-v/set-vmprocessor) — `-Count` for the virtual processor count.
- [Using checkpoints](https://learn.microsoft.com/en-us/windows-server/virtualization/hyper-v/checkpoints) — production versus standard checkpoints, the production default, `.avhdx` storage, and the warning that a checkpoint is not a backup for replicated systems such as AD.
- [RFC 6762: Multicast DNS](https://www.rfc-editor.org/info/rfc6762/) — `.local` reserved for mDNS; recommends against it for private unicast DNS.
- [Approved Resolutions | Special Meeting of the ICANN Board | 29 July 2024](https://www.icann.org/en/board-activities-and-meetings/materials/approved-resolutions-special-meeting-of-the-icann-board-29-07-2024-en) — `.internal` permanently reserved for private use.
