Create Quest on Demand Migration Lab – Part1 Build a Zero-Trust, Two-Tenant Playground
Time to build: ~ 8 Hours
Prerequisites:
- Hyper-V host
64 – 128 GB RAM
~ 1 TB SSD - ISO’s
Windows Server 2022 or later
Windows 11 - Two Microsoft 365 tenants
If you’ve ever stared at a blank lab and thought, “where do I even start for a real tenant-to-tenant migration?”, this series is for you. I’m building a lab that behaves like the messy, high-stakes world we actually work in, two tenants, hybrid identity, strict security, and the freedom to break things safely. Then we’ll stand up Quest On Demand Migration (ODM), design jobs, and rehearse the move like pros.
The busines story (why this lab exists)
ContosoLab sells a business unit to an investment firm. The unit becomes FabrikamLab, an independent company on a fresh tenant. We need to move identities, mail, OneDrive, SharePoint, Teams, and all the little gremlins in between, while keeping coexistence (chat, meetings, mail flow, calendars) until cutover. And because the world is what it is, we’ll do it on a zero-trust foundation
What you’ll have by the end of Part 1
- A Hyper-V host with three virtual switches feeding a firewall hub (pfSense) (WAN + two isolated LAN’s)
- Two miniature Enterprises: ContosoLab and FabrikamLAb
- Two Microsoft 365 tenant, each wired to its domain via Entra Connect, ready for Quest ODM

Quest on Demand Lab
Prerequisites (host, ISO’s, tenants)
- Hardware:
CPUs with at least 4 cores (8 threads) are recommended.
RAM : 64 – 128 GB
DISKS: 1 TB SSD recommended (more is nicer) - Host OS:
Windows 11 pro/Enterprise or Windows Server with the Hyper-V Role.
These can be downloaded from the Microsoft Evaluation Center or if you have a Partner agreement with Microsoft with one of the Partner Core benefits, or a Visual Studio license. - Firewall: pfSense community edition is a instance ISO that can be downloaded from here
- Tenant: as two tenants are requires, create these for the Microsoft demo – and developer portal
Why two isolated tenants?
Because Quest ODM scenarios live or die on identity, permisions and throthling that single-tenant demos never surface
Step 1 – Create the network Spine (Hyper-V + pfSense)
We’ll use three switches: one External (bound to the host NIC), and two Internal Networks, one per lab domain. pfSense sits in the middle as a routed hub.
Create the vSwitches
The switch can be create from the GUI or with PowerShell as shown here
# External WAN (NAT via your physical network)
New-VMSwitch -Name "vWAN-External" -NetAdapterName "Ethernet" -AllowManagementOS $true
# Internal LANs (isolated)
New-VMSwitch -Name "vLAN-ContosoLab" -SwitchType Internal
New-VMSwitch -Name "vLAN-FabrikamLab" -SwitchType Internal
pfSense VM (Small but mighty)
Settings:
- vCPU: 1
- RAM: 1-2 GB
- Disk: 20 GB
- NICs: 3 (WAN = vWAN-External, LAN1 = vLAN-ContosoLab, LAN2 = vLAN-FabrikamLab)
- Set LAN1 to 192.168.10.1/24 and LAN2 to 192.168.20.1/24
- Enable NAT outbound so both LANs reach the internet
- Create Aliasses for common host/ports; write explicit permit rules (keep defaults deny).
- Add a one-liner NAT to reach lab PCs from your laptop if your managing remotely.
The table shows the NAT configuration and Firewall rules applied.
Firewall Rules
Source IP | Port | Destination IP | Port | Traffic type | Allow/Block | Traffic type |
---|---|---|---|---|---|---|
ContosoLab | ||||||
192.168.10.0/24 | any | any | 3478:3481 50000:50059 |
UDP | Allow | Teams |
any | any | any | 80 443 |
TCP | Allow | HTTP/HTTPS |
192.168.10.0/24 | any | any | 123 | UDP | Allow | NTP |
192.168.10.0/24 | any | 192.168.10.1 | 53 | UDP | Allow | DNS |
192.168.10.0/24 | any | 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 |
any | Block | RFC1918 | |
192.168.10.0/24 | any | any | any | any | Block | Block all |
FabrikamLab | ||||||
192.168.20.0/24 | any | any | 3478:3481 50000:50059 |
UDP | Allow | Teams |
any | any | any | 80 443 |
TCP | Allow | HTTP/HTTPS |
192.168.20.0/24 | any | any | 123 | UDP | Allow | NTP |
192.168.20.0/24 | any | 192.168.20.1 | 53 | UDP | Allow | DNS |
192.168.20.0/24 | any | 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 |
any | Block | RFC1918 | |
192.168.20.0/24 | any | any | any | any | Block | Block all |
Zero-Trust tip:
Start opinionated: deny by defaults. Then open only what your build needs.
(AD, DNS, NTP, HTTP/HTTPS, RDP/WINRM for admin).
Step 2 – Built the domains
Stand up each environment in this order: DC → App Server → Workstations. Keep specs lean, so your optimized for repeatable tests, not benchmark, unless it’s your requirement.
VM Name | vCPU | RAM (GB) | Disk (GB) | Secure Boot | TPM | vSwitch | IP |
CONTOSO-DC01 | 1 | 2 | 60 | On | No | vLAN-Contoso | 192.168.10.100 |
CONTOSO-APP01 | 1 | 2 | 60 | On | No | vLAN-Contoso | 192.168.10.110 |
CONTOSO-PC01 | 2 | 4 | 60 | On | Yes | vLAN-Contoso | 192.168.10.123 |
CONTOSO-PC02 | 2 | 4 | 60 | On | Yes | vLAN-Contoso | 192.168.10.124 |
FABRIKAM-DC01 | 1 | 2 | 60 | On | No | vLAN-Fabrikam | 192.168.20.100 |
FABRIKAM-APP01 | 1 | 2 | 60 | On | No | vLAN-Fabrikam | 192.168.20.110 |
FABRIKAM-PC01 | 2 | 4 | 60 | On | Yes | vLAN-Fabrikam | 192.168.20.123 |
These VMs can be created via the GUI or the following PowerShell Script
function New-LabVM {
<#
.SYNOPSIS
Creates a Generation 2 Hyper-V VM with sane defaults (VHDX, memory, CPU, firmware, ISO, TPM).
.EXAMPLE
New-LabVM -Name CONTOSO-DC01 -SwitchName vLAN-Contoso -ISOPath D:\ISOs\WS2022.iso -MemoryStartupGB 2 -VHDSizeGB 60 -VCPU 1
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)][string]$Name,
[Parameter(Mandatory)][string]$SwitchName,
[string]$VMRoot = "C:\Hyper-V\VMs",
[string]$VHDRoot = "C:\Hyper-V\VHDs",
[int]$Generation = 2,
[int]$VCPU = 2,
[int]$MemoryStartupGB = 2,
[switch]$UseDynamicMemory,
[int]$MemoryMinGB = 2,
[int]$MemoryMaxGB = 8,
[int]$VHDSizeGB = 60,
[string]$ISOPath, # optional
[ValidateSet("MicrosoftWindows","MicrosoftUEFICertificateAuthority")]
[string]$SecureBootTemplate = "MicrosoftWindows",
[switch]$DisableSecureBoot,
[switch]$EnableTPM,
[switch]$StartVM
)
# --- Pre-flight ---
if (-not (Get-Module -ListAvailable -Name Hyper-V)) {
throw "Hyper-V module not found. Install the Hyper-V role & tools."
}
if (-not (Get-VMSwitch -Name $SwitchName -ErrorAction SilentlyContinue)) {
throw "VMSwitch '$SwitchName' not found."
}
if ($ISOPath -and -not (Test-Path $ISOPath)) {
throw "ISO not found at $ISOPath"
}
$vmPath = Join-Path $VMRoot $Name
$vhdPath = Join-Path $VHDRoot "$Name.vhdx"
New-Item -ItemType Directory -Path $vmPath -Force | Out-Null
New-Item -ItemType Directory -Path $VHDRoot -Force | Out-Null
# --- Create VM + VHDX ---
$vm = New-VM -Name $Name -Generation $Generation `
-MemoryStartupBytes ($MemoryStartupGB * 1GB) `
-SwitchName $SwitchName `
-Path $vmPath `
-NewVHDPath $vhdPath -NewVHDSizeBytes ($VHDSizeGB * 1GB)
# CPU
Set-VM -Name $Name -ProcessorCount $VCPU
# Memory
if ($UseDynamicMemory) {
Set-VMMemory -VMName $Name -DynamicMemoryEnabled $true `
-MinimumBytes ($MemoryMinGB * 1GB) `
-StartupBytes ($MemoryStartupGB * 1GB) `
-MaximumBytes ($MemoryMaxGB * 1GB)
}
# Firmware / Secure Boot
if ($DisableSecureBoot) {
Set-VMFirmware -VMName $Name -EnableSecureBoot Off
} else {
Set-VMFirmware -VMName $Name -EnableSecureBoot On -SecureBootTemplate $SecureBootTemplate
}
# Optional TPM (for Win11 etc.)
if ($EnableTPM) {
Set-VMKeyProtector -VMName $Name -NewLocalKeyProtector
Enable-VMTPM -VMName $Name
}
# Optional ISO + boot order (DVD first)
if ($ISOPath) {
$dvd = Get-VMDvdDrive -VMName $Name -ErrorAction SilentlyContinue
if (-not $dvd) { $dvd = Add-VMDvdDrive -VMName $Name -Path $ISOPath }
else { Set-VMDvdDrive -VMName $Name -Path $ISOPath | Out-Null }
$hdd = Get-VMHardDiskDrive -VMName $Name | Where-Object { $_.Path -like "*.vhd*" }
Set-VMFirmware -VMName $Name -BootOrder $dvd,$hdd
}
if ($StartVM) { Start-VM -Name $Name }
# Return a quick summary
[PSCustomObject]@{
Name = $Name
Switch = $SwitchName
VCPU = $VCPU
Memory = "{0} GB{1}" -f $MemoryStartupGB, ($(if($UseDynamicMemory){" (dyn ${MemoryMinGB}-${MemoryMaxGB} GB)"}))
VHD = $vhdPath
ISO = $ISOPath
TPM = [bool]$EnableTPM
SecureBoot = $(if($DisableSecureBoot){"Off"} else {$SecureBootTemplate})
}
}
Heads-up
Windows 11: Enable Secure boot and vTPM, or setup will fail. For servers TPM isn’t required here, but is no issue when enabling
Keep your tiering model in mind as you build OU’s and admin accounts, even in a lab, muscle memory matters. Apply Windows Security Baseline or even the CIS aligned benchmark or your environments security requirements, so you catch migration snags that only appear under tightened policies.
Step 3 – Tenants that resemble reality
For the source tenant I have used the demo version provided by Microsoft, as it has user, groups, SharePoint and Teams already provisioned.
Also here depending on your requirements additional setting like security labels or PowerBi dashboard can be created, which I will elaborate more in Part 2 – Setting up your Source Tenant.
As of the business case the target has been created from Microsofts Developer site, with no users and data, but also a trail tenant will suffice.
For each tenant I registers the DNS domain (purchased) and confirmed these can be used later for the routing and coexistence.
These tenants will be available for 90 days be aware to remove the domains from the tenants, beforehand.
Step 4 – Wire identity with Entra Connect
Install Entra Connect on the CONTOSO-APP01 and the FABRIKAM-APP01. Configure the OU’s that you want to have synced.
Also here opening up ports was required for the sync to work accordingly.
After the source domain was created I created some user and groups in AD as these would be synced onto the tenant. As some user also exist on the source tenant you can do some matching, or you can make change regarding the licenses for the synced users.
with this action I also confirmed :
- UPN suffix matches the tenant’s primary or verified domain.
- Password hash sync (or pass-through) enabled.
This is the moment the lab starts feeling like the real world: two directories, two tenants, controlled network path, and identities flowing on your terms.
Step 5 – Workstation logon
As a last step I finished setting up one of the workstations by logging on as an user and adding the work and school account and installing the Microsoft 365 apps. With the last check on checking if Outlook is opening and mail can be send and received, as this workstation will be part of the migration process at the end.
What’s next (Part2)
We’ll seed realistic data (mail, OneDrive, SharePoint, Teams, even a bit of PowerBi and some security labels) so Quest ODM has something meaningful to chew on. Then we’ll light up Quest On Demand, grant consent and start designing migration jobs.