Skip to content
Menu

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

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)
After install:
  • 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.