Skip to main content

πŸ§ͺ Offline Boot Testing & Generalization of VHDX OS Images

VHDX Imaging and Azure Upload Process Guide

This guide follows the Disk2vhd imaging and Azure upload process. In networks where the .vhdx is synced to local hypervisor storage, the image must be tested offline to validate bootability before being generalized and cleaned.


1. πŸ”Œ Boot Test in Local Staging Hypervisor

Objective:

Ensure the .vhdx image boots in a virtual machine within a completely offline staging environment.

Steps:

  1. Import the .vhdx into your hypervisor (e.g., Hyper-V, Proxmox, VMware Workstation).
  2. Create a test VM:
    • Connect the disk as the primary boot volume.
    • Ensure no internet/network adapters are connected.
  3. Power on the VM.

🏒 Special Case: Ridgebrook Client Deployment

For Ridgebrook:

  • Images stored in the Azure file share:
    • Storage account: clientosimages01e2usdtc
    • File share: client-os-images-01
  • The image should be synced to the local staging hypervisor:
    • Hypervisor name: ELDERBRAIN
    • Local path:
      D:\Virtual Hard Disks\client-os-images-01
  • Ridgebrook .vhdx images should be placed into a subfolder matching their PSA company name (e.g., ridgebrook-industrial).

Once synced, follow the standard boot validation and conversion process below.


2. πŸ”„ Convert MBR to GPT (If Needed)

If the VM fails to boot, the .vhdx likely uses MBR instead of GPT.

βœ… Option 1: MBR2GPT (Recommended)

Steps:

mbr2gpt /validate /disk:0 /allowFullOS
mbr2gpt /convert /disk:0 /allowFullOS

Make sure the OS volume is the last volume and delete any non-essential partitions (see below).

⚠️ Option 2: gptgen (Advanced / Manual Bootloader Required)

gptgen is a third-party tool that can convert MBR to GPT when MBR2GPT fails or is unsupported (e.g., on older systems or modified layouts). However, it does not set up the bootloader automatically.

Key Points:

  • You must manually recreate the EFI partition.
  • You must manually install the UEFI bootloader using bcdboot after conversion.
  • Proceed only if MBR2GPT is not usable.

  • Always follow up gptgen with Step 4: Rebuild BCD Bootloader.

    Bootloader.

  • 3. πŸ“Ό Cleaning Up Partitions With DiskPart

    Target Partition Layout (Post-Post‑Conversion):

    Partition Purpose Size
    1 EFI System 4 GB
    2 Recovery 4 GB
    3 OS Volume Remainder

    Why fixed sizes?
    Microsoft has misaligned these in the past β€” we standardize to 10 GB for EFI and Recovery to avoid risk.

    DiskPart Steps:

    diskpart
    list disk
    select disk 0
    list partition
    
    • Delete unwanted partitions (OEM, redundant recovery, etc).

    • Create EFI:

    create partition efi size=4096
    format quick fs=fat32 label="System"
    assign letter=S
    
    • Create Recovery (optional):

    create partition primary size=4096
    format quick fs=ntfs label="Recovery"
    assign letter=R
    

    4. πŸ₯Ύ Rebuild BCD Bootloader

    bcdboot C:\Windows /s S: /f UEFI
    
    • C: = OS path
    • S: = EFI partition
    • /f UEFI = Force GPT boot

    5. πŸ“½πŸ§‘β€πŸ’» Generalize the OS Image (Post-Boot Cleanup)

    Once the VHDX boots successfully in offline staging, perform the following steps to prepare it as a reusable image.

    πŸ‘₯ Optional:REQUIRED: Backup & Remove User Profiles (for shared deployments)

    IfBefore thisproceeding, image will be deployed toyou multiple VMs or physical workstationsmust, it's important to remove all local user accounts and profiles to avoid SID duplication and profile conflicts. This is required for all images.

    Step 1: Backup Profiles with ProfWiz

     (if needed)

    Use ForensiT User Profile Wizard (ProfWiz) to safely back up andprofiles if you need to preserve profilesany before deletion.data.

    Step 2: Delete Local User Profiles

    wmic useraccount where "name!='Administrator' and name!='DefaultAccount' and name!='Guest' and name!='WDAGUtilityAccount'" delete
    
    for /D %%i in (C:\Users\*) do (
      rd /s /q "%%i"
    )
    

    ⚠️ This permanentlyPermanently deletes all local user accounts and their data, except system accounts.


    πŸ” Clear Agent Identity & Token Data (Preserve Services)

    ToolRemove From RegistryClear File System Path
    NinjaRMMHKLM\SOFTWARE\NinjaRMMC:\ProgramData\NinjaRMMAgent
    Veeam AgentHKLM\SOFTWARE\Veeam\Veeam Endpoint BackupC:\ProgramData\Veeam
    reg delete "HKLM\SOFTWARE\WOW6432NODE\NinjaRMM\Agent\NodeId" /f
    reg delete "HKLM\SOFTWARE\Veeam /f
    
    rd /s /q "C:\ProgramData\NinjaRMMAgent"
    rd /s /q "C:\ProgramData\Veeam"
    

    ❗ Do not delete agent service keys under HKLM\SYSTEM\CurrentControlSet\Services.


    πŸ₯΅ Windows Log Cleanup

    del /s /q C:\Windows\System32\winevt\Logs\*.*
    del /f /q %SystemRoot%\Panther\*.*
    del /f /q %SystemRoot%\Windows.old\*.*
    

    6. πŸ’Ύ Optional:REQUIRED: Skip OOBE with unattend.xml

    UseYou must use an unattend.xml to skip setup UI and telemetry.telemetry for all images.

    Sample unattend.xml Snippet

    <?xml version="1.0" encoding="utf-8"?>
    <unattend xmlns="urn:schemas-microsoft-com:unattend"
              xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <!-- ========================== -->
      <!-- 1️⃣  OOBE-SYSTEM   (runs after specialize) -->
      <!-- ========================== -->
      <settings pass="oobeSystem">
        <component name="Microsoft-Windows-Shell-Setup"
                   processorArchitecture="amd64"
                   publicKeyToken="31bf3856ad364e35"
                   language="neutral"
                   versionScope="nonSxS">
    
          <!-- A. Auto-logon (must be first inside this component) -->
          <AutoLogon>
            <Username>installadmin</Username>
            <Password>
              <Value>DTC@dental2025</Value>
              <PlainText>true</PlainText>
            </Password>
            <Enabled>true</Enabled>
            <LogonCount>1</LogonCount>
          </AutoLogon>
          <!-- B. Suppress *all* interactive OOBE pages -->
          <OOBE>
            <HideEULAPage>true</HideEULAPage>
            <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
            <NetworkLocation>Work</NetworkLocation>
            <ProtectYourPC>3</ProtectYourPC>
            <SkipMachineOOBE>true</SkipMachineOOBE>
            <SkipUserOOBE>true</SkipUserOOBE>
          </OOBE>
          <!-- C. Regional / time-zone preference -->
          <TimeZone>Eastern Standard Time</TimeZone>
    
          <!-- D. Create the local administrator account -->
          <UserAccounts>
            <LocalAccounts>
              <LocalAccount wcm:action="add">
                <Name>installadmin</Name>
                <Group>Administrators</Group>
                <Password>
                  <Value>DTC@dental2025</Value>
                  <PlainText>true</PlainText>
                </Password>
              </LocalAccount>
            </LocalAccounts>
          </UserAccounts>
        </component>
      </settings>
      <!-- ========================== -->
      <!-- 2️⃣  SPECIALIZE  (optional tweaks, none that cause β€œcomponent missing”) -->
      <!-- ========================== -->
      <settings pass="specialize">
    
        <!-- Disable Application Experience telemetry that does still exist -->
        <component name="Microsoft-Windows-ApplicationExperience"
                   processorArchitecture="amd64"
                   publicKeyToken="31bf3856ad364e35"
                   language="neutral"
                   versionScope="nonSxS">
          <AITEnable>false</AITEnable>
        </component>
    
        <!-- Disable Windows Error Reporting pop-ups -->
        <component name="Microsoft-Windows-ErrorReportingCore"
                   processorArchitecture="amd64"
                   publicKeyToken="31bf3856ad364e35"
                   language="neutral"
                   versionScope="nonSxS">
          <DisableWerReporting>true</DisableWerReporting>
        </component>
      </settings>
    </unattend>
    

    How

    7. πŸ“ Optional (But Highly Recommended) Pre-Generalization Cleanup

    Before generalization, consider performing the following steps to Useensure a clean, reusable image. These are optional but highly recommended for images that will be widely distributed.

    πŸ” Clear Agent Identity & Token Data (Preserve Services)

    ToolRemove From RegistryClear File System Path
    NinjaRMMHKLM\SOFTWARE\WOW6432NODE\NinjaRMMC:\ProgramData\NinjaRMMAgent
    Veeam AgentHKLM\SOFTWARE\Veeam\Veeam Endpoint BackupC:\ProgramData\Veeam
    reg delete "HKLM\SOFTWARE\WOW6432NODE\NinjaRMM\Agent\NodeId" /f
    reg delete "HKLM\SOFTWARE\Veeam" /f
    
    rd /s /q "C:\ProgramData\NinjaRMMAgent"
    rd /s /q "C:\ProgramData\Veeam"
    

    ❗ Do not delete agent service keys under HKLM\SYSTEM\CurrentControlSet\Services.

    πŸ₯΅ Windows Log Cleanup

    del /s /q C:\Windows\System32\winevt\Logs\*.*
    del /f /q %SystemRoot%\Panther\*.*
    

    8. πŸ“Έ Create VM Checkpoint Before Generalization

    ⚠️ CRITICAL STEP: After all cleanup and preparation, create a checkpoint/snapshot of your VM. This allows you to restore and retry if generalization fails.

    Steps:

    1. In your hypervisor management console, create a checkpoint/snapshot
    2. Name it appropriately (e.g., "Pre-Sysprep-Checkpoint")
    3. This allows you to restore and retry if sysprep fails

    9. 🎯 Generalization (Sysprep) and Appx Package Removal (Final Steps)

    Step 1: Run Generalization (Sysprep)

    sysprep /generalize /oobe /shutdown /unattend:C:\Windows\System32\Sysprep\unattend.xml
    

    Expected Outcome: Sysprep may fail on the first attempt due to problematic packages. This is normal and expected.

    Step 2: If Sysprep Fails, Remove Problematic Windows Packages

    After a failed sysprep attempt, analyze the logs and remove problematic packages.

    Review setuperr.log

    Open:

    C:\Windows\System32\Sysprep\Panther\setuperr.log
    

    Look for entries such as:

    Package <Name> cannot be removed and is preventing Sysprep
    

    Remove Identified Packages via PowerShell

    βœ… Include asterisks (*) before and after the package name for fuzzy matching.

    Get-AppxPackage -AllUsers *PackageName* | Remove-AppxPackage -AllUsers
    

    If Get-AppxPackage does not list the package, use DISM:

    dism /Online /Remove-ProvisionedAppxPackage /PackageName:*PackageName*
    

    You can script removal for multiple packages by parsing the log.

    πŸ›‘ Run from an elevated PowerShell session and verify success before continuing.

    Retry Generalization

    After removing problematic packages, run sysprep again:

    sysprep /generalize /oobe /shutdown /unattend:C:\Windows\System32\Sysprep\unattend.xml
    

    Repeat this process until sysprep completes successfully without errors.

    βœ… Final Checklist

    • Booted offline on hypervisor
    • Converted to GPT with valid partitions
    • Optional: user profiles backed up and deleted
    • Agent identities reset (binaries remain)
    • Problematic packages removed based on setuperr.log
    •  unattend.xml disables OOBE + telemetry
    • Sysprep executed successfully