AZ-802 Study Guide: Automating Windows Server 2025 Lab Setup (Part 3): Unattended REPRO-DC02 Setup
Automate the deployment and promotion of an additional replica domain controller (DC02) in an existing Active Directory forest via PowerShell
📖 AZ-802 Study Series: This is Part 3 of my hands-on lab series leading up to the Microsoft AZ-802 exam. To see the preceding steps, check out Part 2: Automating Windows Server Lab Setup and Part 1: Building a Multi-DC Active Directory Forest, or check out The Ultimate Guide to Windows Server Hybrid Administrator Associate (AZ-802) Study Guide for the complete roadmap.
In an enterprise IT infrastructure, a single domain controller is a single point of failure. To achieve high availability and load-balanced authentication services for on-premises and hybrid workloads, a secondary replica domain controller is required.
In Part 1 of this series, we mapped out the forest architecture. In Part 2, we automate the deployment of REPRO-DC01 (the forest root). In this third installment, we will walk through the automated provisioning and replica promotion of REPRO-DC02 using PowerShell, Hyper-V, and Windows unattended answer files.
🎓 Exam Alignment (Active Directory Domain Services & Site Topology):
Configuring, promoting, and maintaining replica domain controllers and Active Directory replication topologies are key skills tested in the AZ-802: Configuring Windows Server Hybrid Advanced Services exam under the "Deploy and Manage Active Directory Domain Services" domain. You are expected to understand:
- Replica Promotion: Deploying and promoting replica domain controllers into an existing forest using PowerShell (Install-ADDSDomainController).
- Active Directory Sites & Subnets: Creating and managing AD sites, configuring replication subnets, and managing site link costs.
- Replication Diagnostics: Troubleshooting replication status, verifying directory database health, and identifying transient sync failures using command-line diagnostics (like repadmin and
dcdiag).- Server Core Administration: Deploying domain controller roles on Server Core and managing them remotely.
The Architecture: DC01 vs. DC02
While the initial installation of Windows Server 2025 is identical on both machines, their post-install bootstrap tasks differ significantly:
| Configuration Aspect | Primary DC (REPRO-DC01) |
Replica DC (REPRO-DC02) |
|---|---|---|
| Target OS Type | Datacenter (Desktop Experience) | Datacenter (Server Core) |
| Static IP Address | 10.0.1.10/24 |
10.0.1.11/24 |
| Primary DNS Server | 127.0.0.1 (Self-pointing) |
10.0.1.10 (Points to DC01) |
| Secondary DNS Server | 1.1.1.1 (Public Resolver) |
1.1.1.1 (Public Resolver) |
| AD Role Component | Active Directory Domain Services | Active Directory Domain Services |
| Promotion Cmdlet | Install-ADDSForest | Install-ADDSDomainController |
| Target Context | Creates a new forest root | Joins an existing domain partition |
The Critical Gotcha: DNS Bootstrapping
[!IMPORTANT]
The single most common failure point when automating secondary domain controllers is DNS resolution.During the promotion phase, Install-ADDSDomainController queries the DNS server for SRV records matching the domain name (
lab.reprodev.local). If the replica server cannot resolve the IP address of the Primary DC through DNS, the domain join will fail immediately.The replica DC must have its primary network adapter's DNS server set to the IP address of the primary DC (
10.0.1.10) before the AD DS role promotion starts.
The Full Automation Script (rebuild-repro-dc02.ps1)
Save this script as rebuild-repro-dc02.ps1 and run it from an elevated Administrator PowerShell console on your Hyper-V host:
# HYPER-V VM REBUILD & UNATTENDED BOOTSTRAPPER FOR REPRO-DC02
# Run this from an Administrator PowerShell console.
# 1. Configuration Variables
$VMName = "REPRO-DC02"
$VMRoot = "D:\Hyper-V"
$VMPath = Join-Path $VMRoot $VMName
$UnattendIsoPath = Join-Path $VMPath "${VMName}-unattend.iso"
$AdminPassword = "P@ssword123!"
$IsoPath = "C:\Users\KnightboxOC\windows-server2025-26100.1742.240906-0331.ge_release_svc_refresh_SERVER_EVAL_x64FRE_en-us.iso"
# Stop and remove existing VM if it exists to start completely fresh
if (Get-VM -Name $VMName -ErrorAction SilentlyContinue) {
Write-Host "VM '$VMName' already exists. Cleaning up and deleting it..." -ForegroundColor Yellow
Stop-VM -Name $VMName -Force -ErrorAction SilentlyContinue
Remove-VM -Name $VMName -Force
}
# Remove existing unattend.iso if it exists to avoid locks
$oldIso = Join-Path $VMPath "${VMName}-unattend.iso"
if (Test-Path $oldIso) {
Remove-Item -Path $oldIso -Force -ErrorAction SilentlyContinue
}
# Delete the existing VM folder if it exists to avoid conflicts
if (Test-Path $VMPath) {
Start-Sleep -Seconds 1
Remove-Item -Path $VMPath -Recurse -Force -ErrorAction SilentlyContinue
}
# 2. Pre-create the directory structure
New-Item -ItemType Directory -Path $VMPath -Force | Out-Null
New-Item -ItemType Directory -Path (Join-Path $VMPath "Virtual Hard Disks") -Force | Out-Null
# 2.5 Ensure the Internal NAT Switch and Host Routing are Configured
$switchName = "Internal-Lab-Switch"
$gatewayIP = "10.0.1.1"
if (-not (Get-VMSwitch -Name $switchName -ErrorAction SilentlyContinue)) {
New-VMSwitch -Name $switchName -SwitchType Internal -ErrorAction Stop | Out-Null
}
$hostInterfaceAlias = "vEthernet ($switchName)"
$existingIP = Get-NetIPAddress -InterfaceAlias $hostInterfaceAlias -IPAddress $gatewayIP -ErrorAction SilentlyContinue
if (-not $existingIP) {
Remove-NetIPAddress -InterfaceAlias $hostInterfaceAlias -Confirm:$false -ErrorAction SilentlyContinue
New-NetIPAddress -InterfaceAlias $hostInterfaceAlias -IPAddress $gatewayIP -PrefixLength 24 -ErrorAction Stop | Out-Null
}
$existingNat = Get-NetNat | Where-Object {$_.InternalIPInterfaceAddressPrefix -eq "10.0.1.0/24"}
if (-not $existingNat) {
Get-NetNat -Name "LabNatNetwork" -ErrorAction SilentlyContinue | Remove-NetNat -Confirm:$false
New-NetNat -Name "LabNatNetwork" -InternalIPInterfaceAddressPrefix "10.0.1.0/24" -ErrorAction Stop | Out-Null
}
# 3. Create the VM in its dedicated path
New-VM -Name $VMName `
-Generation 2 `
-MemoryStartupBytes 2048MB `
-Path $VMPath `
-NewVHDPath (Join-Path $VMPath "Virtual Hard Disks\${VMName}.vhdx") `
-NewVHDSizeBytes 50GB `
-SwitchName "Internal-Lab-Switch" | Out-Null
# 4. Configure Best Practice VM Settings
Set-VM -Name $VMName -ProcessorCount 2 -DynamicMemory -MemoryMinimumBytes 1024MB -MemoryMaximumBytes 4096MB -AutomaticCheckpointsEnabled $false
Enable-VMIntegrationService -VMName $VMName -Name "Guest Service Interface"
Set-VMFirmware -VMName $VMName -EnableSecureBoot On
# Configure virtual TPM
Set-VMKeyProtector -VMName $VMName -NewLocalKeyProtector
Enable-VMTPM -VMName $VMName
# 5. Create the unattended installation ISO folder structure
$TempDir = Join-Path $VMPath "TempUnattend"
New-Item -ItemType Directory -Path $TempDir -Force | Out-Null
# Define IMAPI2 ISO generation helper
if (-not ([System.Type]::GetType("IsoHelper.FileUtil"))) {
$code = @"
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
namespace IsoHelper {
public static class FileUtil {
public static void WriteIStreamToFile(object i, string fileName) {
IStream inputStream = i as IStream;
using (FileStream outputFileStream = File.OpenWrite(fileName)) {
byte[] buffer = new byte[2048];
int bytesRead = 0;
IntPtr pcbRead = Marshal.AllocCoTaskMem(sizeof(int));
try {
do {
inputStream.Read(buffer, buffer.Length, pcbRead);
bytesRead = Marshal.ReadInt32(pcbRead);
if (bytesRead > 0) {
outputFileStream.Write(buffer, 0, bytesRead);
}
} while (bytesRead > 0);
} finally {
Marshal.FreeCoTaskMem(pcbRead);
}
outputFileStream.Flush();
}
}
}
}
"@
Add-Type -TypeDefinition $code
}
# 6. Write the autounattend.xml content (Desktop Experience / GUI configuration)
$xmlPath = Join-Path $TempDir "autounattend.xml"
$xmlContent = @"
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
<settings pass="windowsPE">
<component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WCM/2002/XML">
<InputLocale>en-US</InputLocale>
<SystemLocale>en-US</SystemLocale>
<UILanguage>en-US</UILanguage>
<UserLocale>en-US</UserLocale>
<SetupUILanguage>
<UILanguage>en-US</UILanguage>
<WillShowUI>Never</WillShowUI>
</SetupUILanguage>
</component>
<component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WCM/2002/XML">
<DiskConfiguration>
<Disk wcm:action="add">
<CreatePartitions>
<CreatePartition wcm:action="add">
<Order>1</Order>
<Type>EFI</Type>
<Size>100</Size>
</CreatePartition>
<CreatePartition wcm:action="add">
<Order>2</Order>
<Type>MSR</Type>
<Size>128</Size>
</CreatePartition>
<CreatePartition wcm:action="add">
<Order>3</Order>
<Type>Primary</Type>
<Extend>true</Extend>
</CreatePartition>
</CreatePartitions>
<ModifyPartitions>
<ModifyPartition wcm:action="add">
<Order>1</Order>
<PartitionID>1</PartitionID>
<Format>FAT32</Format>
<Label>System</Label>
</ModifyPartition>
<ModifyPartition wcm:action="add">
<Order>2</Order>
<PartitionID>3</PartitionID>
<Format>NTFS</Format>
<Label>Windows</Label>
</ModifyPartition>
</ModifyPartitions>
<DiskID>0</DiskID>
<WillWipeDisk>true</WillWipeDisk>
</Disk>
<WillShowUI>OnError</WillShowUI>
</DiskConfiguration>
<ImageInstall>
<OSImage>
<InstallTo>
<DiskID>0</DiskID>
<PartitionID>3</PartitionID>
</InstallTo>
<InstallFrom>
<MetaData wcm:action="add">
<Key>/IMAGE/INDEX</Key>
<!-- Index 3 selects Windows Server Datacenter (Server Core) Evaluation -->
<Value>3</Value>
</MetaData>
</InstallFrom>
</OSImage>
<WillShowUI>OnError</WillShowUI>
</ImageInstall>
<UserData>
<AcceptEula>true</AcceptEula>
<FullName>Reprodev Lab</FullName>
<Organization>Reprodev</Organization>
</UserData>
</component>
</settings>
<settings pass="specialize">
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WCM/2002/XML">
<ComputerName>$VMName</ComputerName>
<TimeZone>GMT Standard Time</TimeZone>
</component>
<component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WCM/2002/XML">
<RunSynchronous>
<RunSynchronousCommand wcm:action="add">
<Order>1</Order>
<Path>reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\OOBE" /v DisablePrivacyExperience /t REG_DWORD /d 1 /f</Path>
<Description>Disable Privacy Settings Experience</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
</settings>
<settings pass="oobeSystem">
<component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WCM/2002/XML">
<InputLocale>en-GB</InputLocale>
<SystemLocale>en-GB</SystemLocale>
<UILanguage>en-US</UILanguage>
<UserLocale>en-GB</UserLocale>
</component>
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WCM/2002/XML">
<AutoLogon>
<Password>
<Value>$AdminPassword</Value>
<PlainText>true</PlainText>
</Password>
<Enabled>true</Enabled>
<LogonCount>1</LogonCount>
<Username>Administrator</Username>
</AutoLogon>
<UserAccounts>
<AdministratorPassword>
<Value>$AdminPassword</Value>
<PlainText>true</PlainText>
</AdministratorPassword>
</UserAccounts>
<FirstLogonCommands>
<SynchronousCommand wcm:action="add">
<Order>1</Order>
<CommandLine>powershell -Command "`$drive = (Get-Volume -FileSystemLabel 'UNATTEND' -ErrorAction SilentlyContinue).DriveLetter; if (`$drive) { powershell -ExecutionPolicy Bypass -File \"`${drive}:\bootstrap.ps1\" > C:\bootstrap.log 2>&1 }"</CommandLine>
<Description>Run Post-Install Bootstrap Script</Description>
</SynchronousCommand>
</FirstLogonCommands>
</component>
</settings>
</unattend>
"@
$utf8NoBom = [System.Text.UTF8Encoding]::new($false)
[System.IO.File]::WriteAllText($xmlPath, $xmlContent, $utf8NoBom)
# 8. Write the post-install bootstrap script containing network configuration and AD DS replica promotion
$bootstrapPath = Join-Path $TempDir "bootstrap.ps1"
$bootstrapContent = @"
# ==============================================================================
# POST-INSTALL BOOTSTRAP SCRIPT FOR REPRO-DC02
# Runs automatically on first logon to configure networking and AD DS Replica
# ==============================================================================
Write-Host "Enabling WinRM and setting network profile to Private..." -ForegroundColor Green
Enable-PSRemoting -Force
Set-NetConnectionProfile -NetworkCategory Private
Write-Host "Configuring Static IP (10.0.1.11), Gateway (10.0.1.1), and DNS (10.0.1.10)..." -ForegroundColor Green
`$adapter = Get-NetAdapter | Select-Object -First 1
if (`$adapter) {
Set-NetIPInterface -InterfaceIndex `$adapter.InterfaceIndex -DHCP Disabled -ErrorAction SilentlyContinue
Remove-NetIPAddress -InterfaceIndex `$adapter.InterfaceIndex -Confirm:`$false -ErrorAction SilentlyContinue
Remove-NetRoute -InterfaceIndex `$adapter.InterfaceIndex -Confirm:`$false -ErrorAction SilentlyContinue
New-NetIPAddress -InterfaceIndex `$adapter.InterfaceIndex -IPAddress 10.0.1.11 -PrefixLength 24 -DefaultGateway 10.0.1.1 -ErrorAction SilentlyContinue
Set-DnsClientServerAddress -InterfaceIndex `$adapter.InterfaceIndex -ServerAddresses "10.0.1.10","1.1.1.1" -ErrorAction SilentlyContinue
}
Write-Host "Installing Active Directory Domain Services role..." -ForegroundColor Green
Install-WindowsFeature AD-Domain-Services -IncludeManagementTools
Write-Host "Configuring post-promotion AutoLogon for LAB\Administrator..." -ForegroundColor Green
`$winlogonPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
Set-ItemProperty -Path `$winlogonPath -Name "AutoAdminLogon" -Value "1" -Force -ErrorAction SilentlyContinue
Set-ItemProperty -Path `$winlogonPath -Name "DefaultUserName" -Value "Administrator" -Force -ErrorAction SilentlyContinue
Set-ItemProperty -Path `$winlogonPath -Name "DefaultPassword" -Value "P@ssword123!" -Force -ErrorAction SilentlyContinue
Set-ItemProperty -Path `$winlogonPath -Name "DefaultDomainName" -Value "LAB" -Force -ErrorAction SilentlyContinue
Write-Host "Promoting to replica Domain Controller in existing domain (lab.reprodev.local)..." -ForegroundColor Green
`$dsrmPassword = ConvertTo-SecureString "P@ssword123!" -AsPlainText -Force
`$domainCred = New-Object System.Management.Automation.PSCredential("LAB\Administrator", (ConvertTo-SecureString "P@ssword123!" -AsPlainText -Force))
Install-ADDSDomainController -DomainName "lab.reprodev.local" -Credential `$domainCred -SafeModeAdministratorPassword `$dsrmPassword -InstallDns -Force
"@
[System.IO.File]::WriteAllText($bootstrapPath, $bootstrapContent, $utf8NoBom)
# 9. Compile the ISO using Windows IMAPI2 COM objects
$fsi = New-Object -ComObject IMAPI2FS.MsftFileSystemImage
$fsi.FileSystemsToCreate = 7
$fsi.VolumeName = "UNATTEND"
$fsi.Root.AddTree($TempDir, $false)
$resultImage = $fsi.CreateResultImage()
[IsoHelper.FileUtil]::WriteIStreamToFile($resultImage.ImageStream, $UnattendIsoPath)
# Clean up temp folder
Remove-Item -Path $TempDir -Recurse -Force -ErrorAction SilentlyContinue
# 10. Re-create virtual DVD drives and mount both ISOs
Get-VMDvdDrive -VMName $VMName | Remove-VMDvdDrive
$installDvd = Add-VMDvdDrive -VMName $VMName -Path $IsoPath -Passthru
$unattendDvd = Add-VMDvdDrive -VMName $VMName -Path $UnattendIsoPath -Passthru
# 11. Set UEFI boot order: DVD -> HDD
$hdd = Get-VMHardDiskDrive -VMName $VMName
Set-VMFirmware -VMName $VMName -BootOrder $installDvd, $hdd
# 13. Start the VM and automatically inject the boot keypress via Hyper-V WMI
Start-VM -Name $VMName
Start-Sleep -Seconds 1
$vmWmi = Get-WmiObject -Namespace "root\virtualization\v2" -Class "Msvm_ComputerSystem" |
Where-Object { $_.ElementName -eq $VMName }
$keyboard = $vmWmi.GetRelated("Msvm_Keyboard") | Select-Object -First 1
$pressEndTime = (Get-Date).AddSeconds(25)
while ((Get-Date) -lt $pressEndTime) {
[void]$keyboard.PressKey(32) # Spacebar
Start-Sleep -Milliseconds 100
[void]$keyboard.ReleaseKey(32)
Start-Sleep -Milliseconds 100
[void]$keyboard.PressKey(13) # Enter
Start-Sleep -Milliseconds 100
[void]$keyboard.ReleaseKey(13)
Start-Sleep -Milliseconds 100
}
Write-Host "Boot prompt window passed. Windows Setup is loading..." -ForegroundColor Green
Write-Host "The unattended installation will now proceed fully automatically." -ForegroundColor Yellow
Write-Host "Expected time to completion: 10-20 minutes. You can monitor progress in the VM Connection window." -ForegroundColor Cyan
How It Works: Script Deep Dive
1. Target DNS Configured Prior to Join
The bootstrap sets the primary DNS server on REPRO-DC02 to point directly to 10.0.1.10 (the primary domain controller REPRO-DC01):
Set-DnsClientServerAddress -InterfaceIndex $adapter.InterfaceIndex -ServerAddresses "10.0.1.10","1.1.1.1"
This enables the VM to locate and authenticate against the lab.reprodev.local domain immediately.
2. Passing Domain Credentials Securely
To promote a server as a replica domain controller, the promotion cmdlet needs domain-wide administrator permissions. The script constructs a secure credential object using the domain administrator account:
$domainCred = New-Object System.Management.Automation.PSCredential("LAB\Administrator", (ConvertTo-SecureString "P@ssword123!" -AsPlainText -Force))
3. Executing Replica Promotion
Instead of calling Install-ADDSForest (which initializes a brand-new directory database), the script runs the replica promotion cmdlet:
Install-ADDSDomainController `
-DomainName "lab.reprodev.local" `
-Credential $domainCred `
-SafeModeAdministratorPassword $dsrmPassword `
-InstallDns -Force
This configures REPRO-DC02 as a domain controller and kicks off initial directory database replication.
Step-by-Step Deployment Runbook
Step 1: Verify REPRO-DC01 Status
Ensure that the primary domain controller (REPRO-DC01) is running, has completed its bootstrap, and has active internet access. You can test connectivity from the Hyper-V host:
Test-Connection -ComputerName 10.0.1.10 -Count 2
Step 2: Re-run the Replica Bootstrapper Script
Execute the bootstrapper script on the host as Administrator:
C:\Users\KnightboxOC\Documents\PowershellHyperVLab\V2\Repro-DC02.ps1
Step 3: Monitor Installation
Windows Setup will run unattended. Once the OOBE pass finishes, the VM logs in as local Administrator and runs the bootstrap. You can inspect progress within the guest VM console or by reading the local log file inside the VM:
Get-Content C:\bootstrap.log -Wait
[!NOTE]
Like DC01, the entire OS install, role configuration, and domain promotion reboot takes about 20 minutes.On its final boot, the registry keys re-apply
AutoAdminLogonautomatically, logging you straight into the desktop as the Domain Admin (LAB\Administrator).
- Username:
LAB\Administrator- Default Password:
P@ssword123!
Verification & Replication Diagnostics
Once REPRO-DC02 boots to the desktop, you must verify that both domain controllers are actively replicating directory databases.
Run the following commands in an elevated PowerShell console inside either VM:
1. Check Replication Summary
repadmin /replsummary
Expected Output:
Both REPRO-DC01 and REPRO-DC02 are listed under Source/Destination with 0 fails and replication times under 15 minutes.
2. Force Immediate Replication
repadmin /syncall /AeD
Expected Output:
Replication completes with messages confirming success: SyncAll terminated with no errors.
3. Check FSMO Role Assignment
Verify that FSMO roles reside on REPRO-DC01 (since it is the forest root master):
Get-ADForest | Select-Object Name, SchemaMaster, DomainNamingMaster
Get-ADDomain | Select-Object DNSRoot, PDCEmulator, RIDMaster, InfrastructureMaster
🎓 Exam Alignment (Replication Troubleshooting):
On the AZ-802 exam, you will be expected to analyze diagnostic output fromrepadminto isolate sync failures. Be sure you know the difference betweenrepadmin /replsummary(quick health check across all DCs) andrepadmin /showrepl(granular transactional error details).
Post-Installation Cleanup
Once you have verified replication, shut down the REPRO-DC02 VM, and detach the unattend ISO:
# Remove the unattend configuration DVD drive
Get-VMDvdDrive -VMName "REPRO-DC02" | Where-Object {$_.Path -like "*-unattend.iso"} | Remove-VMDvdDrive
You can now delete the REPRO-DC02-unattend.iso file from your storage folder safely.
Key Takeaways
- DNS is the AD Lifeline: A secondary DC can only join the forest if it can resolve the target domain controller dynamically via DNS.
- Install-ADDSDomainController is for Replicas: Use this cmdlet to replicate existing schemas; never use
Install-ADDSForestfor secondary nodes. - Automate Replication Verification: Running repadmin immediately after deployment ensures the domain replication database is healthy and ready to process local authentication requests.
Don't forget to explore the rest of our website as we build out more content. Stay tuned for more tutorials, tips, and tricks to help you make tech work for you.
If you want to stay up-to-date with regular updates, make sure to subscribe to our free mailing list.