Hyper-V | Assign Graphics Card to Virtual Machine

Table of Contents
Intro
A while back I wanted to see how I could get a graphics card installed in my Hyper-V Server 2019 Core host to map to a VM of my choosing. I also wanted to test emulating the game Jak and Daxter: The Precursor Legacy (OpenGOAL port) in the VM and also use an Xbox controller connected to my laptop by USB which would be redirected into the RDP session to the VM with the graphics card mapped.
Initial Research
The graphics card of choice for this was a NVIDIA Quadro K2200. The graphics card shows up via Device Manager when remote to my (Hyper-V 2019 Core) server. Device Manager is usable by installing Server Core App Compatibility Feature on Demand (FOD) on the server.
This will be very helpful for this guide. Initial research led to the following article by Microsoft, Deploy graphics devices using RemoteFX vGPU. Which no longer is accurate as it has a security vulnerability.
Additionally, mapping a USB device such as a Xbox controller to a VM worked with the following article, Introducing Microsoft RemoteFX USB Redirection: Part 1. This actually worked just fine, and the controller worked in the VM no problem. Now I just needed to get the graphics card working.
Discrete Device Assignment (DDA)
So, to do this we need to use something called Discrete Device Assignment (DDA). You can read the following article for more information, Deploy graphics devices using Discrete Device Assignment. Essentially just follow the article and run a few scripts on the Hyper-V server.
Microsoft explains how to do the steps individually, but at the end they include a script which does it all and is geared for NVIDIA cards. You will need to edit the scripts for your VM name and hardware.
Warning
If the GPU is physically disconnected from the server/host before it’s dismounted from the VM, the VM will fail to start with an error. What I have done below with the help of ChatGPT is have the location path of the GPU placed in a text file. In the case where a GPU fails, you should be able to edit the $LocationPath variable in the dismount script with that value to edit the VM in hope of avoiding not being able to boot the VM.
Script | Mounting a GPU to a VM
# Define Variables
$VMName = "WR-CLI-VM-MPS"
$LogPath = "C:\Logs"
# Ensure the log directory exists
if (!(Test-Path -Path $LogPath)) {
New-Item -ItemType Directory -Path $LogPath -Force | Out-Null
}
$OutputFile = "$LogPath\discrete-device-assignment-mount.txt"
# Function to log and output messages
function Log-Message {
param ($Message)
Write-Output $Message | Tee-Object -FilePath $OutputFile -Append
}
# Configure VM for Discrete Device Assignment
Set-VM -Name $VMName -AutomaticStopAction TurnOff
Set-VM -GuestControlledCacheTypes $True -VMName $VMName
Set-VM -LowMemoryMappedIoSpace 3GB -VMName $VMName
Set-VM -HighMemoryMappedIoSpace 33280MB -VMName $VMName
# Get all NVIDIA Display Devices
$GPUDevices = Get-PnpDevice -PresentOnly | Where-Object {
$_.Class -eq "Display" -and $_.Manufacturer -like "NVIDIA"
}
if ($GPUDevices.Count -eq 0) {
Log-Message "No NVIDIA GPU found for passthrough. Exiting."
exit
}
# Select the first available NVIDIA GPU and retrieve its location path
$SelectedGPU = $GPUDevices[0]
$LocationPath = ($SelectedGPU | Get-PnpDeviceProperty -KeyName 'DEVPKEY_Device_LocationPaths').Data[0]
# Log selected GPU information
Log-Message "Selected GPU: $($SelectedGPU.FriendlyName)"
Log-Message "Location Path: $LocationPath"
# Disable GPU on Host
Disable-PnpDevice -InstanceId $SelectedGPU.InstanceId -Confirm:$False
# Dismount GPU from Host
Dismount-VMHostAssignableDevice -Force -LocationPath $LocationPath
# Assign GPU to VM
Add-VMAssignableDevice -LocationPath $LocationPath -VMName $VMName
# Final Output
Log-Message "GPU successfully assigned to $VMName."
Script | Removing a GPU from a VM and Returning It Back to The Host
# Define Variables
$VMName = "WR-CLI-VM-MPS"
$LogPath = "C:\Logs"
# Ensure log directory exists
if (!(Test-Path -Path $LogPath)) {
New-Item -ItemType Directory -Path $LogPath -Force | Out-Null
}
$OutputFile = "$LogPath\device-reassignment-log.txt"
# Function to log messages
function Log-Message {
param ($Message)
Write-Output $Message | Tee-Object -FilePath $OutputFile -Append
}
# Enumerate all PnP Devices and find NVIDIA GPUs
$GPUDevices = Get-PnpDevice | Where-Object {
$_.Class -eq "Display" -and $_.Manufacturer -like "NVIDIA"
}
if ($GPUDevices.Count -eq 0) {
Log-Message "No NVIDIA GPU found for reattachment. Exiting."
exit
}
# Select the first available NVIDIA GPU and retrieve its location path
$SelectedGPU = $GPUDevices[0]
$LocationPath = ($SelectedGPU | Get-PnpDeviceProperty -KeyName 'DEVPKEY_Device_LocationPaths').Data[0]
# Log selected GPU information
Log-Message "Selected GPU: $($SelectedGPU.FriendlyName)"
Log-Message "Location Path: $LocationPath"
# Remove the device from the VM
try {
Remove-VMAssignableDevice -LocationPath $LocationPath -VMName $VMName -ErrorAction Stop
Log-Message "Successfully removed GPU from VM: $VMName."
} catch {
Log-Message "ERROR: Failed to remove GPU from VM: $_"
exit
}
# Mount the device back to the host
try {
Mount-VMHostAssignableDevice -LocationPath $LocationPath -ErrorAction Stop
Log-Message "Successfully mounted GPU back to the host."
} catch {
Log-Message "ERROR: Failed to mount GPU back to the host: $_"
exit
}
# Enable the GPU on the host
try {
Enable-PnpDevice -InstanceId $SelectedGPU.InstanceId -Confirm:$False -ErrorAction Stop
Log-Message "Successfully enabled GPU on the host."
} catch {
Log-Message "ERROR: Failed to enable GPU on the host: $_"
exit
}
# Final Output
Log-Message "GPU reattached and enabled successfully."
Conclusion
Once mapped to the VM you can run Windows Update or install the driver manually. The conclusion to my test was that the game ran just fine but the display lag with RDP session made playing impossible. It was a fun little test, but don’t waste your time ever trying to play a game over an RDP connection. Hopefully this guide will help others out there looking to support some actual business applications.

My name is Dex Sandel, author at WinReflection.
I am a Christian, conservative, and truth seeker that is not afraid to be vocal on important or controversial issues. Unfortunately, when a person has that kind of courage, the enemy tries to send haters and wolves in sheep’s clothing their way to damage their life in various ways. There are many rewards earned in Heaven for those that refuse to give up and fight the good fight. There’s more to life than the worldly status quo, and that’s why many are sad and depressed today, they’re suffocating. Truth and purpose can bring fresh air into one’s life and that’s my mission.
The best is yet to come, and nothing can stop what’s coming!
John 3:16: For God so loved the world that he gave his one and only Son, that whoever believes in him shall not perish but have eternal life.
Leave a Reply
Want to join the discussion?Feel free to contribute!