Deploy a Hyper-V VM and register it for Autopilot automatically using PowerShell

I need a VM connected to the customer’s tenant for Endpoint Manager testing. This involves deploying a Windows 10 or 11 VM, changing hardware settings (Secure Boot/TPM/Checkpoint settings), and registering it for Autopilot. This blog post will show you how to automate the process as much as possible.

What does the script do?

The script will run the following steps:

  • Ask for a VM name
  • Ask for the number of CPU cores
  • Ask for the amount of RAM in GBs (Static and not Dynamic)
  • Ask for the HDD size in GBs
  • Show a list of ISO files you specified in the $ISOPath variable at the top of the list, and select the one that you want to use for installing the VM
  • Show a list of your VM switches, and select the one that you want to use for the VM
  • Create the VM with all the settings (Secure Boot/TPM/Checkpoints/CPU/RAM/HDD/ISO and will connect a Console session to the VM. You might need to reset the VM for the “Press any key to boot from CD or DVD” prompt if you’re not fast enough.
  • While running through the basic installation tasks (Selecting Pro or Enterprise, accepting License Terms, and formatting the disk), the script will pause until you press Enter when you’re at the region selection screen. At this moment, the VM will mount the intune.iso file.
  • When the ISO is mounted, you can press Shift-F10, switch to d:\, run autopilot.cmd, register the VM as an Autopilot device, and shut down the VM when ready.

You will then have a VM ready for Endpoint Manager testing 🙂

Preparations

Azure App Registration

In a previous blog post (here), I already covered this, but the steps are as follows:

  • Go to App Registrations
  • Select New Registration
  • Enter “Autopilot Registration” as the name, and select Register.
  • Select API Permissions on the left side and select Add a permission
  • Select Microsoft Graph
  • Select Application permissions
  • Search for DeviceManagementServiceConfig.ReadWrite.All, select the checkbox and select Add Permissions.
  • Select Grant admin consent for xxxxx.onmicrosoft.com and select Yes
  • Select Certificates & secrets on the left side and select New client secret
  • Enter “Autopilot Registration Secret” as a description. For example, select how long the client secret should be valid (I selected 24 months) and click Add
  • Select the copy icon behind the value column and copy it somewhere safely (A password manager or database)
  • Select Overview on the left side, copy the Application (client) ID plus the Directory (tenant) ID, and save them with the value you copied in the previous step.

Intune Script ISO

During the deployment, the script will dismount the Windows installation ISO and mount an Intune.ISO file which contains the two scripts below. The steps for adding this to an ISO file are:

  • Create an autopilot.cmd in a temporary directory containing this command line:
    powershell.exe -executionpolicy bypass -file .\autopilot.ps1
  • Create an autopilot.ps1 in the same temporary directory containing the command lines below, and replace XXX with the values from the previous chapter in which you registered the Azure App:
    Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Confirm:$false -Force:$true
    Install-Script get-windowsautopilotinfo -Confirm:$false -Force:$true
    get-windowsautopilotinfo -Online -TenantId xxxx -AppId xxxx -AppSecret xxxx
    shutdown.exe /s /t 10
  • Download and install the AnyToISO program (Direct Link). The free version works with files not bigger than a regular CD size (870 MB). In our case, that’s not a problem. We only need 2Kb 🙂
  • Start the AnyToIso program after installation and follow these steps:
    Select the Folder to ISO tab
    Select Browse for Folder and select your temporary folder where the .cmd and .ps1 are saved
    select Choose ISO and browse and enter a destination .iso filename
    Select Make ISO

Running the script

Below are the steps you will see when running the script (Run it as Administrator). Before starting the script, please configure the two settings at the top of the script to your file locations:

#ISO Paths
$ISOPath = 'D:\ISO'
$IntuneISO = 'D:\ISO\intune.iso'

Enter the desired VM Name, Cores, RAM, and HDD size:

Hyper-V Role is installed, continuing...
Please enter the name of the VM to be created, for example W11Intune: PowerShellisfun
Please enter the amount of cores, for example 2: 4
Enter Memory in Gb's, for example 4: 8
Enter HDD size in Gb's, for example 40: 40

Select the desired ISO file and click on Ok:

Select the desired VM Switch and click on Ok:

The VM will now be created and configured with all the settings, the Console connection will be launched, and you can start the initial installation steps until the VM is on the region settings screen.
Note: You might need to reset the VM if you’re not in time for the “Press any key to boot from CD or DVD” prompt.

Using C:\Data\Hyper-V\PowerShellisfun as Virtual Machine location...
Configuring settings on PowerShellisfun...
Starting VM PowerShellisfun, press Enter to continue when you are on the language selection screen after completing the inital setup steps.
Connecting to console now....
Press Enter to continue...:

When you have arrived at this screen:


You can switch back to your PowerShell prompt and press Enter to continue to see the next steps:

Press Shift-F10 on the console of VM PowerShellisfun, switch to d:\ and run d:\autopilot.cmd to upload hardware hash to Intune. The VM will shutdown when done!
Press Enter when the VM has shutdown to stop this script and disconnect the Intune ISO file from VM PowerShellisfun
Press Enter to continue...:

Switch back to your VM, press Shift-F10, switch to d:\, and run autopilot.cmd, registering the VM as an Autopilot device and shut down the VM when ready.

Switch to D:\ and run autopilot.cmd:

Wait for the device to be imported:

Import done, VM will shut down:

When the VM is shut down, switch back to the script and press Enter to eject the intune.iso and finish the script:

Ejecting Intune ISO file from VM PowerShellisfun
Done, the deployment took 0 hours, 19 minutes and 15 seconds

And you’re done. The Windows installation is done, including Autopilot registration 🙂
Note: Please wait 5 minutes to power the VM again. It does take a few minutes after registering Autopilot for it to be processed in Dynamic Groups attached to your Deployment profile.

The script

Below is the script, don’t forget to change the ISO variables to your locations 🙂

#Requires -RunAsAdministrator

#ISO Paths
$ISOPath = 'D:\ISO'
$IntuneISO = 'D:\ISO\intune.iso'

#Start a stopwatch to measure the deployment time
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()

#Detect if Hyper-V is installed
if ((Get-WindowsOptionalFeature -FeatureName Microsoft-Hyper-V-All -Online).State -ne 'Enabled') {
    Write-Warning ("Hyper-V Role and/or required PowerShell module is not installed, please install before running this script...")
}
else {
    Write-host ("Hyper-V Role is installed, continuing...") -ForegroundColor Green
}


#Set VM Parameters
$VMname = Read-Host 'Please enter the name of the VM to be created, for example W11Intune'
if ((Get-VM -Name $VMname -ErrorAction SilentlyContinue).count -ge 1) {
    Write-Warning ("VM {0} already exists on this system, aborting..." -f $VMname)
    return
}

$VMCores = Read-Host 'Please enter the amount of cores, for example 2'
[int64]$VMRAM = 1GB * (read-host "Enter Memory in Gb's, for example 4")
[int64]$VMDISK = 1GB * (read-host "Enter HDD size in Gb's, for example 40")
$VMdir = (get-vmhost).VirtualMachinePath + $VMname

$ISO = Get-Childitem $ISOPath *.ISO | Out-GridView -OutputMode Single -Title 'Please select the ISO from the list and click OK' 
if (($ISO.FullName).Count -ne '1') {
    Write-Warning ("No ISO, script aborted...")
    return
}

$SwitchName = Get-VMSwitch | Out-GridView -OutputMode Single -Title 'Please select the VM Switch and click OK' | Select-Object Name
if (($SwitchName.Name).Count -ne '1') {
    Write-Warning ("No Virtual Switch selected, script aborted...")
    return
}

#Create VM directory
try {
    New-Item -ItemType Directory -Path $VMdir -Force:$true -ErrorAction SilentlyContinue | Out-Null
}
catch {
    Write-Warning ("Couldn't create {0} folder, please check VM Name for illegal characters or permissions on folder..." -f $VMdir)
    return
}
finally {
    if (test-path -Path $VMdir -ErrorAction SilentlyContinue) { 
        Write-Host ("Using {0} as Virtual Machine location..." -f $VMdir) -ForegroundColor Green
    }
}

#Create VM with the specified values
try {
    New-VM -Name $VMname `
    -SwitchName $SwitchName.Name `
    -Path $VMdir `
    -Generation 2 `
    -Confirm:$false `
    -NewVHDPath "$($vmdir)\$($VMname).vhdx" `
    -NewVHDSizeBytes ([math]::Round($vmdisk * 1024) / 1KB) `
    -ErrorAction Stop `
    | Out-Null  
}
catch {
    Write-Warning ("Error creating {0}, please check logs and make sure {0} doesn't already exist..." -f $VMname)
    return
}
finally {
    if (Get-VM -Name $VMname -ErrorAction SilentlyContinue | Out-Null) {
        write-host ("Created {0})..." -f $VMname) -ForegroundColor Green
    }
}

#Configure settings on the VM, CPU/Memory/Disk/BootOrder/TPM/Checkpoints
try {
    Write-Host ("Configuring settings on {0}..." -f $VMname) -ForegroundColor Green
    
    #VM Settings
    Set-VM -name $VMname `
        -ProcessorCount $VMCores `
        -StaticMemory `
        -MemoryStartupBytes $VMRAM `
        -CheckpointType ProductionOnly `
        -AutomaticCheckpointsEnabled:$false `
        -ErrorAction SilentlyContinue `
    | Out-Null 
    
    #Add Harddisk
    Add-VMHardDiskDrive -VMName $VMname -Path "$($vmdir)\$($VMname).vhdx" -ControllerType SCSI -ErrorAction SilentlyContinue | Out-Null
    
    #Add DVD with iso and set it as bootdevice
    Add-VMDvdDrive -VMName $VMName -Path $ISO.FullName -Passthru -ErrorAction SilentlyContinue | Out-Null
    $DVD = Get-VMDvdDrive -VMName $VMname
    $VMHD = Get-VMHardDiskDrive -VMName $VMname
    Set-VMFirmware -VMName $VMName -FirstBootDevice $VMHD
    Set-VMFirmware -VMName $VMName -FirstBootDevice $DVD
    Set-VMFirmware -VMName $VMname -EnableSecureBoot:On

    #Enable TPM and secure boot
    $owner = Get-HgsGuardian UntrustedGuardian
    $kp = New-HgsKeyProtector -Owner $owner -AllowUntrustedRoot
    Set-VMKeyProtector -VMName $VMname -KeyProtector $kp.RawData
    Enable-VMTPM -VMName $VMname 

    #Enable all integration services
    Enable-VMIntegrationService -VMName $VMname -Name 'Guest Service Interface' , 'Heartbeat', 'Key-Value Pair Exchange', 'Shutdown', 'Time Synchronization', 'VSS'
    
}
catch {
    Write-Warning ("Error setting VM parameters, check settings of VM {0} ..." -f $VMname)
    return
}

#Start VM and wait until VM is at language selection screen
Write-Host ("Starting VM {0}, press Enter to continue when you are on the language selection screen after completing the inital setup steps. `nConnecting to console now...." -f $VMname) -ForegroundColor Green
Start-VM -VMName $VMname
vmconnect.exe localhost $VMName
Pause

#Add Intune ISO
Set-VMDvdDrive -VMName $VMname -Path $IntuneISO
Write-Host ("Press Shift-F10 on the console of VM {0}, switch to d:\ and run d:\autopilot.cmd to upload hardware hash to Intune. The VM will shutdown when done!" -f $VMname) -ForegroundColor Green
Write-Host ("Press Enter when the VM has shutdown to stop this script and disconnect the Intune ISO file from VM {0}" -f $VMname) -ForegroundColor Green
pause
Write-Host ("Ejecting Intune ISO file from VM {0}" -f $VMname) -ForegroundColor Green
Set-VMDvdDrive -VMName $VMname -Path $null

#The end, stop stopwatch and display the time that it took to deploy
$stopwatch.Stop()
Write-Host "Done, the deployment took $($stopwatch.Elapsed.Hours) hours, $($stopwatch.Elapsed.Minutes) minutes and $($stopwatch.Elapsed.Seconds) seconds" -ForegroundColor Green

Download the script(s) from GitHub here

3 thoughts on “Deploy a Hyper-V VM and register it for Autopilot automatically using PowerShell

  1. Pingback: Blogpost – Create Hyper-V VM and enroll it in Autopilot automatically – 247 TECH

  2. Pingback: Endpoint Manager Newsletter – 26th August 2022 – Andrew Taylor

  3. Pingback: PowerShell is fun :) Overview of 2022 posts

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.