Skip to content

Instantly share code, notes, and snippets.

@neggles
Last active November 4, 2025 20:46
Show Gist options
  • Select an option

  • Save neggles/e35793da476095beac716c16ffba1d23 to your computer and use it in GitHub Desktop.

Select an option

Save neggles/e35793da476095beac716c16ffba1d23 to your computer and use it in GitHub Desktop.

Revisions

  1. neggles revised this gist Jul 5, 2022. 1 changed file with 5 additions and 2 deletions.
    7 changes: 5 additions & 2 deletions New-GPUPDriverPackage.ps1
    Original file line number Diff line number Diff line change
    @@ -178,9 +178,12 @@ process {
    Write-Verbose -Message ('Device SystemDriver:')
    ($SystemDriver | Format-Table -AutoSize | Out-String).Trim().Split("`n") | ForEach-Object -Process { Write-Verbose -Message (' {0}' -f $_) }

    Write-Verbose -Message ('SystemDriver main/anchor file: {0}' -f $SystemDriver.PathName)
    $DriverStoreFolder = Get-Item -Path (Split-Path -Path $SystemDriver.PathName -Parent)
    Write-Verbose -Message ('Device DriverStoreFolder:')
    Write-Verbose -Message (' - Driver store folder: {0}' -f $DriverStoreFolder.FullName)
    while ((Get-Item (Split-Path $DriverStoreFolder)).Name -notlike 'FileRepository') {
    $DriverStoreFolder = Get-Item -Path (Split-Path -Path $DriverStoreFolder -Parent)
    }
    Write-Verbose -Message ('Device DriverStoreFolder: {0}' -f $DriverStoreFolder.FullName)

    Write-Output -InputObject ('Found package {0}, copying DriverStore folder {1} to temporary directory' -f $PnPSignedDriver.InfName, (Split-Path $DriverStoreFolder -Leaf))
    $TempDriverStore = ('{0}/System32/HostDriverStore/FileRepository/{1}' -f $fTempFolder, $DriverStoreFolder.Name)
  2. neggles revised this gist Jun 28, 2022. 1 changed file with 181 additions and 164 deletions.
    345 changes: 181 additions & 164 deletions New-GPUPDriverPackage.ps1
    Original file line number Diff line number Diff line change
    @@ -20,184 +20,201 @@
    .FUNCTIONALITY
    Creates a guest driver package.
    #>
    # commented out so this functions as a script instead of a function
    #function New-GPUPDriverPackage {
    [CmdletBinding(
    SupportsShouldProcess = $true,
    PositionalBinding = $true,
    DefaultParameterSetName = 'NoPathProvided',
    HelpUri = 'http://www.microsoft.com/',
    ConfirmImpact = 'Low')]
    [Alias()]
    [OutputType([String])]
    Param (
    # Path to output directory.
    # If no file name is specified the filename will be GPUPDriverPackage-YYYYMMMDD.zip
    [Parameter(
    Mandatory = $false,
    ParameterSetName = 'PathProvided',
    HelpMessage = "Path to one or more locations.")]
    [Alias("PSPath", "Path")]
    [ValidateNotNullOrEmpty()]
    [string]
    $DestinationPath,

    # Device friendly name filter.
    # Only devices whose friendly names contain the supplied string will be processed
    [Parameter(
    Mandatory = $false,
    HelpMessage = "Only add drivers for devices whose friendly names contain the supplied string.")]
    [ValidateNotNullOrEmpty]
    [String]
    $Filter
    )

    process {
    try {
    # make me a temporary folder, and assemble the structure
    $fTempFolder = Join-Path -Path $Env:TEMP -ChildPath "GPUPDriverPackage"

    [CmdletBinding(
    SupportsShouldProcess = $true,
    PositionalBinding = $true,
    DefaultParameterSetName = 'NoPathProvided',
    HelpUri = 'http://www.microsoft.com/',
    ConfirmImpact = 'Low')]
    [Alias()]
    [OutputType([String])]
    Param (
    # Path to output directory.
    # If no file name is specified the filename will be GPUPDriverPackage-YYYYMMMDD.zip
    [Parameter(
    Mandatory = $false,
    ParameterSetName = 'PathProvided',
    HelpMessage = "Path to one or more locations.")]
    [Alias("PSPath", "Path")]
    [ValidateNotNullOrEmpty()]
    [string]
    $DestinationPath,

    # Device friendly name filter.
    # Only devices whose friendly names contain the supplied string will be processed
    [Parameter(
    Mandatory = $false,
    HelpMessage = "Only add drivers for devices whose friendly names contain the supplied string.")]
    [ValidateNotNullOrEmpty]
    [String]
    $Filter
    )

    process {
    try {
    # make me a temporary folder, and assemble the structure
    $fTempFolder = Join-Path -Path $Env:TEMP -ChildPath "GPUPDriverPackage"
    (New-Item -ItemType Directory -Path "$fTempFolder/System32/HostDriverStore/FileRepository" -Force -ErrorAction SilentlyContinue | Out-Null)
    (New-Item -ItemType Directory -Path "$fTempFolder/SysWOW64" -Force -ErrorAction SilentlyContinue | Out-Null)

    # Set default archive name
    $ArchiveName = ('GPUPDriverPackage-{0}.zip' -f $(Get-Date -UFormat '+%Y%b%d'))

    # Check if DestinationPath has been provided
    if ($PSCmdlet.ParameterSetName -eq "PathProvided") {
    switch ($DestinationPath) {
    { (Split-Path -Path $_ -Leaf) -match '(\.zip)' } {
    # Path we've been provided is a full file path, so we'll just use it
    $ArchiveFolder = Split-Path -Path $DestinationPath -Parent
    $ArchiveName = Split-Path -Path $DestinationPath -Leaf
    break
    }
    { Test-Path -Path $_ -PathType Container } {
    # Path exists and is a directory, so we place our file in it with the default name.
    $ArchiveFolder = $DestinationPath
    break
    }
    Default {
    # Path doesn't end in .zip and doesn't exist, so we're going to assume it's a directory and place the file in it with the default name.
    $ArchiveFolder = $DestinationPath
    # Set default archive name
    $ArchiveName = ('GPUPDriverPackage-{0}.zip' -f $(Get-Date -UFormat '+%Y%b%d'))

    # Check if DestinationPath has been provided
    if ($PSCmdlet.ParameterSetName -eq "PathProvided") {
    switch ($DestinationPath) {
    { (Split-Path -Path $_ -Leaf) -match '(\.zip)' } {
    # Path we've been provided is a full file path, so we'll just use it
    $ArchiveFolder = Split-Path -Path $DestinationPath -Parent
    $ArchiveName = Split-Path -Path $DestinationPath -Leaf
    break
    }
    { Test-Path -Path $_ -PathType Container } {
    # Path exists and is a directory, so we place our file in it with the default name.
    $ArchiveFolder = $DestinationPath
    break
    }
    Default {
    # Path doesn't end in .zip and doesn't exist, so we're going to assume it's a directory and place the file in it with the default name.
    $ArchiveFolder = $DestinationPath
    (New-Item -ItemType Directory -Path $ArchiveFolder -Force -ErrorAction SilentlyContinue | Out-Null)
    break
    }
    break
    }
    } else {
    # if DestinationPath not supplied, use current directory and default name
    $ArchiveFolder = (Get-Location).Path
    }
    } else {
    # if DestinationPath not supplied, use current directory and default name
    $ArchiveFolder = (Get-Location).Path
    }

    # just double check that one
    if (-not $ArchiveFolder) { $ArchiveFolder = (Get-Location).Path }

    Write-Output -InputObject ('Creating GPU-P driver package for host {0}' -f $Env:COMPUTERNAME)
    Write-Output -InputObject ('Destination path: {0}' -f (Join-Path -Path $ArchiveFolder -ChildPath $ArchiveName))

    <#
    Determine which cmdlet we should use to gather the list of GPU-P capable GPUs.
    On Windows builds before Server 2022/21H2, the cmdlet is 'Get-VMPartitionableGpu'
    On later builds, it's 'Get-VMHostPartitionableGpu', and the old cmdlet just prints an error.
    So, we check if Get-VMHostPartitionableGpu is a valid cmdlet to determine whether we should use it.
    #>
    Write-Output -InputObject "Getting all GPU-P capable GPUs in the current system..."
    if (Get-Command -Name 'Get-VMHostPartitionableGpu' -ErrorAction SilentlyContinue) {
    Write-Verbose -Message 'Using new Get-VMHostPartitionableGpu cmdlet'
    $PVCapableGPUs = Get-VMHostPartitionableGpu
    } else {
    Write-Verbose -Message 'Using old Get-VMPartitionableGpu cmdlet'
    $PVCapableGPUs = Get-VMPartitionableGpu
    }
    # just double check that one
    if (-not $ArchiveFolder) { $ArchiveFolder = (Get-Location).Path }

    Write-Output -InputObject ('Creating GPU-P driver package for host {0}' -f $Env:COMPUTERNAME)
    Write-Output -InputObject ('Destination path: {0}' -f (Join-Path -Path $ArchiveFolder -ChildPath $ArchiveName))

    <#
    Determine which cmdlet we should use to gather the list of GPU-P capable GPUs.
    On Windows builds before Server 2022/21H2, the cmdlet is 'Get-VMPartitionableGpu'
    On later builds, it's 'Get-VMHostPartitionableGpu', and the old cmdlet just prints an error.
    So, we check if Get-VMHostPartitionableGpu is a valid cmdlet to determine whether we should use it.
    #>
    Write-Output -InputObject "Getting all GPU-P capable GPUs in the current system..."
    if (Get-Command -Name 'Get-VMHostPartitionableGpu' -ErrorAction SilentlyContinue) {
    Write-Verbose -Message 'Using new Get-VMHostPartitionableGpu cmdlet'
    $PVCapableGPUs = Get-VMHostPartitionableGpu
    } else {
    Write-Verbose -Message 'Using old Get-VMPartitionableGpu cmdlet'
    $PVCapableGPUs = Get-VMPartitionableGpu
    }

    # if we found no GPU-P capable GPUs, throw an exception
    if ($PVCapableGPUs.Count -lt 1) {
    throw [System.Management.Automation.ItemNotFoundException]::new('Did not find any GPU-P capable GPUs in this system.')
    } elseif ($PvGPUs.Count -gt 1) {
    Write-Warning -Message (
    # if we found no GPU-P capable GPUs, throw an exception
    if ($PVCapableGPUs.Count -lt 1) {
    throw [System.Management.Automation.ItemNotFoundException]::new('Did not find any GPU-P capable GPUs in this system.')
    } elseif ($PvGPUs.Count -gt 1) {
    Write-Warning -Message (
    ("You have {0} GPU-P capable GPUs in this system. `n" -f $PvGPUs.Count) +
    " At present, there is no way to control which one is assigned to a given VM.`n" +
    " Unless one of the available GPUs is an intel IGP, it is highly recommended`n" +
    " that you disable the GPU(s) you do not wish to use.`n")
    $choices = '&Yes', '&No'
    $question = 'Do you wish to proceed without disabling the extra GPU(s)?'
    if ($Host.UI.PromptForChoice('', $question, $choices, 1) -eq 1) {
    throw [System.Management.Automation.ActionPreferenceStopException]::new('User requested to cancel.')
    }
    " At present, there is no way to control which one is assigned to a given VM.`n" +
    " Unless one of the available GPUs is an intel IGP, it is highly recommended`n" +
    " that you disable the GPU(s) you do not wish to use.`n")
    $choices = '&Yes', '&No'
    $question = 'Do you wish to proceed without disabling the extra GPU(s)?'
    if ($Host.UI.PromptForChoice('', $question, $choices, 1) -eq 1) {
    throw [System.Management.Automation.ActionPreferenceStopException]::new('User requested to cancel.')
    }
    }

    # Map each PVCapableGPU to the corresponding PnPDevice. Regex (mostly) extracts the InstanceId from the VMPartitionableGpu 'name' property.
    Write-Output -InputObject ('Mapping GPU-P capable GPUs to their corresponding PnPDevice objects...')
    $InstanceExpr = [regex]::New('^\\\\\?\\(.+)#.*$')
    $TargetGPUs = $PVCapableGPUs.Name | ForEach-Object -Process {
    # I'm not proud of this dirty regex trick, but it works.
    Get-PnpDevice -InstanceId $InstanceExpr.Replace($_, '$1').Replace('#', '\')
    }

    # Map each PVCapableGPU to the corresponding PnPDevice. Regex (mostly) extracts the InstanceId from the VMPartitionableGpu 'name' property.
    Write-Output -InputObject ('Mapping GPU-P capable GPUs to their corresponding PnPDevice objects...')
    $InstanceExpr = [regex]::New('^\\\\\?\\(.+)#.*$')
    $TargetGPUs = $PVCapableGPUs.Name | ForEach-Object -Process {
    # I'm not proud of this dirty regex trick, but it works.
    Get-PnpDevice -InstanceId $InstanceExpr.Replace($_, '$1').Replace('#', '\')
    }
    # OK, now that we have some actual device names, we can filter them if we've been asked to
    if ($null -ne $Filter) {
    Write-Output -InputObject ('Applying filter "{0}" to device list...' -f $Filter)
    $TargetGPUs = $TargetGPUs | Where-Object { $_.FriendlyName -like ('*{0}*' -f $Filter) }
    }
    Write-Output -InputObject ('Will create driver package for {0} GPUs:' -f $TargetGPUs.Count)
    $TargetGPUs.FriendlyName | ForEach-Object { Write-Output -InputObject (' - {0}' -f $_) }
    } catch { throw $PSItem }

    # OK, now that we have some actual device names, we can filter them if we've been asked to
    if ($null -ne $Filter) {
    Write-Output -InputObject ('Applying filter "{0}" to device list...' -f $Filter)
    $TargetGPUs = $TargetGPUs | Where-Object { $_.FriendlyName -like ('*{0}*' -f $Filter) }
    }
    Write-Output -InputObject ('Will create driver package for {0} GPUs:' -f $TargetGPUs.Count)
    $TargetGPUs.FriendlyName | ForEach-Object { Write-Output -InputObject (' - {0}' -f $_) }
    } catch { throw $PSItem }

    # Last chance to turn back, traveler. Are you sure?
    if ($pscmdlet.ShouldProcess("Driver Package", "Create")) {
    try {
    Write-Output -InputObject ('The next few steps may take some time, depending on how many devices & driver packages are installed.')
    Write-Output -InputObject ('If the script appears hung, please give it a few minutes to complete before terminating.')
    # Get display class devices
    Write-Output -InputObject ('Gathering display device CIM objects...')
    $PnPEntities = Get-CimInstance -ClassName 'Win32_PnPEntity' | Where-Object { $_.Class -like 'Display' }
    # Get display class drivers
    Write-Output -InputObject ('Gathering display driver CIM objects...')
    $PnPSignedDrivers = Get-CimInstance -ClassName 'Win32_PnPSignedDriver' -Filter "DeviceClass = 'DISPLAY'"
    # next we have to get every PnPSignedDriverCIMDataFile, because Get-CimAssociatedInstance doesn't wanna play ball
    Write-Output -InputObject ('Gathering all driver file CIM objects... (this is the slow one. Blame Microsoft.)') # or me not understanding CIM i guess?
    $SignedDriverFiles = Get-CimInstance -ClassName 'Win32_PNPSignedDriverCIMDataFile' # -Verbose
    Write-Output -InputObject ('CIM objects gathered.')

    foreach ($GPU in $TargetGPUs) {
    Write-Output -InputObject ('Getting driver package for {0}' -f $GPU.FriendlyName)
    $PnPEntity = $PnPEntities | Where-Object { $_.InstanceId -eq $GPU.InstanceId }[0]
    $PnPSignedDriver = $PnPSignedDrivers | Where-Object { $_.DeviceId -eq $GPU.InstanceId }
    $SystemDriver = Get-CimAssociatedInstance -InputObject $PnPEntity -Association Win32_SystemDriverPNPEntity
    $DriverStoreFolder = Split-Path -Path $SystemDriver.PathName -Parent
    Write-Output -InputObject ('Found package {0}, copying DriverStore folder {1} to temporary directory' -f $PnPSignedDriver.InfName, (Split-Path $DriverStoreFolder -Leaf))
    Copy-Item -Path $DriverStoreFolder -Destination "$fTempFolder/System32/HostDriverStore/FileRepository/" -Recurse -Force

    # Get driver files from system32 etc and copy
    Write-Output -InputObject ('DriverStore folder copied, gathering files from System32 and SysWOW64')
    $DriverFiles = ($SignedDriverFiles | Where-Object { $_.Antecedent.DeviceID -like $GPU.DeviceID }).Dependent.Name
    $System32Files = $DriverFiles | Where-Object { (Split-Path -Path $_ -Parent) -like "$Env:SYSTEMROOT\System32" }
    $SysWOW64Files = $DriverFiles | Where-Object { (Split-Path -Path $_ -Parent) -like "$Env:SYSTEMROOT\SysWOW64" }

    Write-Output -InputObject ('Found {0} files:' -f ($System32Files.Count + $SysWOW64Files.Count))
    $System32Files | ForEach-Object { Write-Output -InputObject (' - System32\{0}' -f (Split-Path -Path $_ -Leaf)) }
    $SysWOW64Files | ForEach-Object { Write-Output -InputObject (' - SysWOW64\{0}' -f (Split-Path -Path $_ -Leaf)) }

    Write-Output -InputObject ('Copying to temporary directory...')
    Copy-Item -Path $System32Files -Destination "$fTempFolder/System32/"
    Copy-Item -Path $SysWOW64Files -Destination "$fTempFolder/SysWOW64/"

    Write-Output -InputObject ('Finished gathering files for {0}' -f $GPU.FriendlyName)
    # Last chance to turn back, traveler. Are you sure?
    if ($pscmdlet.ShouldProcess("Driver Package", "Create")) {
    try {
    Write-Output -InputObject ('The next few steps may take some time, depending on how many devices & driver packages are installed.')
    Write-Output -InputObject ('If the script appears hung, please give it a few minutes to complete before terminating.')
    # Get display class devices
    Write-Output -InputObject ('Gathering display device CIM objects...')
    $PnPEntities = Get-CimInstance -ClassName 'Win32_PnPEntity' | Where-Object { $_.Class -like 'Display' }
    Write-Verbose -Message ('Found {0} display devices' -f $PnPEntities.Count)
    ($PnPEntities | Format-Table -AutoSize | Out-String).Trim().Split("`n") | ForEach-Object -Process { Write-Verbose -Message (' {0}' -f $_) }

    # Get display class drivers
    Write-Output -InputObject ('Gathering display device driver CIM objects...')
    $PnPSignedDrivers = Get-CimInstance -ClassName 'Win32_PnPSignedDriver' -Filter "DeviceClass = 'DISPLAY'"
    Write-Verbose -Message ('Found {0} display device drivers' -f $PnPSignedDrivers.Count)
    $PnPSignedInfo = ($PnPSignedDrivers | Select-Object -Property DeviceName,DriverProviderName,InfName,DriverVersion,Description | Format-Table -AutoSize | Out-String).Trim().Split("`n")
    $PnPSignedInfo | ForEach-Object -Process { Write-Verbose -Message (' {0}' -f $_) }

    # next we have to get every PnPSignedDriverCIMDataFile, because Get-CimAssociatedInstance doesn't wanna play ball
    Write-Output -InputObject ('Gathering all driver file objects... (this is the slow one. Blame Microsoft.)') # or me not understanding CIM i guess?
    $SignedDriverFiles = Get-CimInstance -ClassName 'Win32_PNPSignedDriverCIMDataFile'
    Write-Output -InputObject ('Found {0} files across all system drivers.' -f $SignedDriverFiles.Count)

    foreach ($GPU in $TargetGPUs) {
    Write-Output -InputObject ('Getting driver package for {0}' -f $GPU.FriendlyName)
    $PnPEntity = $PnPEntities | Where-Object { $_.InstanceId -eq $GPU.InstanceId }[0]
    Write-Verbose -Message ('Device PnP Entity:')
    ($PnPEntity | Format-Table -AutoSize | Out-String).Trim().Split("`n") | ForEach-Object -Process { Write-Verbose -Message (' {0}' -f $_) }

    $PnPSignedDriver = $PnPSignedDrivers | Where-Object { $_.DeviceId -eq $GPU.InstanceId }
    Write-Verbose -Message ('Device PnPSignedDriver:')
    ($PnPSignedDriver | Format-Table -AutoSize | Out-String).Trim().Split("`n") | ForEach-Object -Process { Write-Verbose -Message (' {0}' -f $_) }

    $SystemDriver = Get-CimAssociatedInstance -InputObject $PnPEntity -Association Win32_SystemDriverPNPEntity
    Write-Verbose -Message ('Device SystemDriver:')
    ($SystemDriver | Format-Table -AutoSize | Out-String).Trim().Split("`n") | ForEach-Object -Process { Write-Verbose -Message (' {0}' -f $_) }

    $DriverStoreFolder = Get-Item -Path (Split-Path -Path $SystemDriver.PathName -Parent)
    Write-Verbose -Message ('Device DriverStoreFolder:')
    Write-Verbose -Message (' - Driver store folder: {0}' -f $DriverStoreFolder.FullName)

    Write-Output -InputObject ('Found package {0}, copying DriverStore folder {1} to temporary directory' -f $PnPSignedDriver.InfName, (Split-Path $DriverStoreFolder -Leaf))
    $TempDriverStore = ('{0}/System32/HostDriverStore/FileRepository/{1}' -f $fTempFolder, $DriverStoreFolder.Name)
    $DriverStoreFolder | Copy-Item -Destination $TempDriverStore -Recurse -Force
    Write-Output -InputObject ('Copied {0} of {1} files to temporary directory' -f (Get-ChildItem -Path $TempDriverStore -Recurse).Count, (Get-ChildItem -Path $DriverStoreFolder -Recurse).Count)

    # Get driver files from system32 etc and copy
    Write-Output -InputObject ('Gathering files from System32 and SysWOW64')
    $DriverFiles = ($SignedDriverFiles | Where-Object { $_.Antecedent.DeviceID -like $GPU.DeviceID }).Dependent.Name | Sort-Object
    $NonDriverStoreFiles = $DriverFiles.Where{$_ -notlike '*DriverStore*'}

    Write-Output -InputObject ('Found {0} files, copying to temporary directory...' -f $NonDriverStoreFiles.Count)
    $NonDriverStoreFiles | ForEach-Object -Process {
    $TargetPath = Join-Path -Path $fTempFolder -ChildPath $_.ToLower().Replace(('{0}\' -f $Env:SYSTEMROOT.ToLower()),'')
    # make sure the parent folder exists
    (New-Item -ItemType directory -Path (Split-Path -Path $TargetPath -Parent) -Force -ErrorAction SilentlyContinue | Out-Null)
    Write-Output -InputObject (' - {0} -> {1}' -f $_, $TargetPath)
    Copy-Item -Path $_ -Destination $TargetPath -Force -Recurse
    }
    Write-Output -InputObject ('All driver files have been collected, creating archive file')
    $Location = (Get-Location).Path
    Set-Location -Path (Split-Path -Path $fTempFolder -Parent)
    Compress-Archive -Path $fTempFolder -DestinationPath (Join-Path -Path $ArchiveFolder -ChildPath $ArchiveName) -CompressionLevel Fastest -Confirm:$false
    Set-Location -Path $Location
    Write-Output -InputObject ('GPU driver package has been created at path {0}\{1}' -f $ArchiveFolder, $ArchiveName)
    } catch {
    throw $PSItem
    } finally {
    Write-Output -InputObject ('Cleaning up temporary directory {0}' -f $fTempFolder)
    Remove-Item -Recurse -Force -Path $fTempFolder
    Write-Output -InputObject ('Finished gathering files for {0}' -f $GPU.FriendlyName)
    }
    Write-Output -InputObject ('All driver files have been collected, creating archive file')
    $Location = (Get-Location).Path
    Set-Location -Path (Split-Path -Path $fTempFolder -Parent)
    Compress-Archive -Path $fTempFolder -DestinationPath (Join-Path -Path $ArchiveFolder -ChildPath $ArchiveName) -CompressionLevel Fastest -Confirm:$false
    Set-Location -Path $Location
    Write-Output -InputObject ('GPU driver package has been created at path {0}\{1}' -f $ArchiveFolder, $ArchiveName)
    } catch {
    throw $PSItem
    } finally {
    Write-Output -InputObject ('Cleaning up temporary directory {0}' -f $fTempFolder)
    Remove-Item -Recurse -Force -Path $fTempFolder
    }
    Write-Output -InputObject ('Driver package generation complete.')
    Write-Output -InputObject ('Please copy it to your guest and extract the archive into C:\Windows\')
    }
    #} # see top
    Write-Output -InputObject ('Driver package generation complete.')
    Write-Output -InputObject ('Please copy it to your guest and extract the archive into C:\Windows\')
    }
  3. neggles revised this gist Dec 31, 2021. 1 changed file with 24 additions and 7 deletions.
    31 changes: 24 additions & 7 deletions New-GPUPVirtualMachine.ps1
    Original file line number Diff line number Diff line change
    @@ -1,20 +1,30 @@
    $Config = @{
    VMName = 'GPU-VM' # pick a NAME BRO
    VMName = 'GPU-VM' # Edit this to match your existing VM. If you don't have a VM with this name, it will be created.
    VMMemory = 8192MB # Set appropriately
    VMCores = 4 # likewise
    MinRsrc = 80000000 # We don't really know what these values do - my GPU reports 100,000,000 available units...
    MaxRsrc = 100000000 # I suspect in the current implementation they do nothing, but this is known to work - play around if you like!
    OptimalRsrc = 100000000
    }

    ### actual execution
    try {
    # Create VM if needed
    if (-not (Get-VM -Name $Config.VMName)) {

    # Get VM host capabilities
    $VMHost = Get-VMHost
    # Get highest VM config version available
    $VMVersion = $VMHost.SupportedVmVersions[$VMHost.SupportedVmVersions.Count - 1]

    # Get existing VM if it exists
    $VMObject = (Get-VM -Name $Config.VMName -ErrorAction SilentlyContinue)

    # Create VM if it doesn't already exist
    if (-not $VMObject) {
    $NewVM = @{
    Name = $Config.VMName
    MemoryStartupBytes = $Config.VMMemory
    Generation = 2
    Version = 9.2 # Latest available virtual hardware version
    Version = $VMVersion
    }
    New-VM @NewVM
    }
    @@ -48,12 +58,19 @@ try {
    MaxPartitionCompute = $Config.MaxRsrc
    OptimalPartitionCompute = $Config.OptimalRsrc
    }

    # Get adapter if it exists
    $VMAdapter = (Get-VMGpuPartitionAdapter -VMName $Config.VMName -ErrorAction SilentlyContinue)

    # Add adapter if not present, update if present
    if (-not (Get-VMGpuPartitionAdapter -VMName $Config.VMName)) {
    Add-VMGpuPartitionAdapter @GPUParams
    } else {
    if ($VMAdapter) {
    Set-VMGpuPartitionAdapter @GPUParams
    } else {
    Add-VMGpuPartitionAdapter @GPUParams
    }

    } catch {
    Write-Error "Something went wrong with creation. Error details below:"
    Write-Error $PSItem.ErrorDetails
    throw $PSItem
    }
  4. neggles revised this gist Sep 25, 2021. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion New-GPUPDriverPackage.ps1
    Original file line number Diff line number Diff line change
    @@ -85,7 +85,7 @@
    }
    } else {
    # if DestinationPath not supplied, use current directory and default name
    Default { $ArchiveFolder = (Get-Location).Path }
    $ArchiveFolder = (Get-Location).Path
    }

    # just double check that one
  5. neggles revised this gist Sep 5, 2021. 1 changed file with 31 additions and 22 deletions.
    53 changes: 31 additions & 22 deletions New-GPUPDriverPackage.ps1
    Original file line number Diff line number Diff line change
    @@ -22,17 +22,20 @@
    #>
    # commented out so this functions as a script instead of a function
    #function New-GPUPDriverPackage {
    [CmdletBinding(
    [CmdletBinding(
    SupportsShouldProcess = $true,
    PositionalBinding = $true,
    DefaultParameterSetName = 'NoPathProvided',
    HelpUri = 'http://www.microsoft.com/',
    ConfirmImpact = 'Low')]
    [Alias()]
    [OutputType([String])]
    Param (
    # Path to output directory.
    # If no file name is specified the filename will be GPUPDriverPackage-YYYYMMMDD.zip
    [Parameter(Mandatory = $false,
    [Parameter(
    Mandatory = $false,
    ParameterSetName = 'PathProvided',
    HelpMessage = "Path to one or more locations.")]
    [Alias("PSPath", "Path")]
    [ValidateNotNullOrEmpty()]
    @@ -41,7 +44,8 @@

    # Device friendly name filter.
    # Only devices whose friendly names contain the supplied string will be processed
    [Parameter(Mandatory = $false,
    [Parameter(
    Mandatory = $false,
    HelpMessage = "Only add drivers for devices whose friendly names contain the supplied string.")]
    [ValidateNotNullOrEmpty]
    [String]
    @@ -55,27 +59,32 @@
    (New-Item -ItemType Directory -Path "$fTempFolder/System32/HostDriverStore/FileRepository" -Force -ErrorAction SilentlyContinue | Out-Null)
    (New-Item -ItemType Directory -Path "$fTempFolder/SysWOW64" -Force -ErrorAction SilentlyContinue | Out-Null)

    # Do some processing on the DestinationPath
    # Set default archive name
    $ArchiveName = ('GPUPDriverPackage-{0}.zip' -f $(Get-Date -UFormat '+%Y%b%d'))
    switch ($DestinationPath) {
    # if path is a folder that exists, use the default name and just put it there
    { Test-Path -Path $_ -PathType Container -ErrorAction SilentlyContinue } {
    $ArchiveFolder = $DestinationPath
    break
    }
    # if path is a folder that does NOT exist, create it and put archive in it
    { Test-Path -Path $_ -PathType Container -IsValid -ErrorAction SilentlyContinue } {
    $ArchiveFolder = $DestinationPath
    (New-Item -ItemType Directory -Path $ArchiveFolder -Force -ErrorAction SilentlyContinue | Out-Null)
    break
    }
    # if path is a valid path with a file name ending in .zip, use that
    { Test-Path -Path $_ -PathType Leaf -IsValid -ErrorAction SilentlyContinue } {
    $ArchiveFolder = Split-Path -Path $DestinationPath -Parent
    $ArchiveName = Split-Path -Path $DestinationPath -Leaf
    break

    # Check if DestinationPath has been provided
    if ($PSCmdlet.ParameterSetName -eq "PathProvided") {
    switch ($DestinationPath) {
    { (Split-Path -Path $_ -Leaf) -match '(\.zip)' } {
    # Path we've been provided is a full file path, so we'll just use it
    $ArchiveFolder = Split-Path -Path $DestinationPath -Parent
    $ArchiveName = Split-Path -Path $DestinationPath -Leaf
    break
    }
    { Test-Path -Path $_ -PathType Container } {
    # Path exists and is a directory, so we place our file in it with the default name.
    $ArchiveFolder = $DestinationPath
    break
    }
    Default {
    # Path doesn't end in .zip and doesn't exist, so we're going to assume it's a directory and place the file in it with the default name.
    $ArchiveFolder = $DestinationPath
    (New-Item -ItemType Directory -Path $ArchiveFolder -Force -ErrorAction SilentlyContinue | Out-Null)
    break
    }
    }
    # if param not supplied, use current directory and default name
    } else {
    # if DestinationPath not supplied, use current directory and default name
    Default { $ArchiveFolder = (Get-Location).Path }
    }

  6. neggles revised this gist Sep 5, 2021. 1 changed file with 3 additions and 2 deletions.
    5 changes: 3 additions & 2 deletions New-GPUPDriverPackage.ps1
    Original file line number Diff line number Diff line change
    @@ -20,7 +20,8 @@
    .FUNCTIONALITY
    Creates a guest driver package.
    #>
    function New-GPUPDriverPackage {
    # commented out so this functions as a script instead of a function
    #function New-GPUPDriverPackage {
    [CmdletBinding(
    SupportsShouldProcess = $true,
    PositionalBinding = $true,
    @@ -190,4 +191,4 @@ function New-GPUPDriverPackage {
    Write-Output -InputObject ('Driver package generation complete.')
    Write-Output -InputObject ('Please copy it to your guest and extract the archive into C:\Windows\')
    }
    }
    #} # see top
  7. neggles revised this gist Sep 5, 2021. 1 changed file with 128 additions and 114 deletions.
    242 changes: 128 additions & 114 deletions New-GPUPDriverPackage.ps1
    Original file line number Diff line number Diff line change
    @@ -48,132 +48,146 @@ function New-GPUPDriverPackage {
    )

    process {
    # make me a temporary folder, and assemble the structure
    $fTempFolder = Join-Path -Path $Env:TEMP -ChildPath "GPUPDriverPackage"
    (New-Item -ItemType Directory -Path "$fTempFolder/System32/HostDriverStore/FileRepository" -Force -ErrorAction SilentlyContinue | Out-Null)
    (New-Item -ItemType Directory -Path "$fTempFolder/SysWOW64" -Force -ErrorAction SilentlyContinue | Out-Null)

    # Do some processing on the DestinationPath
    $ArchiveName = ('GPUPDriverPackage-{0}.zip' -f $(Get-Date -UFormat '+%Y%b%d'))
    switch ($DestinationPath) {
    # if path is a folder that exists, use the default name and just put it there
    { Test-Path -Path $PSItem -PathType Container -ErrorAction SilentlyContinue } {
    $ArchiveFolder = $DestinationPath
    break
    }
    # if path is a folder that does NOT exist, create it and put archive in it
    { Test-Path -Path $PSItem -PathType Container -IsValid -ErrorAction SilentlyContinue } {
    $ArchiveFolder = $DestinationPath
    (New-Item -ItemType Directory -Path $ArchiveFolder -Force -ErrorAction SilentlyContinue | Out-Null)
    break
    }
    # if path is a valid path with a file name ending in .zip, use that
    { Test-Path -Path $PSItem -PathType Leaf -IsValid -ErrorAction SilentlyContinue } {
    $ArchiveFolder = Split-Path -Path $DestinationPath -Parent
    $ArchiveName = Split-Path -Path $DestinationPath -Leaf
    break
    try {
    # make me a temporary folder, and assemble the structure
    $fTempFolder = Join-Path -Path $Env:TEMP -ChildPath "GPUPDriverPackage"
    (New-Item -ItemType Directory -Path "$fTempFolder/System32/HostDriverStore/FileRepository" -Force -ErrorAction SilentlyContinue | Out-Null)
    (New-Item -ItemType Directory -Path "$fTempFolder/SysWOW64" -Force -ErrorAction SilentlyContinue | Out-Null)

    # Do some processing on the DestinationPath
    $ArchiveName = ('GPUPDriverPackage-{0}.zip' -f $(Get-Date -UFormat '+%Y%b%d'))
    switch ($DestinationPath) {
    # if path is a folder that exists, use the default name and just put it there
    { Test-Path -Path $_ -PathType Container -ErrorAction SilentlyContinue } {
    $ArchiveFolder = $DestinationPath
    break
    }
    # if path is a folder that does NOT exist, create it and put archive in it
    { Test-Path -Path $_ -PathType Container -IsValid -ErrorAction SilentlyContinue } {
    $ArchiveFolder = $DestinationPath
    (New-Item -ItemType Directory -Path $ArchiveFolder -Force -ErrorAction SilentlyContinue | Out-Null)
    break
    }
    # if path is a valid path with a file name ending in .zip, use that
    { Test-Path -Path $_ -PathType Leaf -IsValid -ErrorAction SilentlyContinue } {
    $ArchiveFolder = Split-Path -Path $DestinationPath -Parent
    $ArchiveName = Split-Path -Path $DestinationPath -Leaf
    break
    }
    # if param not supplied, use current directory and default name
    Default { $ArchiveFolder = (Get-Location).Path }
    }
    # if param not supplied, use current directory and default name
    Default { $ArchiveFolder = (Get-Location).Path }
    }

    # just double check that one
    if (-not $ArchiveFolder) { $ArchiveFolder = (Get-Location).Path }

    Write-Output -InputObject ('Creating GPU-P driver package for host {0}' -f $Env:COMPUTERNAME)
    Write-Output -InputObject ('Destination path: {0}' -f (Join-Path -Path $ArchiveFolder -ChildPath $ArchiveName))

    Write-Output -InputObject "Getting all GPU-P capable GPUs in the current system..."
    # requires some care as the command name changed in Server 2022/W10 21H2
    try {
    $PVCapableGPUs = Get-VMPartitionableGpu
    } catch {
    $PVCapableGPUs = Get-VMHostPartitionableGpu
    }
    # just double check that one
    if (-not $ArchiveFolder) { $ArchiveFolder = (Get-Location).Path }

    Write-Output -InputObject ('Creating GPU-P driver package for host {0}' -f $Env:COMPUTERNAME)
    Write-Output -InputObject ('Destination path: {0}' -f (Join-Path -Path $ArchiveFolder -ChildPath $ArchiveName))

    <#
    Determine which cmdlet we should use to gather the list of GPU-P capable GPUs.
    On Windows builds before Server 2022/21H2, the cmdlet is 'Get-VMPartitionableGpu'
    On later builds, it's 'Get-VMHostPartitionableGpu', and the old cmdlet just prints an error.
    So, we check if Get-VMHostPartitionableGpu is a valid cmdlet to determine whether we should use it.
    #>
    Write-Output -InputObject "Getting all GPU-P capable GPUs in the current system..."
    if (Get-Command -Name 'Get-VMHostPartitionableGpu' -ErrorAction SilentlyContinue) {
    Write-Verbose -Message 'Using new Get-VMHostPartitionableGpu cmdlet'
    $PVCapableGPUs = Get-VMHostPartitionableGpu
    } else {
    Write-Verbose -Message 'Using old Get-VMPartitionableGpu cmdlet'
    $PVCapableGPUs = Get-VMPartitionableGpu
    }

    # if we found no GPU-P capable GPUs, throw an exception
    if ($PVCapableGPUs.Count -lt 1) {
    throw [System.Management.Automation.ItemNotFoundException]::new('Did not find any GPU-P capable GPUs in this system.')
    } elseif ($PvGPUs.Count -gt 1) {
    Write-Warning -Message (
    ("You have {0} GPU-P capable GPUs in this system. `n" -f $PvGPUs.Count) +
    " At present, there is no way to control which one is assigned to a given VM.`n" +
    " Unless one of the available GPUs is an intel IGP, it is highly recommended`n" +
    " that you disable the GPU(s) you do not wish to use.`n")
    $choices = '&Yes', '&No'
    $question = 'Do you wish to proceed without disabling the extra GPU(s)?'
    if ($Host.UI.PromptForChoice('', $question, $choices, 1) -eq 1) {
    throw [System.Management.Automation.ActionPreferenceStopException]::new('User requested to cancel.')
    # if we found no GPU-P capable GPUs, throw an exception
    if ($PVCapableGPUs.Count -lt 1) {
    throw [System.Management.Automation.ItemNotFoundException]::new('Did not find any GPU-P capable GPUs in this system.')
    } elseif ($PvGPUs.Count -gt 1) {
    Write-Warning -Message (
    ("You have {0} GPU-P capable GPUs in this system. `n" -f $PvGPUs.Count) +
    " At present, there is no way to control which one is assigned to a given VM.`n" +
    " Unless one of the available GPUs is an intel IGP, it is highly recommended`n" +
    " that you disable the GPU(s) you do not wish to use.`n")
    $choices = '&Yes', '&No'
    $question = 'Do you wish to proceed without disabling the extra GPU(s)?'
    if ($Host.UI.PromptForChoice('', $question, $choices, 1) -eq 1) {
    throw [System.Management.Automation.ActionPreferenceStopException]::new('User requested to cancel.')
    }
    }
    }


    # Map each PVCapableGPU to the corresponding PnPDevice. Regex (mostly) extracts the InstanceId from the VMPartitionableGpu 'name' property.
    Write-Output -InputObject ('Mapping GPU-P capable GPUs to their corresponding PnPDevice objects')
    $InstanceExpr = [regex]::New('^\\\\\?\\(.+)#.*$')
    $TargetGPUs = $PVCapableGPUs.Name | ForEach-Object -Process {
    # I'm not proud of this, no sir
    Get-PnpDevice -InstanceId $InstanceExpr.Replace($_, '$1').Replace('#', '\')
    }
    # Map each PVCapableGPU to the corresponding PnPDevice. Regex (mostly) extracts the InstanceId from the VMPartitionableGpu 'name' property.
    Write-Output -InputObject ('Mapping GPU-P capable GPUs to their corresponding PnPDevice objects...')
    $InstanceExpr = [regex]::New('^\\\\\?\\(.+)#.*$')
    $TargetGPUs = $PVCapableGPUs.Name | ForEach-Object -Process {
    # I'm not proud of this dirty regex trick, but it works.
    Get-PnpDevice -InstanceId $InstanceExpr.Replace($_, '$1').Replace('#', '\')
    }

    # OK, now that we have some actual device names, we can filter them if we've been asked to
    if ($null -ne $Filter) {
    Write-Output -InputObject ('Applying filter "{0}" to device list...' -f $Filter)
    $TargetGPUs = $TargetGPUs | Where-Object { $_.FriendlyName -like ('*{0}*' -f $Filter) }
    }
    Write-Output -InputObject ('Will create driver package for {0} GPUs:' -f $TargetGPUs.Count)
    $TargetGPUs.FriendlyName | ForEach-Object { Write-Output -InputObject (' - {0}' -f $_) }
    # OK, now that we have some actual device names, we can filter them if we've been asked to
    if ($null -ne $Filter) {
    Write-Output -InputObject ('Applying filter "{0}" to device list...' -f $Filter)
    $TargetGPUs = $TargetGPUs | Where-Object { $_.FriendlyName -like ('*{0}*' -f $Filter) }
    }
    Write-Output -InputObject ('Will create driver package for {0} GPUs:' -f $TargetGPUs.Count)
    $TargetGPUs.FriendlyName | ForEach-Object { Write-Output -InputObject (' - {0}' -f $_) }
    } catch { throw $PSItem }

    # Last chance to turn back, traveler. Are you sure?
    if ($pscmdlet.ShouldProcess("Driver Package", "Create")) {
    Write-Output -InputObject ('The next few steps may take some time, depending on how many devices & driver packages are installed.')
    Write-Output -InputObject ('If the script appears hung, please give it a few minutes to complete before terminating.')
    # Get display class devices
    Write-Output -InputObject ('Gathering display device CIM objects...')
    $PnPEntities = Get-CimInstance -ClassName 'Win32_PnPEntity' | Where-Object { $_.Class -like 'Display' }
    # Get display class drivers
    Write-Output -InputObject ('Gathering display driver CIM objects...')
    $PnPSignedDrivers = Get-CimInstance -ClassName 'Win32_PnPSignedDriver' -Filter "DeviceClass = 'DISPLAY'"
    # next we have to get every PnPSignedDriverCIMDataFile, because Get-CimAssociatedInstance doesn't wanna play ball
    Write-Output -InputObject ('Gathering all driver file CIM objects... (this is the slow one. Blame Microsoft.)') # or me not understanding CIM i guess?
    $SignedDriverFiles = Get-CimInstance -ClassName 'Win32_PNPSignedDriverCIMDataFile' # -Verbose
    Write-Output -InputObject ('CIM objects gathered.')

    foreach ($GPU in $TargetGPUs) {
    Write-Output -InputObject ('Getting driver package for {0}' -f $GPU.FriendlyName)
    $PnPEntity = $PnPEntities | Where-Object { $_.InstanceId -eq $GPU.InstanceId }[0]
    $PnPSignedDriver = $PnPSignedDrivers | Where-Object { $_.DeviceId -eq $GPU.InstanceId }
    $SystemDriver = Get-CimAssociatedInstance -InputObject $PnPEntity -Association Win32_SystemDriverPNPEntity
    $DriverStoreFolder = Split-Path -Path $SystemDriver.PathName -Parent
    Write-Output -InputObject ('Found package {0}, copying DriverStore folder {1} to temporary directory' -f $PnPSignedDriver.InfName, (Split-Path $DriverStoreFolder -Leaf))
    Copy-Item -Path $DriverStoreFolder -Destination "$fTempFolder/System32/HostDriverStore/FileRepository/" -Recurse -Force

    # Get driver files from system32 etc and copy
    Write-Output -InputObject ('Done, getting files from System32 and SysWOW64')
    $DriverFiles = ($SignedDriverFiles | Where-Object { $_.Antecedent.DeviceID -like $GPU.DeviceID }).Dependent.Name
    $System32Files = $DriverFiles | Where-Object { (Split-Path -Path $_ -Parent) -like "$Env:SYSTEMROOT\System32" }
    $SysWOW64Files = $DriverFiles | Where-Object { (Split-Path -Path $_ -Parent) -like "$Env:SYSTEMROOT\SysWOW64" }

    Write-Output -InputObject ('Found {0} files:' -f ($System32Files.Count + $SysWOW64Files.Count))
    $System32Files | ForEach-Object { Write-Output -InputObject (' - System32\{0}' -f (Split-Path -Path $_ -Leaf)) }
    $SysWOW64Files | ForEach-Object { Write-Output -InputObject (' - SysWOW64\{0}' -f (Split-Path -Path $_ -Leaf)) }

    Write-Output -InputObject ('Copying to temporary directory...')
    Copy-Item -Path $System32Files -Destination "$fTempFolder/System32/"
    Copy-Item -Path $SysWOW64Files -Destination "$fTempFolder/SysWOW64/"

    Write-Output -InputObject ('Finished gathering files for {0}' -f $GPU.FriendlyName)
    try {
    Write-Output -InputObject ('The next few steps may take some time, depending on how many devices & driver packages are installed.')
    Write-Output -InputObject ('If the script appears hung, please give it a few minutes to complete before terminating.')
    # Get display class devices
    Write-Output -InputObject ('Gathering display device CIM objects...')
    $PnPEntities = Get-CimInstance -ClassName 'Win32_PnPEntity' | Where-Object { $_.Class -like 'Display' }
    # Get display class drivers
    Write-Output -InputObject ('Gathering display driver CIM objects...')
    $PnPSignedDrivers = Get-CimInstance -ClassName 'Win32_PnPSignedDriver' -Filter "DeviceClass = 'DISPLAY'"
    # next we have to get every PnPSignedDriverCIMDataFile, because Get-CimAssociatedInstance doesn't wanna play ball
    Write-Output -InputObject ('Gathering all driver file CIM objects... (this is the slow one. Blame Microsoft.)') # or me not understanding CIM i guess?
    $SignedDriverFiles = Get-CimInstance -ClassName 'Win32_PNPSignedDriverCIMDataFile' # -Verbose
    Write-Output -InputObject ('CIM objects gathered.')

    foreach ($GPU in $TargetGPUs) {
    Write-Output -InputObject ('Getting driver package for {0}' -f $GPU.FriendlyName)
    $PnPEntity = $PnPEntities | Where-Object { $_.InstanceId -eq $GPU.InstanceId }[0]
    $PnPSignedDriver = $PnPSignedDrivers | Where-Object { $_.DeviceId -eq $GPU.InstanceId }
    $SystemDriver = Get-CimAssociatedInstance -InputObject $PnPEntity -Association Win32_SystemDriverPNPEntity
    $DriverStoreFolder = Split-Path -Path $SystemDriver.PathName -Parent
    Write-Output -InputObject ('Found package {0}, copying DriverStore folder {1} to temporary directory' -f $PnPSignedDriver.InfName, (Split-Path $DriverStoreFolder -Leaf))
    Copy-Item -Path $DriverStoreFolder -Destination "$fTempFolder/System32/HostDriverStore/FileRepository/" -Recurse -Force

    # Get driver files from system32 etc and copy
    Write-Output -InputObject ('DriverStore folder copied, gathering files from System32 and SysWOW64')
    $DriverFiles = ($SignedDriverFiles | Where-Object { $_.Antecedent.DeviceID -like $GPU.DeviceID }).Dependent.Name
    $System32Files = $DriverFiles | Where-Object { (Split-Path -Path $_ -Parent) -like "$Env:SYSTEMROOT\System32" }
    $SysWOW64Files = $DriverFiles | Where-Object { (Split-Path -Path $_ -Parent) -like "$Env:SYSTEMROOT\SysWOW64" }

    Write-Output -InputObject ('Found {0} files:' -f ($System32Files.Count + $SysWOW64Files.Count))
    $System32Files | ForEach-Object { Write-Output -InputObject (' - System32\{0}' -f (Split-Path -Path $_ -Leaf)) }
    $SysWOW64Files | ForEach-Object { Write-Output -InputObject (' - SysWOW64\{0}' -f (Split-Path -Path $_ -Leaf)) }

    Write-Output -InputObject ('Copying to temporary directory...')
    Copy-Item -Path $System32Files -Destination "$fTempFolder/System32/"
    Copy-Item -Path $SysWOW64Files -Destination "$fTempFolder/SysWOW64/"

    Write-Output -InputObject ('Finished gathering files for {0}' -f $GPU.FriendlyName)
    }
    Write-Output -InputObject ('All driver files have been collected, creating archive file')
    $Location = (Get-Location).Path
    Set-Location -Path (Split-Path -Path $fTempFolder -Parent)
    Compress-Archive -Path $fTempFolder -DestinationPath (Join-Path -Path $ArchiveFolder -ChildPath $ArchiveName) -CompressionLevel Fastest -Confirm:$false
    Set-Location -Path $Location
    Write-Output -InputObject ('GPU driver package has been created at path {0}\{1}' -f $ArchiveFolder, $ArchiveName)
    } catch {
    throw $PSItem
    } finally {
    Write-Output -InputObject ('Cleaning up temporary directory {0}' -f $fTempFolder)
    Remove-Item -Recurse -Force -Path $fTempFolder
    }
    Write-Output -InputObject ('All driver files have been collected, creating archive file')
    $Location = (Get-Location).Path
    Set-Location -Path (Split-Path -Path $fTempFolder -Parent)
    Compress-Archive -Path $fTempFolder -DestinationPath (Join-Path -Path $ArchiveFolder -ChildPath $ArchiveName) -CompressionLevel Fastest -Confirm:$false
    Set-Location -Path $Location
    Write-Output -InputObject ('GPU driver package has been created at path {0}\{1}' -f $ArchiveFolder, $ArchiveName)

    Write-Output -InputObject ('Cleaning up temporary directory {0}' -f $fTempFolder)
    Remove-Item -Recurse -Force -Path $fTempFolder
    }
    Write-Output -InputObject ('Driver package generation complete.')
    Write-Output -InputObject ('Please copy it to your guest and extract the archive into C:\Windows\')
    }
    }
  8. neggles revised this gist Aug 18, 2021. 1 changed file with 114 additions and 128 deletions.
    242 changes: 114 additions & 128 deletions New-GPUPDriverPackage.ps1
    Original file line number Diff line number Diff line change
    @@ -48,146 +48,132 @@ function New-GPUPDriverPackage {
    )

    process {
    try {
    # make me a temporary folder, and assemble the structure
    $fTempFolder = Join-Path -Path $Env:TEMP -ChildPath "GPUPDriverPackage"
    (New-Item -ItemType Directory -Path "$fTempFolder/System32/HostDriverStore/FileRepository" -Force -ErrorAction SilentlyContinue | Out-Null)
    (New-Item -ItemType Directory -Path "$fTempFolder/SysWOW64" -Force -ErrorAction SilentlyContinue | Out-Null)

    # Do some processing on the DestinationPath
    $ArchiveName = ('GPUPDriverPackage-{0}.zip' -f $(Get-Date -UFormat '+%Y%b%d'))
    switch ($DestinationPath) {
    # if path is a folder that exists, use the default name and just put it there
    { Test-Path -Path $_ -PathType Container -ErrorAction SilentlyContinue } {
    $ArchiveFolder = $DestinationPath
    break
    }
    # if path is a folder that does NOT exist, create it and put archive in it
    { Test-Path -Path $_ -PathType Container -IsValid -ErrorAction SilentlyContinue } {
    $ArchiveFolder = $DestinationPath
    (New-Item -ItemType Directory -Path $ArchiveFolder -Force -ErrorAction SilentlyContinue | Out-Null)
    break
    }
    # if path is a valid path with a file name ending in .zip, use that
    { Test-Path -Path $_ -PathType Leaf -IsValid -ErrorAction SilentlyContinue } {
    $ArchiveFolder = Split-Path -Path $DestinationPath -Parent
    $ArchiveName = Split-Path -Path $DestinationPath -Leaf
    break
    }
    # if param not supplied, use current directory and default name
    Default { $ArchiveFolder = (Get-Location).Path }
    # make me a temporary folder, and assemble the structure
    $fTempFolder = Join-Path -Path $Env:TEMP -ChildPath "GPUPDriverPackage"
    (New-Item -ItemType Directory -Path "$fTempFolder/System32/HostDriverStore/FileRepository" -Force -ErrorAction SilentlyContinue | Out-Null)
    (New-Item -ItemType Directory -Path "$fTempFolder/SysWOW64" -Force -ErrorAction SilentlyContinue | Out-Null)

    # Do some processing on the DestinationPath
    $ArchiveName = ('GPUPDriverPackage-{0}.zip' -f $(Get-Date -UFormat '+%Y%b%d'))
    switch ($DestinationPath) {
    # if path is a folder that exists, use the default name and just put it there
    { Test-Path -Path $PSItem -PathType Container -ErrorAction SilentlyContinue } {
    $ArchiveFolder = $DestinationPath
    break
    }

    # just double check that one
    if (-not $ArchiveFolder) { $ArchiveFolder = (Get-Location).Path }

    Write-Output -InputObject ('Creating GPU-P driver package for host {0}' -f $Env:COMPUTERNAME)
    Write-Output -InputObject ('Destination path: {0}' -f (Join-Path -Path $ArchiveFolder -ChildPath $ArchiveName))

    <#
    Determine which cmdlet we should use to gather the list of GPU-P capable GPUs.
    On Windows builds before Server 2022/21H2, the cmdlet is 'Get-VMPartitionableGpu'
    On later builds, it's 'Get-VMHostPartitionableGpu', and the old cmdlet just prints an error.
    So, we check if Get-VMHostPartitionableGpu is a valid cmdlet to determine whether we should use it.
    #>
    Write-Output -InputObject "Getting all GPU-P capable GPUs in the current system..."
    if (Get-Command -Name 'Get-VMHostPartitionableGpu' -ErrorAction SilentlyContinue) {
    Write-Verbose -Message 'Using new Get-VMHostPartitionableGpu cmdlet'
    $PVCapableGPUs = Get-VMHostPartitionableGpu
    } else {
    Write-Verbose -Message 'Using old Get-VMPartitionableGpu cmdlet'
    $PVCapableGPUs = Get-VMPartitionableGpu
    # if path is a folder that does NOT exist, create it and put archive in it
    { Test-Path -Path $PSItem -PathType Container -IsValid -ErrorAction SilentlyContinue } {
    $ArchiveFolder = $DestinationPath
    (New-Item -ItemType Directory -Path $ArchiveFolder -Force -ErrorAction SilentlyContinue | Out-Null)
    break
    }

    # if we found no GPU-P capable GPUs, throw an exception
    if ($PVCapableGPUs.Count -lt 1) {
    throw [System.Management.Automation.ItemNotFoundException]::new('Did not find any GPU-P capable GPUs in this system.')
    } elseif ($PvGPUs.Count -gt 1) {
    Write-Warning -Message (
    ("You have {0} GPU-P capable GPUs in this system. `n" -f $PvGPUs.Count) +
    " At present, there is no way to control which one is assigned to a given VM.`n" +
    " Unless one of the available GPUs is an intel IGP, it is highly recommended`n" +
    " that you disable the GPU(s) you do not wish to use.`n")
    $choices = '&Yes', '&No'
    $question = 'Do you wish to proceed without disabling the extra GPU(s)?'
    if ($Host.UI.PromptForChoice('', $question, $choices, 1) -eq 1) {
    throw [System.Management.Automation.ActionPreferenceStopException]::new('User requested to cancel.')
    }
    # if path is a valid path with a file name ending in .zip, use that
    { Test-Path -Path $PSItem -PathType Leaf -IsValid -ErrorAction SilentlyContinue } {
    $ArchiveFolder = Split-Path -Path $DestinationPath -Parent
    $ArchiveName = Split-Path -Path $DestinationPath -Leaf
    break
    }
    # if param not supplied, use current directory and default name
    Default { $ArchiveFolder = (Get-Location).Path }
    }

    # just double check that one
    if (-not $ArchiveFolder) { $ArchiveFolder = (Get-Location).Path }

    # Map each PVCapableGPU to the corresponding PnPDevice. Regex (mostly) extracts the InstanceId from the VMPartitionableGpu 'name' property.
    Write-Output -InputObject ('Mapping GPU-P capable GPUs to their corresponding PnPDevice objects...')
    $InstanceExpr = [regex]::New('^\\\\\?\\(.+)#.*$')
    $TargetGPUs = $PVCapableGPUs.Name | ForEach-Object -Process {
    # I'm not proud of this dirty regex trick, but it works.
    Get-PnpDevice -InstanceId $InstanceExpr.Replace($_, '$1').Replace('#', '\')
    }
    Write-Output -InputObject ('Creating GPU-P driver package for host {0}' -f $Env:COMPUTERNAME)
    Write-Output -InputObject ('Destination path: {0}' -f (Join-Path -Path $ArchiveFolder -ChildPath $ArchiveName))

    Write-Output -InputObject "Getting all GPU-P capable GPUs in the current system..."
    # requires some care as the command name changed in Server 2022/W10 21H2
    try {
    $PVCapableGPUs = Get-VMPartitionableGpu
    } catch {
    $PVCapableGPUs = Get-VMHostPartitionableGpu
    }

    # OK, now that we have some actual device names, we can filter them if we've been asked to
    if ($null -ne $Filter) {
    Write-Output -InputObject ('Applying filter "{0}" to device list...' -f $Filter)
    $TargetGPUs = $TargetGPUs | Where-Object { $_.FriendlyName -like ('*{0}*' -f $Filter) }
    # if we found no GPU-P capable GPUs, throw an exception
    if ($PVCapableGPUs.Count -lt 1) {
    throw [System.Management.Automation.ItemNotFoundException]::new('Did not find any GPU-P capable GPUs in this system.')
    } elseif ($PvGPUs.Count -gt 1) {
    Write-Warning -Message (
    ("You have {0} GPU-P capable GPUs in this system. `n" -f $PvGPUs.Count) +
    " At present, there is no way to control which one is assigned to a given VM.`n" +
    " Unless one of the available GPUs is an intel IGP, it is highly recommended`n" +
    " that you disable the GPU(s) you do not wish to use.`n")
    $choices = '&Yes', '&No'
    $question = 'Do you wish to proceed without disabling the extra GPU(s)?'
    if ($Host.UI.PromptForChoice('', $question, $choices, 1) -eq 1) {
    throw [System.Management.Automation.ActionPreferenceStopException]::new('User requested to cancel.')
    }
    Write-Output -InputObject ('Will create driver package for {0} GPUs:' -f $TargetGPUs.Count)
    $TargetGPUs.FriendlyName | ForEach-Object { Write-Output -InputObject (' - {0}' -f $_) }
    } catch { throw $PSItem }
    }


    # Map each PVCapableGPU to the corresponding PnPDevice. Regex (mostly) extracts the InstanceId from the VMPartitionableGpu 'name' property.
    Write-Output -InputObject ('Mapping GPU-P capable GPUs to their corresponding PnPDevice objects')
    $InstanceExpr = [regex]::New('^\\\\\?\\(.+)#.*$')
    $TargetGPUs = $PVCapableGPUs.Name | ForEach-Object -Process {
    # I'm not proud of this, no sir
    Get-PnpDevice -InstanceId $InstanceExpr.Replace($_, '$1').Replace('#', '\')
    }

    # OK, now that we have some actual device names, we can filter them if we've been asked to
    if ($null -ne $Filter) {
    Write-Output -InputObject ('Applying filter "{0}" to device list...' -f $Filter)
    $TargetGPUs = $TargetGPUs | Where-Object { $_.FriendlyName -like ('*{0}*' -f $Filter) }
    }
    Write-Output -InputObject ('Will create driver package for {0} GPUs:' -f $TargetGPUs.Count)
    $TargetGPUs.FriendlyName | ForEach-Object { Write-Output -InputObject (' - {0}' -f $_) }

    # Last chance to turn back, traveler. Are you sure?
    if ($pscmdlet.ShouldProcess("Driver Package", "Create")) {
    try {
    Write-Output -InputObject ('The next few steps may take some time, depending on how many devices & driver packages are installed.')
    Write-Output -InputObject ('If the script appears hung, please give it a few minutes to complete before terminating.')
    # Get display class devices
    Write-Output -InputObject ('Gathering display device CIM objects...')
    $PnPEntities = Get-CimInstance -ClassName 'Win32_PnPEntity' | Where-Object { $_.Class -like 'Display' }
    # Get display class drivers
    Write-Output -InputObject ('Gathering display driver CIM objects...')
    $PnPSignedDrivers = Get-CimInstance -ClassName 'Win32_PnPSignedDriver' -Filter "DeviceClass = 'DISPLAY'"
    # next we have to get every PnPSignedDriverCIMDataFile, because Get-CimAssociatedInstance doesn't wanna play ball
    Write-Output -InputObject ('Gathering all driver file CIM objects... (this is the slow one. Blame Microsoft.)') # or me not understanding CIM i guess?
    $SignedDriverFiles = Get-CimInstance -ClassName 'Win32_PNPSignedDriverCIMDataFile' # -Verbose
    Write-Output -InputObject ('CIM objects gathered.')

    foreach ($GPU in $TargetGPUs) {
    Write-Output -InputObject ('Getting driver package for {0}' -f $GPU.FriendlyName)
    $PnPEntity = $PnPEntities | Where-Object { $_.InstanceId -eq $GPU.InstanceId }[0]
    $PnPSignedDriver = $PnPSignedDrivers | Where-Object { $_.DeviceId -eq $GPU.InstanceId }
    $SystemDriver = Get-CimAssociatedInstance -InputObject $PnPEntity -Association Win32_SystemDriverPNPEntity
    $DriverStoreFolder = Split-Path -Path $SystemDriver.PathName -Parent
    Write-Output -InputObject ('Found package {0}, copying DriverStore folder {1} to temporary directory' -f $PnPSignedDriver.InfName, (Split-Path $DriverStoreFolder -Leaf))
    Copy-Item -Path $DriverStoreFolder -Destination "$fTempFolder/System32/HostDriverStore/FileRepository/" -Recurse -Force

    # Get driver files from system32 etc and copy
    Write-Output -InputObject ('DriverStore folder copied, gathering files from System32 and SysWOW64')
    $DriverFiles = ($SignedDriverFiles | Where-Object { $_.Antecedent.DeviceID -like $GPU.DeviceID }).Dependent.Name
    $System32Files = $DriverFiles | Where-Object { (Split-Path -Path $_ -Parent) -like "$Env:SYSTEMROOT\System32" }
    $SysWOW64Files = $DriverFiles | Where-Object { (Split-Path -Path $_ -Parent) -like "$Env:SYSTEMROOT\SysWOW64" }

    Write-Output -InputObject ('Found {0} files:' -f ($System32Files.Count + $SysWOW64Files.Count))
    $System32Files | ForEach-Object { Write-Output -InputObject (' - System32\{0}' -f (Split-Path -Path $_ -Leaf)) }
    $SysWOW64Files | ForEach-Object { Write-Output -InputObject (' - SysWOW64\{0}' -f (Split-Path -Path $_ -Leaf)) }

    Write-Output -InputObject ('Copying to temporary directory...')
    Copy-Item -Path $System32Files -Destination "$fTempFolder/System32/"
    Copy-Item -Path $SysWOW64Files -Destination "$fTempFolder/SysWOW64/"

    Write-Output -InputObject ('Finished gathering files for {0}' -f $GPU.FriendlyName)
    }
    Write-Output -InputObject ('All driver files have been collected, creating archive file')
    $Location = (Get-Location).Path
    Set-Location -Path (Split-Path -Path $fTempFolder -Parent)
    Compress-Archive -Path $fTempFolder -DestinationPath (Join-Path -Path $ArchiveFolder -ChildPath $ArchiveName) -CompressionLevel Fastest -Confirm:$false
    Set-Location -Path $Location
    Write-Output -InputObject ('GPU driver package has been created at path {0}\{1}' -f $ArchiveFolder, $ArchiveName)
    } catch {
    throw $PSItem
    } finally {
    Write-Output -InputObject ('Cleaning up temporary directory {0}' -f $fTempFolder)
    Remove-Item -Recurse -Force -Path $fTempFolder
    Write-Output -InputObject ('The next few steps may take some time, depending on how many devices & driver packages are installed.')
    Write-Output -InputObject ('If the script appears hung, please give it a few minutes to complete before terminating.')
    # Get display class devices
    Write-Output -InputObject ('Gathering display device CIM objects...')
    $PnPEntities = Get-CimInstance -ClassName 'Win32_PnPEntity' | Where-Object { $_.Class -like 'Display' }
    # Get display class drivers
    Write-Output -InputObject ('Gathering display driver CIM objects...')
    $PnPSignedDrivers = Get-CimInstance -ClassName 'Win32_PnPSignedDriver' -Filter "DeviceClass = 'DISPLAY'"
    # next we have to get every PnPSignedDriverCIMDataFile, because Get-CimAssociatedInstance doesn't wanna play ball
    Write-Output -InputObject ('Gathering all driver file CIM objects... (this is the slow one. Blame Microsoft.)') # or me not understanding CIM i guess?
    $SignedDriverFiles = Get-CimInstance -ClassName 'Win32_PNPSignedDriverCIMDataFile' # -Verbose
    Write-Output -InputObject ('CIM objects gathered.')

    foreach ($GPU in $TargetGPUs) {
    Write-Output -InputObject ('Getting driver package for {0}' -f $GPU.FriendlyName)
    $PnPEntity = $PnPEntities | Where-Object { $_.InstanceId -eq $GPU.InstanceId }[0]
    $PnPSignedDriver = $PnPSignedDrivers | Where-Object { $_.DeviceId -eq $GPU.InstanceId }
    $SystemDriver = Get-CimAssociatedInstance -InputObject $PnPEntity -Association Win32_SystemDriverPNPEntity
    $DriverStoreFolder = Split-Path -Path $SystemDriver.PathName -Parent
    Write-Output -InputObject ('Found package {0}, copying DriverStore folder {1} to temporary directory' -f $PnPSignedDriver.InfName, (Split-Path $DriverStoreFolder -Leaf))
    Copy-Item -Path $DriverStoreFolder -Destination "$fTempFolder/System32/HostDriverStore/FileRepository/" -Recurse -Force

    # Get driver files from system32 etc and copy
    Write-Output -InputObject ('Done, getting files from System32 and SysWOW64')
    $DriverFiles = ($SignedDriverFiles | Where-Object { $_.Antecedent.DeviceID -like $GPU.DeviceID }).Dependent.Name
    $System32Files = $DriverFiles | Where-Object { (Split-Path -Path $_ -Parent) -like "$Env:SYSTEMROOT\System32" }
    $SysWOW64Files = $DriverFiles | Where-Object { (Split-Path -Path $_ -Parent) -like "$Env:SYSTEMROOT\SysWOW64" }

    Write-Output -InputObject ('Found {0} files:' -f ($System32Files.Count + $SysWOW64Files.Count))
    $System32Files | ForEach-Object { Write-Output -InputObject (' - System32\{0}' -f (Split-Path -Path $_ -Leaf)) }
    $SysWOW64Files | ForEach-Object { Write-Output -InputObject (' - SysWOW64\{0}' -f (Split-Path -Path $_ -Leaf)) }

    Write-Output -InputObject ('Copying to temporary directory...')
    Copy-Item -Path $System32Files -Destination "$fTempFolder/System32/"
    Copy-Item -Path $SysWOW64Files -Destination "$fTempFolder/SysWOW64/"

    Write-Output -InputObject ('Finished gathering files for {0}' -f $GPU.FriendlyName)
    }
    Write-Output -InputObject ('All driver files have been collected, creating archive file')
    $Location = (Get-Location).Path
    Set-Location -Path (Split-Path -Path $fTempFolder -Parent)
    Compress-Archive -Path $fTempFolder -DestinationPath (Join-Path -Path $ArchiveFolder -ChildPath $ArchiveName) -CompressionLevel Fastest -Confirm:$false
    Set-Location -Path $Location
    Write-Output -InputObject ('GPU driver package has been created at path {0}\{1}' -f $ArchiveFolder, $ArchiveName)

    Write-Output -InputObject ('Cleaning up temporary directory {0}' -f $fTempFolder)
    Remove-Item -Recurse -Force -Path $fTempFolder
    }
    Write-Output -InputObject ('Driver package generation complete.')
    Write-Output -InputObject ('Please copy it to your guest and extract the archive into C:\Windows\')
    }
    }
  9. neggles revised this gist Jul 9, 2021. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion New-GPUPDriverPackage.ps1
    Original file line number Diff line number Diff line change
    @@ -160,7 +160,7 @@ function New-GPUPDriverPackage {

    # Get driver files from system32 etc and copy
    Write-Output -InputObject ('DriverStore folder copied, gathering files from System32 and SysWOW64')
    $DriverFiles = $SignedDriverFiles | Where-Object { $_.Antecedent.DeviceID -like $GPU.DeviceID }.Dependent.Name
    $DriverFiles = ($SignedDriverFiles | Where-Object { $_.Antecedent.DeviceID -like $GPU.DeviceID }).Dependent.Name
    $System32Files = $DriverFiles | Where-Object { (Split-Path -Path $_ -Parent) -like "$Env:SYSTEMROOT\System32" }
    $SysWOW64Files = $DriverFiles | Where-Object { (Split-Path -Path $_ -Parent) -like "$Env:SYSTEMROOT\SysWOW64" }

  10. neggles revised this gist Jul 7, 2021. 1 changed file with 25 additions and 19 deletions.
    44 changes: 25 additions & 19 deletions readme.md
    Original file line number Diff line number Diff line change
    @@ -8,34 +8,40 @@ I am not in any way responsible for working this out, that credit goes to [Nimoa
    1. Create a new VM using `New-GPUPVirtualMachine.ps1` below and install Windows 10 on it.
    1. Gather driver files for your guest using one of the two methods below:

    ## Using the driver gathering script (recommended)

    Download the `New-GPUPDriverPackage.ps1` script from this gist, it will gather the various files for you - it must be run as admin, make sure you either specify a destination path or run it from the folder you'd like the .zip created in.

    1. Run the script on your host, in an admin PowerShell session. It will create `GPUPDriverPackage-[date].zip` in the current directory, or a path specified with `-Destination <path>`.
    2. Copy the .zip to your guest VM and extract it.
    3. Copy the contents of the extracted `GPUPDriverPackage` folder into `C:\Windows\` on the guest VM
    4. Reboot the guest, and enjoy your hardware acceleration!

    This has been tested on nVidia, AMD, and Intel GPU drivers.
    (Most Intel iGPUs have support for GPU-P, though I've not tested for Quick Sync Video support in guests yet)

    ## Gathering driver files manually

    This only covers nVidia drivers, but the process is very similar for Intel and AMD.

    1. On your host system:
    1. Browse to ``C:\Windows\system32\DriverStore\FileRepository``
    2. Find the ``nvdispsi.inf_amd64_<guid>`` and/or ``nvltsi.inf_amd64_<guid>`` folders, and copy them to a temporary folder
    3. Grab ``nvapi64.dll`` from ``C:\Windows\system32`` as well
    4. On your guest system:
    3. View driver details in device manager on your host, and copy all the files you see listed in `System32` or `SysWOW64` into matching folders inside your temporary folder.
    2. On your guest system:
    1. Browse to ``C:\Windows\system32\HostDriverStore\FileRepository``
    (You will likely need to create the ``HostDriverStore`` and ``FileRepository`` directories)
    2. Copy the two driver folders you collected from your host to this path.
    3. Copy ``nvapi64.dll`` to ``C:\Windows\system32\`` on the guest as well, and a few others - look at 'driver details' for your GPU in device manager to work out which ones
    5. Shut down the guest VM,
    6. Make sure the VM's checkpoints are disabled, and automatic stop action is set to 'Turn Off'
    3. Copy ``nvapi64.dll`` (and the other to ``C:\Windows\system32\`` on the guest as well
    3. Shut down the guest VM,
    4. Make sure the VM's checkpoints are disabled, and automatic stop action is set to 'Turn Off'
    (The VM creation script covers this, but never hurts to be sure)
    6. Boot your VM, and enjoy your hardware acceleration!

    ## Using the driver gathering script

    You also have the option to use `New-GPUPDriverPackage.ps1`, which will gather the various files for you.

    1. Run the script on your host, in an admin PowerShell session. It will create `GPUPDriverPackage-[date].zip` in the current directory.
    2. Copy the .zip to your guest VM and extract
    3. Copy the contents of the extracted `GPUPDriverPackage` folder into `C:\Windows\`
    4. Reboot the guest.

    This will work for nVidia GPUs, at least, and should also work for Intel/AMD ones.
    5. Boot your VM, and enjoy your hardware acceleration!

    ----
    Windows features that must be enabled:
    * Hyper-V and all associated subfeatures
    * Windows Subsystem for Linux
    * Hyper-V
    * Windows Subsystem for Linux*
    * Virtual Machine Platform

    \* WSL/WSL2 is probably not necessary, but this functionality is present in Windows 10 to allow for CUDA support in WSL2, so it's probably a good idea to turn it on.
  11. neggles revised this gist Jul 7, 2021. 1 changed file with 15 additions and 7 deletions.
    22 changes: 15 additions & 7 deletions New-GPUPDriverPackage.ps1
    Original file line number Diff line number Diff line change
    @@ -84,12 +84,19 @@ function New-GPUPDriverPackage {
    Write-Output -InputObject ('Creating GPU-P driver package for host {0}' -f $Env:COMPUTERNAME)
    Write-Output -InputObject ('Destination path: {0}' -f (Join-Path -Path $ArchiveFolder -ChildPath $ArchiveName))

    <#
    Determine which cmdlet we should use to gather the list of GPU-P capable GPUs.
    On Windows builds before Server 2022/21H2, the cmdlet is 'Get-VMPartitionableGpu'
    On later builds, it's 'Get-VMHostPartitionableGpu', and the old cmdlet just prints an error.
    So, we check if Get-VMHostPartitionableGpu is a valid cmdlet to determine whether we should use it.
    #>
    Write-Output -InputObject "Getting all GPU-P capable GPUs in the current system..."
    # requires some care as the command name changed in Server 2022/W10 21H2
    try {
    $PVCapableGPUs = Get-VMPartitionableGpu
    } catch {
    if (Get-Command -Name 'Get-VMHostPartitionableGpu' -ErrorAction SilentlyContinue) {
    Write-Verbose -Message 'Using new Get-VMHostPartitionableGpu cmdlet'
    $PVCapableGPUs = Get-VMHostPartitionableGpu
    } else {
    Write-Verbose -Message 'Using old Get-VMPartitionableGpu cmdlet'
    $PVCapableGPUs = Get-VMPartitionableGpu
    }

    # if we found no GPU-P capable GPUs, throw an exception
    @@ -110,10 +117,10 @@ function New-GPUPDriverPackage {


    # Map each PVCapableGPU to the corresponding PnPDevice. Regex (mostly) extracts the InstanceId from the VMPartitionableGpu 'name' property.
    Write-Output -InputObject ('Mapping GPU-P capable GPUs to their corresponding PnPDevice objects')
    Write-Output -InputObject ('Mapping GPU-P capable GPUs to their corresponding PnPDevice objects...')
    $InstanceExpr = [regex]::New('^\\\\\?\\(.+)#.*$')
    $TargetGPUs = $PVCapableGPUs.Name | ForEach-Object -Process {
    # I'm not proud of this, no sir
    # I'm not proud of this dirty regex trick, but it works.
    Get-PnpDevice -InstanceId $InstanceExpr.Replace($_, '$1').Replace('#', '\')
    }

    @@ -152,7 +159,7 @@ function New-GPUPDriverPackage {
    Copy-Item -Path $DriverStoreFolder -Destination "$fTempFolder/System32/HostDriverStore/FileRepository/" -Recurse -Force

    # Get driver files from system32 etc and copy
    Write-Output -InputObject ('Done, getting files from System32 and SysWOW64')
    Write-Output -InputObject ('DriverStore folder copied, gathering files from System32 and SysWOW64')
    $DriverFiles = $SignedDriverFiles | Where-Object { $_.Antecedent.DeviceID -like $GPU.DeviceID }.Dependent.Name
    $System32Files = $DriverFiles | Where-Object { (Split-Path -Path $_ -Parent) -like "$Env:SYSTEMROOT\System32" }
    $SysWOW64Files = $DriverFiles | Where-Object { (Split-Path -Path $_ -Parent) -like "$Env:SYSTEMROOT\SysWOW64" }
    @@ -181,5 +188,6 @@ function New-GPUPDriverPackage {
    }
    }
    Write-Output -InputObject ('Driver package generation complete.')
    Write-Output -InputObject ('Please copy it to your guest and extract the archive into C:\Windows\')
    }
    }
  12. neggles revised this gist Jun 27, 2021. 1 changed file with 118 additions and 112 deletions.
    230 changes: 118 additions & 112 deletions New-GPUPDriverPackage.ps1
    Original file line number Diff line number Diff line change
    @@ -48,131 +48,137 @@ function New-GPUPDriverPackage {
    )

    process {
    # make me a temporary folder, and assemble the structure
    $fTempFolder = Join-Path -Path $Env:TEMP -ChildPath "GPUPDriverPackage"
    (New-Item -ItemType Directory -Path "$fTempFolder/System32/HostDriverStore/FileRepository" -Force -ErrorAction SilentlyContinue | Out-Null)
    (New-Item -ItemType Directory -Path "$fTempFolder/SysWOW64" -Force -ErrorAction SilentlyContinue | Out-Null)

    # Do some processing on the DestinationPath
    $ArchiveName = ('GPUPDriverPackage-{0}.zip' -f $(Get-Date -UFormat '+%Y%b%d'))
    switch ($DestinationPath) {
    # if path is a folder that exists, use the default name and just put it there
    { Test-Path -Path $PSItem -PathType Container -ErrorAction SilentlyContinue } {
    $ArchiveFolder = $DestinationPath
    break
    }
    # if path is a folder that does NOT exist, create it and put archive in it
    { Test-Path -Path $PSItem -PathType Container -IsValid -ErrorAction SilentlyContinue } {
    $ArchiveFolder = $DestinationPath
    (New-Item -ItemType Directory -Path $ArchiveFolder -Force -ErrorAction SilentlyContinue | Out-Null)
    break
    }
    # if path is a valid path with a file name ending in .zip, use that
    { Test-Path -Path $PSItem -PathType Leaf -IsValid -ErrorAction SilentlyContinue } {
    $ArchiveFolder = Split-Path -Path $DestinationPath -Parent
    $ArchiveName = Split-Path -Path $DestinationPath -Leaf
    break
    try {
    # make me a temporary folder, and assemble the structure
    $fTempFolder = Join-Path -Path $Env:TEMP -ChildPath "GPUPDriverPackage"
    (New-Item -ItemType Directory -Path "$fTempFolder/System32/HostDriverStore/FileRepository" -Force -ErrorAction SilentlyContinue | Out-Null)
    (New-Item -ItemType Directory -Path "$fTempFolder/SysWOW64" -Force -ErrorAction SilentlyContinue | Out-Null)

    # Do some processing on the DestinationPath
    $ArchiveName = ('GPUPDriverPackage-{0}.zip' -f $(Get-Date -UFormat '+%Y%b%d'))
    switch ($DestinationPath) {
    # if path is a folder that exists, use the default name and just put it there
    { Test-Path -Path $_ -PathType Container -ErrorAction SilentlyContinue } {
    $ArchiveFolder = $DestinationPath
    break
    }
    # if path is a folder that does NOT exist, create it and put archive in it
    { Test-Path -Path $_ -PathType Container -IsValid -ErrorAction SilentlyContinue } {
    $ArchiveFolder = $DestinationPath
    (New-Item -ItemType Directory -Path $ArchiveFolder -Force -ErrorAction SilentlyContinue | Out-Null)
    break
    }
    # if path is a valid path with a file name ending in .zip, use that
    { Test-Path -Path $_ -PathType Leaf -IsValid -ErrorAction SilentlyContinue } {
    $ArchiveFolder = Split-Path -Path $DestinationPath -Parent
    $ArchiveName = Split-Path -Path $DestinationPath -Leaf
    break
    }
    # if param not supplied, use current directory and default name
    Default { $ArchiveFolder = (Get-Location).Path }
    }
    # if param not supplied, use current directory and default name
    Default { $ArchiveFolder = (Get-Location).Path }
    }

    # just double check that one
    if (-not $ArchiveFolder) { $ArchiveFolder = (Get-Location).Path }
    # just double check that one
    if (-not $ArchiveFolder) { $ArchiveFolder = (Get-Location).Path }

    Write-Output -InputObject ('Creating GPU-P driver package for host {0}' -f $Env:COMPUTERNAME)
    Write-Output -InputObject ('Destination path: {0}' -f (Join-Path -Path $ArchiveFolder -ChildPath $ArchiveName))
    Write-Output -InputObject ('Creating GPU-P driver package for host {0}' -f $Env:COMPUTERNAME)
    Write-Output -InputObject ('Destination path: {0}' -f (Join-Path -Path $ArchiveFolder -ChildPath $ArchiveName))

    Write-Output -InputObject "Getting all GPU-P capable GPUs in the current system..."
    # requires some care as the command name changed in Server 2022/W10 21H2
    try {
    $PVCapableGPUs = Get-VMPartitionableGpu
    } catch {
    $PVCapableGPUs = Get-VMHostPartitionableGpu
    }
    Write-Output -InputObject "Getting all GPU-P capable GPUs in the current system..."
    # requires some care as the command name changed in Server 2022/W10 21H2
    try {
    $PVCapableGPUs = Get-VMPartitionableGpu
    } catch {
    $PVCapableGPUs = Get-VMHostPartitionableGpu
    }

    # if we found no GPU-P capable GPUs, throw an exception
    if ($PVCapableGPUs.Count -lt 1) {
    throw [System.Management.Automation.ItemNotFoundException]::new('Did not find any GPU-P capable GPUs in this system.')
    } elseif ($PvGPUs.Count -gt 1) {
    Write-Warning -Message (
    ("You have {0} GPU-P capable GPUs in this system. `n" -f $PvGPUs.Count) +
    " At present, there is no way to control which one is assigned to a given VM.`n" +
    " Unless one of the available GPUs is an intel IGP, it is highly recommended`n" +
    " that you disable the GPU(s) you do not wish to use.`n")
    $choices = '&Yes', '&No'
    $question = 'Do you wish to proceed without disabling the extra GPU(s)?'
    if ($Host.UI.PromptForChoice('', $question, $choices, 1) -eq 1) {
    throw [System.Management.Automation.ActionPreferenceStopException]::new('User requested to cancel.')
    # if we found no GPU-P capable GPUs, throw an exception
    if ($PVCapableGPUs.Count -lt 1) {
    throw [System.Management.Automation.ItemNotFoundException]::new('Did not find any GPU-P capable GPUs in this system.')
    } elseif ($PvGPUs.Count -gt 1) {
    Write-Warning -Message (
    ("You have {0} GPU-P capable GPUs in this system. `n" -f $PvGPUs.Count) +
    " At present, there is no way to control which one is assigned to a given VM.`n" +
    " Unless one of the available GPUs is an intel IGP, it is highly recommended`n" +
    " that you disable the GPU(s) you do not wish to use.`n")
    $choices = '&Yes', '&No'
    $question = 'Do you wish to proceed without disabling the extra GPU(s)?'
    if ($Host.UI.PromptForChoice('', $question, $choices, 1) -eq 1) {
    throw [System.Management.Automation.ActionPreferenceStopException]::new('User requested to cancel.')
    }
    }
    }


    # Map each PVCapableGPU to the corresponding PnPDevice. Regex (mostly) extracts the InstanceId from the VMPartitionableGpu 'name' property.
    Write-Output -InputObject ('Mapping GPU-P capable GPUs to their corresponding PnPDevice objects')
    $InstanceExpr = [regex]::New('^\\\\\?\\(.+)#.*$')
    $TargetGPUs = $PVCapableGPUs.Name | ForEach-Object -Process {
    # I'm not proud of this, no sir
    Get-PnpDevice -InstanceId $InstanceExpr.Replace($_, '$1').Replace('#', '\')
    }
    # Map each PVCapableGPU to the corresponding PnPDevice. Regex (mostly) extracts the InstanceId from the VMPartitionableGpu 'name' property.
    Write-Output -InputObject ('Mapping GPU-P capable GPUs to their corresponding PnPDevice objects')
    $InstanceExpr = [regex]::New('^\\\\\?\\(.+)#.*$')
    $TargetGPUs = $PVCapableGPUs.Name | ForEach-Object -Process {
    # I'm not proud of this, no sir
    Get-PnpDevice -InstanceId $InstanceExpr.Replace($_, '$1').Replace('#', '\')
    }

    # OK, now that we have some actual device names, we can filter them if we've been asked to
    if ($null -ne $Filter) {
    Write-Output -InputObject ('Applying filter "{0}" to device list...' -f $Filter)
    $TargetGPUs = $TargetGPUs | Where-Object { $_.FriendlyName -like ('*{0}*' -f $Filter) }
    }
    Write-Output -InputObject ('Will create driver package for {0} GPUs:' -f $TargetGPUs.Count)
    $TargetGPUs.FriendlyName | ForEach-Object { Write-Output -InputObject (' - {0}' -f $_) }
    # OK, now that we have some actual device names, we can filter them if we've been asked to
    if ($null -ne $Filter) {
    Write-Output -InputObject ('Applying filter "{0}" to device list...' -f $Filter)
    $TargetGPUs = $TargetGPUs | Where-Object { $_.FriendlyName -like ('*{0}*' -f $Filter) }
    }
    Write-Output -InputObject ('Will create driver package for {0} GPUs:' -f $TargetGPUs.Count)
    $TargetGPUs.FriendlyName | ForEach-Object { Write-Output -InputObject (' - {0}' -f $_) }
    } catch { throw $PSItem }

    # Last chance to turn back, traveler. Are you sure?
    if ($pscmdlet.ShouldProcess("Driver Package", "Create")) {
    Write-Output -InputObject ('The next few steps may take some time, depending on how many devices & driver packages are installed.')
    Write-Output -InputObject ('If the script appears hung, please give it a few minutes to complete before terminating.')
    # Get display class devices
    Write-Output -InputObject ('Gathering display device CIM objects...')
    $PnPEntities = Get-CimInstance -ClassName 'Win32_PnPEntity' | Where-Object { $_.Class -like 'Display' }
    # Get display class drivers
    Write-Output -InputObject ('Gathering display driver CIM objects...')
    $PnPSignedDrivers = Get-CimInstance -ClassName 'Win32_PnPSignedDriver' -Filter "DeviceClass = 'DISPLAY'"
    # next we have to get every PnPSignedDriverCIMDataFile, because Get-CimAssociatedInstance doesn't wanna play ball
    Write-Output -InputObject ('Gathering all driver file CIM objects... (this is the slow one. Blame Microsoft.)') # or me not understanding CIM i guess?
    $SignedDriverFiles = Get-CimInstance -ClassName 'Win32_PNPSignedDriverCIMDataFile' # -Verbose
    Write-Output -InputObject ('CIM objects gathered.')

    foreach ($GPU in $TargetGPUs) {
    Write-Output -InputObject ('Getting driver package for {0}' -f $GPU.FriendlyName)
    $PnPEntity = $PnPEntities | Where-Object { $_.InstanceId -eq $GPU.InstanceId }[0]
    $PnPSignedDriver = $PnPSignedDrivers | Where-Object { $_.DeviceId -eq $GPU.InstanceId }
    $SystemDriver = Get-CimAssociatedInstance -InputObject $PnPEntity -Association Win32_SystemDriverPNPEntity
    $DriverStoreFolder = Split-Path -Path $SystemDriver.PathName -Parent
    Write-Output -InputObject ('Found package {0}, copying DriverStore folder {1} to temporary directory' -f $PnPSignedDriver.InfName, (Split-Path $DriverStoreFolder -Leaf))
    Copy-Item -Path $DriverStoreFolder -Destination "$fTempFolder/System32/HostDriverStore/FileRepository/" -Recurse -Force

    # Get driver files from system32 etc and copy
    Write-Output -InputObject ('Done, getting files from System32 and SysWOW64')
    $DriverFiles = ($SignedDriverFiles | Where-Object { $_.Antecedent.DeviceID -like $GPU.DeviceID }).Dependent.Name
    $System32Files = $DriverFiles | Where-Object { (Split-Path -Path $_ -Parent) -like "$Env:SYSTEMROOT\System32" }
    $SysWOW64Files = $DriverFiles | Where-Object { (Split-Path -Path $_ -Parent) -like "$Env:SYSTEMROOT\SysWOW64" }

    Write-Output -InputObject ('Found {0} files:' -f ($System32Files.Count + $SysWOW64Files.Count))
    $System32Files | ForEach-Object { Write-Output -InputObject (' - System32\{0}' -f (Split-Path -Path $_ -Leaf)) }
    $SysWOW64Files | ForEach-Object { Write-Output -InputObject (' - SysWOW64\{0}' -f (Split-Path -Path $_ -Leaf)) }

    Write-Output -InputObject ('Copying to temporary directory...')
    Copy-Item -Path $System32Files -Destination "$fTempFolder/System32/"
    Copy-Item -Path $SysWOW64Files -Destination "$fTempFolder/SysWOW64/"

    Write-Output -InputObject ('Finished gathering files for {0}' -f $GPU.FriendlyName)
    try {
    Write-Output -InputObject ('The next few steps may take some time, depending on how many devices & driver packages are installed.')
    Write-Output -InputObject ('If the script appears hung, please give it a few minutes to complete before terminating.')
    # Get display class devices
    Write-Output -InputObject ('Gathering display device CIM objects...')
    $PnPEntities = Get-CimInstance -ClassName 'Win32_PnPEntity' | Where-Object { $_.Class -like 'Display' }
    # Get display class drivers
    Write-Output -InputObject ('Gathering display driver CIM objects...')
    $PnPSignedDrivers = Get-CimInstance -ClassName 'Win32_PnPSignedDriver' -Filter "DeviceClass = 'DISPLAY'"
    # next we have to get every PnPSignedDriverCIMDataFile, because Get-CimAssociatedInstance doesn't wanna play ball
    Write-Output -InputObject ('Gathering all driver file CIM objects... (this is the slow one. Blame Microsoft.)') # or me not understanding CIM i guess?
    $SignedDriverFiles = Get-CimInstance -ClassName 'Win32_PNPSignedDriverCIMDataFile' # -Verbose
    Write-Output -InputObject ('CIM objects gathered.')

    foreach ($GPU in $TargetGPUs) {
    Write-Output -InputObject ('Getting driver package for {0}' -f $GPU.FriendlyName)
    $PnPEntity = $PnPEntities | Where-Object { $_.InstanceId -eq $GPU.InstanceId }[0]
    $PnPSignedDriver = $PnPSignedDrivers | Where-Object { $_.DeviceId -eq $GPU.InstanceId }
    $SystemDriver = Get-CimAssociatedInstance -InputObject $PnPEntity -Association Win32_SystemDriverPNPEntity
    $DriverStoreFolder = Split-Path -Path $SystemDriver.PathName -Parent
    Write-Output -InputObject ('Found package {0}, copying DriverStore folder {1} to temporary directory' -f $PnPSignedDriver.InfName, (Split-Path $DriverStoreFolder -Leaf))
    Copy-Item -Path $DriverStoreFolder -Destination "$fTempFolder/System32/HostDriverStore/FileRepository/" -Recurse -Force

    # Get driver files from system32 etc and copy
    Write-Output -InputObject ('Done, getting files from System32 and SysWOW64')
    $DriverFiles = $SignedDriverFiles | Where-Object { $_.Antecedent.DeviceID -like $GPU.DeviceID }.Dependent.Name
    $System32Files = $DriverFiles | Where-Object { (Split-Path -Path $_ -Parent) -like "$Env:SYSTEMROOT\System32" }
    $SysWOW64Files = $DriverFiles | Where-Object { (Split-Path -Path $_ -Parent) -like "$Env:SYSTEMROOT\SysWOW64" }

    Write-Output -InputObject ('Found {0} files:' -f ($System32Files.Count + $SysWOW64Files.Count))
    $System32Files | ForEach-Object { Write-Output -InputObject (' - System32\{0}' -f (Split-Path -Path $_ -Leaf)) }
    $SysWOW64Files | ForEach-Object { Write-Output -InputObject (' - SysWOW64\{0}' -f (Split-Path -Path $_ -Leaf)) }

    Write-Output -InputObject ('Copying to temporary directory...')
    Copy-Item -Path $System32Files -Destination "$fTempFolder/System32/"
    Copy-Item -Path $SysWOW64Files -Destination "$fTempFolder/SysWOW64/"

    Write-Output -InputObject ('Finished gathering files for {0}' -f $GPU.FriendlyName)
    }
    Write-Output -InputObject ('All driver files have been collected, creating archive file')
    $Location = (Get-Location).Path
    Set-Location -Path (Split-Path -Path $fTempFolder -Parent)
    Compress-Archive -Path $fTempFolder -DestinationPath (Join-Path -Path $ArchiveFolder -ChildPath $ArchiveName) -CompressionLevel Fastest -Confirm:$false
    Set-Location -Path $Location
    Write-Output -InputObject ('GPU driver package has been created at path {0}\{1}' -f $ArchiveFolder, $ArchiveName)
    } catch {
    throw $PSItem
    } finally {
    Write-Output -InputObject ('Cleaning up temporary directory {0}' -f $fTempFolder)
    Remove-Item -Recurse -Force -Path $fTempFolder
    }
    Write-Output -InputObject ('All driver files have been collected, creating archive file')
    $Location = (Get-Location).Path
    Set-Location -Path (Split-Path -Path $fTempFolder -Parent)
    Compress-Archive -Path $fTempFolder -DestinationPath (Join-Path -Path $ArchiveFolder -ChildPath $ArchiveName) -CompressionLevel Fastest -Confirm:$false
    Set-Location -Path $Location
    Write-Output -InputObject ('GPU driver package has been created at path {0}\{1}' -f $ArchiveFolder, $ArchiveName)

    Write-Output -InputObject ('Cleaning up temporary directory {0}' -f $fTempFolder)
    Remove-Item -Recurse -Force -Path $fTempFolder
    }
    Write-Output -InputObject ('Driver package generation complete.')
    }
  13. neggles revised this gist Jun 26, 2021. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion New-GPUPDriverPackage.ps1
    Original file line number Diff line number Diff line change
    @@ -150,7 +150,7 @@ function New-GPUPDriverPackage {

    # Get driver files from system32 etc and copy
    Write-Output -InputObject ('Done, getting files from System32 and SysWOW64')
    $DriverFiles = $SignedDriverFiles | Where-Object { $_.Antecedent.DeviceID -like $GPU.DeviceID }.Dependent.Name
    $DriverFiles = ($SignedDriverFiles | Where-Object { $_.Antecedent.DeviceID -like $GPU.DeviceID }).Dependent.Name
    $System32Files = $DriverFiles | Where-Object { (Split-Path -Path $_ -Parent) -like "$Env:SYSTEMROOT\System32" }
    $SysWOW64Files = $DriverFiles | Where-Object { (Split-Path -Path $_ -Parent) -like "$Env:SYSTEMROOT\SysWOW64" }

  14. neggles revised this gist Jun 26, 2021. 1 changed file with 7 additions and 7 deletions.
    14 changes: 7 additions & 7 deletions New-GPUPDriverPackage.ps1
    Original file line number Diff line number Diff line change
    @@ -57,17 +57,18 @@ function New-GPUPDriverPackage {
    $ArchiveName = ('GPUPDriverPackage-{0}.zip' -f $(Get-Date -UFormat '+%Y%b%d'))
    switch ($DestinationPath) {
    # if path is a folder that exists, use the default name and just put it there
    { Test-Path -Path $PSItem -PathType Container } {
    { Test-Path -Path $PSItem -PathType Container -ErrorAction SilentlyContinue } {
    $ArchiveFolder = $DestinationPath
    break
    }
    # if path is a folder that exists, use the default name and just put it there
    { Test-Path -Path $PSItem -PathType Container } {
    # if path is a folder that does NOT exist, create it and put archive in it
    { Test-Path -Path $PSItem -PathType Container -IsValid -ErrorAction SilentlyContinue } {
    $ArchiveFolder = $DestinationPath
    (New-Item -ItemType Directory -Path $ArchiveFolder -Force -ErrorAction SilentlyContinue | Out-Null)
    break
    }
    # if path is a valid path with a file name ending in .zip, use that
    { Test-Path -Path $PSItem -PathType Leaf -IsValid } {
    { Test-Path -Path $PSItem -PathType Leaf -IsValid -ErrorAction SilentlyContinue } {
    $ArchiveFolder = Split-Path -Path $DestinationPath -Parent
    $ArchiveName = Split-Path -Path $DestinationPath -Leaf
    break
    @@ -76,9 +77,8 @@ function New-GPUPDriverPackage {
    Default { $ArchiveFolder = (Get-Location).Path }
    }

    if (-not $ArchiveFolder) {
    $ArchiveFolder = (Get-Location).Path
    }
    # just double check that one
    if (-not $ArchiveFolder) { $ArchiveFolder = (Get-Location).Path }

    Write-Output -InputObject ('Creating GPU-P driver package for host {0}' -f $Env:COMPUTERNAME)
    Write-Output -InputObject ('Destination path: {0}' -f (Join-Path -Path $ArchiveFolder -ChildPath $ArchiveName))
  15. neggles revised this gist Jun 26, 2021. 3 changed files with 180 additions and 162 deletions.
    161 changes: 0 additions & 161 deletions Compress-GPUPDriverPackage.ps1
    Original file line number Diff line number Diff line change
    @@ -1,161 +0,0 @@
    <#
    .SYNOPSIS
    Create a GPU-P Guest driver package.
    .DESCRIPTION
    Gathers the necessary files for a GPU-P enabled Windows guest to run.
    .EXAMPLE
    Compress-GPUPDriverPackage -DestinationPath '.'
    .EXAMPLE
    Compress-GPUPDriverPackage -Filter 'nvidia' -DestinationPath '.'
    .INPUTS
    None.
    .OUTPUTS
    A driver package .zip
    .NOTES
    This has some mildly dodgy use of CIM cmdlets...
    .COMPONENT
    PSHyperTools
    .ROLE
    GPUP
    .FUNCTIONALITY
    Creates a guest driver package.
    #>
    [CmdletBinding(
    SupportsShouldProcess = $true,
    PositionalBinding = $true,
    HelpUri = 'http://www.microsoft.com/',
    ConfirmImpact = 'Low')]
    [Alias()]
    [OutputType([String])]
    Param (
    # Path to output directory.
    # If no file name is specified the filename will be GPUPDriverPackage-YYYYMMMDD.zip
    [Parameter(Mandatory = $false,
    HelpMessage = "Path to one or more locations.")]
    [Alias("PSPath", "Path")]
    [ValidateNotNullOrEmpty()]
    [string]
    $DestinationPath,

    # Device friendly name filter.
    # Only devices whose friendly names contain the supplied string will be processed
    [Parameter(Mandatory = $false,
    HelpMessage = "Only add drivers for devices whose friendly names contain the supplied string.")]
    [ValidateNotNullOrEmpty]
    [String]
    $Filter
    )

    begin {
    # make me a temporary folder, and assemble the structure
    $fTempFolder = Join-Path -Path $Env:TEMP -ChildPath "GPUPDriverPackage"
    (New-Item -ItemType Directory -Path "$fTempFolder/System32/HostDriverStore/FileRepository" -Force -ErrorAction SilentlyContinue | Out-Null)
    (New-Item -ItemType Directory -Path "$fTempFolder/SysWOW64" -Force -ErrorAction SilentlyContinue | Out-Null)

    # Do some processing on the DestinationPath
    if ($null -eq $DestinationPath) {
    $ArchiveName = ('GPUPDriverPackage-{0}.zip' -f $(Get-Date -UFormat '+%Y%b%d'))
    $ArchiveFolder = (Get-Location).Path
    } elseif ($DestinationPath -like '*.zip') {
    $ArchiveName = Split-Path -Path $DestinationPath -Leaf
    $ArchiveFolder = Split-Path -Path $DestinationPath -Parent
    }
    Write-Output -InputObject ('Driver package will be created:')
    Write-Output -InputObject (' Name: {0}' -f $ArchiveName)
    Write-Output -InputObject (' Path: {0}\{1}' -f $ArchiveFolder, $ArchiveName)
    }

    process {
    Write-Output -InputObject "Getting all GPU-P capable GPUs in the current system..."
    # requires some care as the command name changed in Server 2022/W10 21H2
    try {
    $PVCapableGPUs = Get-VMPartitionableGpu
    } catch {
    $PVCapableGPUs = Get-VMHostPartitionableGpu
    }

    # if we found no GPU-P capable GPUs, throw an exception
    if ($PVCapableGPUs.Count -lt 1) {
    throw [System.Management.Automation.ItemNotFoundException]::new('Did not find any GPU-P capable GPUs in this system.')
    } elseif ($PvGPUs.Count -gt 1) {
    Write-Warning -Message (
    ("You have {0} GPU-P capable GPUs in this system. `n" -f $PvGPUs.Count) +
    " At present, there is no way to control which one is assigned to a given VM.`n" +
    " Unless one of the available GPUs is an intel IGP, it is highly recommended`n" +
    " that you disable the GPU(s) you do not wish to use.`n")
    $choices = '&Yes', '&No'
    $question = 'Do you wish to proceed without disabling the extra GPU(s)?'
    if ($Host.UI.PromptForChoice('', $question, $choices, 1) -eq 1) {
    throw [System.Management.Automation.ActionPreferenceStopException]::new('User requested to cancel.')
    }
    }


    # Map each PVCapableGPU to the corresponding PnPDevice. Regex (mostly) extracts the InstanceId from the VMPartitionableGpu 'name' property.
    Write-Output -InputObject ('Mapping GPU-P capable GPUs to their corresponding PnPDevice objects')
    $InstanceExpr = [regex]::New('^\\\\\?\\(.+)#.*$')
    $TargetGPUs = $PVCapableGPUs.Name | ForEach-Object -Process {
    # I'm not proud of this, no sir
    Get-PnpDevice -InstanceId $InstanceExpr.Replace($_, '$1').Replace('#', '\')
    }

    # OK, now that we have some actual device names, we can filter them if we've been asked to
    if ($null -ne $Filter) {
    Write-Output -InputObject ('Applying filter "{0}" to device list...' -f $Filter)
    $TargetGPUs = $TargetGPUs | Where-Object { $_.FriendlyName -like ('*{0}*' -f $Filter) }
    }
    Write-Output -InputObject ('Will create driver package for {0} GPUs:' -f $TargetGPUs.Count)
    $TargetGPUs.FriendlyName | ForEach-Object { Write-Output -InputObject (' - {0}' -f $_) }

    # Last chance to turn back, traveler. Are you sure?
    if ($pscmdlet.ShouldProcess("Driver Package", "Create")) {
    Write-Output -InputObject ('The next few steps may take some time, depending on how many devices & driver packages are installed.')
    Write-Output -InputObject ('If the script appears hung, please give it a few minutes to complete before terminating.')
    # Get display class devices
    Write-Output -InputObject ('Gathering display device CIM objects...')
    $PnPEntities = Get-CimInstance -ClassName 'Win32_PnPEntity' | Where-Object { $_.Class -like 'Display' }
    # Get display class drivers
    Write-Output -InputObject ('Gathering display driver CIM objects...')
    $PnPSignedDrivers = Get-CimInstance -ClassName 'Win32_PnPSignedDriver' -Filter "DeviceClass = 'DISPLAY'"
    # next we have to get every PnPSignedDriverCIMDataFile, because Get-CimAssociatedInstance doesn't wanna play ball
    Write-Output -InputObject ('Gathering all driver file CIM objects... (this is the slow one. Blame Microsoft.)') # or me not understanding CIM i guess?
    $SignedDriverFiles = Get-CimInstance -ClassName 'Win32_PNPSignedDriverCIMDataFile' # -Verbose
    Write-Output -InputObject ('CIM objects gathered.')

    foreach ($GPU in $TargetGPUs) {
    Write-Output -InputObject ('Getting driver package for {0}' -f $GPU.FriendlyName)
    $PnPEntity = $PnPEntities.Where{ $_.InstanceId -eq $GPU.InstanceId }[0]
    $PnPSignedDriver = $PnPSignedDrivers.Where{ $_.DeviceId -eq $GPU.InstanceId }
    $SystemDriver = Get-CimAssociatedInstance -InputObject $PnPEntity -Association Win32_SystemDriverPNPEntity
    $DriverStoreFolder = Split-Path -Path $SystemDriver.PathName -Parent
    Write-Output -InputObject ('Found package {0}, copying DriverStore folder {1} to temporary directory' -f $PnPSignedDriver.InfName, (Split-Path $DriverStoreFolder -Leaf))
    Copy-Item -Path $DriverStoreFolder -Destination "$fTempFolder/System32/HostDriverStore/FileRepository/" -Recurse -Force

    # Get driver files from system32 etc and copy
    Write-Output -InputObject ('Done, getting files from System32 and SysWOW64')
    $DriverFiles = $SignedDriverFiles.Where{ $_.Antecedent.DeviceID -like $GPU.DeviceID }.Dependent.Name
    $System32Files = $DriverFiles.Where{ (Split-Path -Path $_ -Parent) -like "$Env:SYSTEMROOT\System32" }
    $SysWOW64Files = $DriverFiles.Where{ (Split-Path -Path $_ -Parent) -like "$Env:SYSTEMROOT\SysWOW64" }

    Write-Output -InputObject ('Found {0} files:' -f ($System32Files.Count + $SysWOW64Files.Count))
    $System32Files | ForEach-Object { Write-Output -InputObject (' - System32\{0}' -f (Split-Path -Path $_ -Leaf)) }
    $SysWOW64Files | ForEach-Object { Write-Output -InputObject (' - SysWOW64\{0}' -f (Split-Path -Path $_ -Leaf)) }

    Write-Output -InputObject ('Copying to temporary directory...')
    Copy-Item -Path $System32Files -Destination "$fTempFolder/System32/"
    Copy-Item -Path $SysWOW64Files -Destination "$fTempFolder/SysWOW64/"

    Write-Output -InputObject ('Finished gathering files for {0}' -f $GPU.FriendlyName)
    }
    Write-Output -InputObject ('All driver files have been collected, creating archive file')
    $Location = (Get-Location).Path
    Set-Location -Path (Split-Path -Path $fTempFolder -Parent)
    Compress-Archive -Path $fTempFolder -DestinationPath (Join-Path -Path $ArchiveFolder -ChildPath $ArchiveName) -CompressionLevel Fastest -Confirm:$false
    Set-Location -Path $Location
    Write-Output -InputObject ('GPU driver package has been created at path {0}\{1}' -f $ArchiveFolder, $ArchiveName)

    Write-Output -InputObject ('Cleaning up temporary directory {0}' -f $fTempFolder)
    Remove-Item -Recurse -Force -Path $fTempFolder
    }
    Write-Output -InputObject ('Driver package generation complete.')
    }
    179 changes: 179 additions & 0 deletions New-GPUPDriverPackage.ps1
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,179 @@
    <#
    .SYNOPSIS
    Create a GPU-P Guest driver package.
    .DESCRIPTION
    Gathers the necessary files for a GPU-P enabled Windows guest to run.
    .EXAMPLE
    New-GPUPDriverPackage -DestinationPath '.'
    .EXAMPLE
    New-GPUPDriverPackage -Filter 'nvidia' -DestinationPath '.'
    .INPUTS
    None.
    .OUTPUTS
    A driver package .zip
    .NOTES
    This has some mildly dodgy use of CIM cmdlets...
    .COMPONENT
    PSHyperTools
    .ROLE
    GPUP
    .FUNCTIONALITY
    Creates a guest driver package.
    #>
    function New-GPUPDriverPackage {
    [CmdletBinding(
    SupportsShouldProcess = $true,
    PositionalBinding = $true,
    HelpUri = 'http://www.microsoft.com/',
    ConfirmImpact = 'Low')]
    [Alias()]
    [OutputType([String])]
    Param (
    # Path to output directory.
    # If no file name is specified the filename will be GPUPDriverPackage-YYYYMMMDD.zip
    [Parameter(Mandatory = $false,
    HelpMessage = "Path to one or more locations.")]
    [Alias("PSPath", "Path")]
    [ValidateNotNullOrEmpty()]
    [string]
    $DestinationPath,

    # Device friendly name filter.
    # Only devices whose friendly names contain the supplied string will be processed
    [Parameter(Mandatory = $false,
    HelpMessage = "Only add drivers for devices whose friendly names contain the supplied string.")]
    [ValidateNotNullOrEmpty]
    [String]
    $Filter
    )

    process {
    # make me a temporary folder, and assemble the structure
    $fTempFolder = Join-Path -Path $Env:TEMP -ChildPath "GPUPDriverPackage"
    (New-Item -ItemType Directory -Path "$fTempFolder/System32/HostDriverStore/FileRepository" -Force -ErrorAction SilentlyContinue | Out-Null)
    (New-Item -ItemType Directory -Path "$fTempFolder/SysWOW64" -Force -ErrorAction SilentlyContinue | Out-Null)

    # Do some processing on the DestinationPath
    $ArchiveName = ('GPUPDriverPackage-{0}.zip' -f $(Get-Date -UFormat '+%Y%b%d'))
    switch ($DestinationPath) {
    # if path is a folder that exists, use the default name and just put it there
    { Test-Path -Path $PSItem -PathType Container } {
    $ArchiveFolder = $DestinationPath
    break
    }
    # if path is a folder that exists, use the default name and just put it there
    { Test-Path -Path $PSItem -PathType Container } {
    $ArchiveFolder = $DestinationPath
    break
    }
    # if path is a valid path with a file name ending in .zip, use that
    { Test-Path -Path $PSItem -PathType Leaf -IsValid } {
    $ArchiveFolder = Split-Path -Path $DestinationPath -Parent
    $ArchiveName = Split-Path -Path $DestinationPath -Leaf
    break
    }
    # if param not supplied, use current directory and default name
    Default { $ArchiveFolder = (Get-Location).Path }
    }

    if (-not $ArchiveFolder) {
    $ArchiveFolder = (Get-Location).Path
    }

    Write-Output -InputObject ('Creating GPU-P driver package for host {0}' -f $Env:COMPUTERNAME)
    Write-Output -InputObject ('Destination path: {0}' -f (Join-Path -Path $ArchiveFolder -ChildPath $ArchiveName))

    Write-Output -InputObject "Getting all GPU-P capable GPUs in the current system..."
    # requires some care as the command name changed in Server 2022/W10 21H2
    try {
    $PVCapableGPUs = Get-VMPartitionableGpu
    } catch {
    $PVCapableGPUs = Get-VMHostPartitionableGpu
    }

    # if we found no GPU-P capable GPUs, throw an exception
    if ($PVCapableGPUs.Count -lt 1) {
    throw [System.Management.Automation.ItemNotFoundException]::new('Did not find any GPU-P capable GPUs in this system.')
    } elseif ($PvGPUs.Count -gt 1) {
    Write-Warning -Message (
    ("You have {0} GPU-P capable GPUs in this system. `n" -f $PvGPUs.Count) +
    " At present, there is no way to control which one is assigned to a given VM.`n" +
    " Unless one of the available GPUs is an intel IGP, it is highly recommended`n" +
    " that you disable the GPU(s) you do not wish to use.`n")
    $choices = '&Yes', '&No'
    $question = 'Do you wish to proceed without disabling the extra GPU(s)?'
    if ($Host.UI.PromptForChoice('', $question, $choices, 1) -eq 1) {
    throw [System.Management.Automation.ActionPreferenceStopException]::new('User requested to cancel.')
    }
    }


    # Map each PVCapableGPU to the corresponding PnPDevice. Regex (mostly) extracts the InstanceId from the VMPartitionableGpu 'name' property.
    Write-Output -InputObject ('Mapping GPU-P capable GPUs to their corresponding PnPDevice objects')
    $InstanceExpr = [regex]::New('^\\\\\?\\(.+)#.*$')
    $TargetGPUs = $PVCapableGPUs.Name | ForEach-Object -Process {
    # I'm not proud of this, no sir
    Get-PnpDevice -InstanceId $InstanceExpr.Replace($_, '$1').Replace('#', '\')
    }

    # OK, now that we have some actual device names, we can filter them if we've been asked to
    if ($null -ne $Filter) {
    Write-Output -InputObject ('Applying filter "{0}" to device list...' -f $Filter)
    $TargetGPUs = $TargetGPUs | Where-Object { $_.FriendlyName -like ('*{0}*' -f $Filter) }
    }
    Write-Output -InputObject ('Will create driver package for {0} GPUs:' -f $TargetGPUs.Count)
    $TargetGPUs.FriendlyName | ForEach-Object { Write-Output -InputObject (' - {0}' -f $_) }

    # Last chance to turn back, traveler. Are you sure?
    if ($pscmdlet.ShouldProcess("Driver Package", "Create")) {
    Write-Output -InputObject ('The next few steps may take some time, depending on how many devices & driver packages are installed.')
    Write-Output -InputObject ('If the script appears hung, please give it a few minutes to complete before terminating.')
    # Get display class devices
    Write-Output -InputObject ('Gathering display device CIM objects...')
    $PnPEntities = Get-CimInstance -ClassName 'Win32_PnPEntity' | Where-Object { $_.Class -like 'Display' }
    # Get display class drivers
    Write-Output -InputObject ('Gathering display driver CIM objects...')
    $PnPSignedDrivers = Get-CimInstance -ClassName 'Win32_PnPSignedDriver' -Filter "DeviceClass = 'DISPLAY'"
    # next we have to get every PnPSignedDriverCIMDataFile, because Get-CimAssociatedInstance doesn't wanna play ball
    Write-Output -InputObject ('Gathering all driver file CIM objects... (this is the slow one. Blame Microsoft.)') # or me not understanding CIM i guess?
    $SignedDriverFiles = Get-CimInstance -ClassName 'Win32_PNPSignedDriverCIMDataFile' # -Verbose
    Write-Output -InputObject ('CIM objects gathered.')

    foreach ($GPU in $TargetGPUs) {
    Write-Output -InputObject ('Getting driver package for {0}' -f $GPU.FriendlyName)
    $PnPEntity = $PnPEntities | Where-Object { $_.InstanceId -eq $GPU.InstanceId }[0]
    $PnPSignedDriver = $PnPSignedDrivers | Where-Object { $_.DeviceId -eq $GPU.InstanceId }
    $SystemDriver = Get-CimAssociatedInstance -InputObject $PnPEntity -Association Win32_SystemDriverPNPEntity
    $DriverStoreFolder = Split-Path -Path $SystemDriver.PathName -Parent
    Write-Output -InputObject ('Found package {0}, copying DriverStore folder {1} to temporary directory' -f $PnPSignedDriver.InfName, (Split-Path $DriverStoreFolder -Leaf))
    Copy-Item -Path $DriverStoreFolder -Destination "$fTempFolder/System32/HostDriverStore/FileRepository/" -Recurse -Force

    # Get driver files from system32 etc and copy
    Write-Output -InputObject ('Done, getting files from System32 and SysWOW64')
    $DriverFiles = $SignedDriverFiles | Where-Object { $_.Antecedent.DeviceID -like $GPU.DeviceID }.Dependent.Name
    $System32Files = $DriverFiles | Where-Object { (Split-Path -Path $_ -Parent) -like "$Env:SYSTEMROOT\System32" }
    $SysWOW64Files = $DriverFiles | Where-Object { (Split-Path -Path $_ -Parent) -like "$Env:SYSTEMROOT\SysWOW64" }

    Write-Output -InputObject ('Found {0} files:' -f ($System32Files.Count + $SysWOW64Files.Count))
    $System32Files | ForEach-Object { Write-Output -InputObject (' - System32\{0}' -f (Split-Path -Path $_ -Leaf)) }
    $SysWOW64Files | ForEach-Object { Write-Output -InputObject (' - SysWOW64\{0}' -f (Split-Path -Path $_ -Leaf)) }

    Write-Output -InputObject ('Copying to temporary directory...')
    Copy-Item -Path $System32Files -Destination "$fTempFolder/System32/"
    Copy-Item -Path $SysWOW64Files -Destination "$fTempFolder/SysWOW64/"

    Write-Output -InputObject ('Finished gathering files for {0}' -f $GPU.FriendlyName)
    }
    Write-Output -InputObject ('All driver files have been collected, creating archive file')
    $Location = (Get-Location).Path
    Set-Location -Path (Split-Path -Path $fTempFolder -Parent)
    Compress-Archive -Path $fTempFolder -DestinationPath (Join-Path -Path $ArchiveFolder -ChildPath $ArchiveName) -CompressionLevel Fastest -Confirm:$false
    Set-Location -Path $Location
    Write-Output -InputObject ('GPU driver package has been created at path {0}\{1}' -f $ArchiveFolder, $ArchiveName)

    Write-Output -InputObject ('Cleaning up temporary directory {0}' -f $fTempFolder)
    Remove-Item -Recurse -Force -Path $fTempFolder
    }
    Write-Output -InputObject ('Driver package generation complete.')
    }
    }
    2 changes: 1 addition & 1 deletion readme.md
    Original file line number Diff line number Diff line change
    @@ -25,7 +25,7 @@ I am not in any way responsible for working this out, that credit goes to [Nimoa

    ## Using the driver gathering script

    You also have the option to use `Compress-GPUPDriverPackage.ps1`, which will gather the various files for you.
    You also have the option to use `New-GPUPDriverPackage.ps1`, which will gather the various files for you.

    1. Run the script on your host, in an admin PowerShell session. It will create `GPUPDriverPackage-[date].zip` in the current directory.
    2. Copy the .zip to your guest VM and extract
  16. neggles revised this gist Jun 24, 2021. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion readme.md
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    # Configuring GPU-PV on Hyper-V

    This works on a Windows 10 version 2004 machine with an nVidia GPU. It should work on AMD and Intel GPUs too, but they need slightly different driver files copied and I haven't had time to work out which yet.
    This works on a Windows Pro 10 version 2004 or newer machine, confirmed to function with nVidia GPUs and recent AMD GPUs. Intel should work as well, but why do you want that?

    I am not in any way responsible for working this out, that credit goes to [Nimoa at cfx.re](https://forum.cfx.re/t/running-fivem-in-a-hyper-v-vm-with-full-gpu-performance-for-testing-gpu-partitioning/1281205) and [reddit users on r/hyperv](https://www.reddit.com/r/HyperV/comments/huy09l/gpupv_in_hyperv_with_windows_10/)

  17. neggles revised this gist Jun 24, 2021. 2 changed files with 1 addition and 1 deletion.
    File renamed without changes.
    2 changes: 1 addition & 1 deletion readme.md
    Original file line number Diff line number Diff line change
    @@ -5,7 +5,7 @@ This works on a Windows 10 version 2004 machine with an nVidia GPU. It should wo
    I am not in any way responsible for working this out, that credit goes to [Nimoa at cfx.re](https://forum.cfx.re/t/running-fivem-in-a-hyper-v-vm-with-full-gpu-performance-for-testing-gpu-partitioning/1281205) and [reddit users on r/hyperv](https://www.reddit.com/r/HyperV/comments/huy09l/gpupv_in_hyperv_with_windows_10/)

    1. Make sure you have Hyper-V enabled (no way!) - there's a list of other features below that you *might* need, enable those if it doesn't work.
    1. Create a new VM using `New-VMWithPartitionGpu.ps1` below and install Windows 10 on it.
    1. Create a new VM using `New-GPUPVirtualMachine.ps1` below and install Windows 10 on it.
    1. Gather driver files for your guest using one of the two methods below:

    ## Gathering driver files manually
  18. neggles revised this gist Jun 24, 2021. 3 changed files with 170 additions and 122 deletions.
    161 changes: 161 additions & 0 deletions Compress-GPUPDriverPackage.ps1
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,161 @@
    <#
    .SYNOPSIS
    Create a GPU-P Guest driver package.
    .DESCRIPTION
    Gathers the necessary files for a GPU-P enabled Windows guest to run.
    .EXAMPLE
    Compress-GPUPDriverPackage -DestinationPath '.'
    .EXAMPLE
    Compress-GPUPDriverPackage -Filter 'nvidia' -DestinationPath '.'
    .INPUTS
    None.
    .OUTPUTS
    A driver package .zip
    .NOTES
    This has some mildly dodgy use of CIM cmdlets...
    .COMPONENT
    PSHyperTools
    .ROLE
    GPUP
    .FUNCTIONALITY
    Creates a guest driver package.
    #>
    [CmdletBinding(
    SupportsShouldProcess = $true,
    PositionalBinding = $true,
    HelpUri = 'http://www.microsoft.com/',
    ConfirmImpact = 'Low')]
    [Alias()]
    [OutputType([String])]
    Param (
    # Path to output directory.
    # If no file name is specified the filename will be GPUPDriverPackage-YYYYMMMDD.zip
    [Parameter(Mandatory = $false,
    HelpMessage = "Path to one or more locations.")]
    [Alias("PSPath", "Path")]
    [ValidateNotNullOrEmpty()]
    [string]
    $DestinationPath,

    # Device friendly name filter.
    # Only devices whose friendly names contain the supplied string will be processed
    [Parameter(Mandatory = $false,
    HelpMessage = "Only add drivers for devices whose friendly names contain the supplied string.")]
    [ValidateNotNullOrEmpty]
    [String]
    $Filter
    )

    begin {
    # make me a temporary folder, and assemble the structure
    $fTempFolder = Join-Path -Path $Env:TEMP -ChildPath "GPUPDriverPackage"
    (New-Item -ItemType Directory -Path "$fTempFolder/System32/HostDriverStore/FileRepository" -Force -ErrorAction SilentlyContinue | Out-Null)
    (New-Item -ItemType Directory -Path "$fTempFolder/SysWOW64" -Force -ErrorAction SilentlyContinue | Out-Null)

    # Do some processing on the DestinationPath
    if ($null -eq $DestinationPath) {
    $ArchiveName = ('GPUPDriverPackage-{0}.zip' -f $(Get-Date -UFormat '+%Y%b%d'))
    $ArchiveFolder = (Get-Location).Path
    } elseif ($DestinationPath -like '*.zip') {
    $ArchiveName = Split-Path -Path $DestinationPath -Leaf
    $ArchiveFolder = Split-Path -Path $DestinationPath -Parent
    }
    Write-Output -InputObject ('Driver package will be created:')
    Write-Output -InputObject (' Name: {0}' -f $ArchiveName)
    Write-Output -InputObject (' Path: {0}\{1}' -f $ArchiveFolder, $ArchiveName)
    }

    process {
    Write-Output -InputObject "Getting all GPU-P capable GPUs in the current system..."
    # requires some care as the command name changed in Server 2022/W10 21H2
    try {
    $PVCapableGPUs = Get-VMPartitionableGpu
    } catch {
    $PVCapableGPUs = Get-VMHostPartitionableGpu
    }

    # if we found no GPU-P capable GPUs, throw an exception
    if ($PVCapableGPUs.Count -lt 1) {
    throw [System.Management.Automation.ItemNotFoundException]::new('Did not find any GPU-P capable GPUs in this system.')
    } elseif ($PvGPUs.Count -gt 1) {
    Write-Warning -Message (
    ("You have {0} GPU-P capable GPUs in this system. `n" -f $PvGPUs.Count) +
    " At present, there is no way to control which one is assigned to a given VM.`n" +
    " Unless one of the available GPUs is an intel IGP, it is highly recommended`n" +
    " that you disable the GPU(s) you do not wish to use.`n")
    $choices = '&Yes', '&No'
    $question = 'Do you wish to proceed without disabling the extra GPU(s)?'
    if ($Host.UI.PromptForChoice('', $question, $choices, 1) -eq 1) {
    throw [System.Management.Automation.ActionPreferenceStopException]::new('User requested to cancel.')
    }
    }


    # Map each PVCapableGPU to the corresponding PnPDevice. Regex (mostly) extracts the InstanceId from the VMPartitionableGpu 'name' property.
    Write-Output -InputObject ('Mapping GPU-P capable GPUs to their corresponding PnPDevice objects')
    $InstanceExpr = [regex]::New('^\\\\\?\\(.+)#.*$')
    $TargetGPUs = $PVCapableGPUs.Name | ForEach-Object -Process {
    # I'm not proud of this, no sir
    Get-PnpDevice -InstanceId $InstanceExpr.Replace($_, '$1').Replace('#', '\')
    }

    # OK, now that we have some actual device names, we can filter them if we've been asked to
    if ($null -ne $Filter) {
    Write-Output -InputObject ('Applying filter "{0}" to device list...' -f $Filter)
    $TargetGPUs = $TargetGPUs | Where-Object { $_.FriendlyName -like ('*{0}*' -f $Filter) }
    }
    Write-Output -InputObject ('Will create driver package for {0} GPUs:' -f $TargetGPUs.Count)
    $TargetGPUs.FriendlyName | ForEach-Object { Write-Output -InputObject (' - {0}' -f $_) }

    # Last chance to turn back, traveler. Are you sure?
    if ($pscmdlet.ShouldProcess("Driver Package", "Create")) {
    Write-Output -InputObject ('The next few steps may take some time, depending on how many devices & driver packages are installed.')
    Write-Output -InputObject ('If the script appears hung, please give it a few minutes to complete before terminating.')
    # Get display class devices
    Write-Output -InputObject ('Gathering display device CIM objects...')
    $PnPEntities = Get-CimInstance -ClassName 'Win32_PnPEntity' | Where-Object { $_.Class -like 'Display' }
    # Get display class drivers
    Write-Output -InputObject ('Gathering display driver CIM objects...')
    $PnPSignedDrivers = Get-CimInstance -ClassName 'Win32_PnPSignedDriver' -Filter "DeviceClass = 'DISPLAY'"
    # next we have to get every PnPSignedDriverCIMDataFile, because Get-CimAssociatedInstance doesn't wanna play ball
    Write-Output -InputObject ('Gathering all driver file CIM objects... (this is the slow one. Blame Microsoft.)') # or me not understanding CIM i guess?
    $SignedDriverFiles = Get-CimInstance -ClassName 'Win32_PNPSignedDriverCIMDataFile' # -Verbose
    Write-Output -InputObject ('CIM objects gathered.')

    foreach ($GPU in $TargetGPUs) {
    Write-Output -InputObject ('Getting driver package for {0}' -f $GPU.FriendlyName)
    $PnPEntity = $PnPEntities.Where{ $_.InstanceId -eq $GPU.InstanceId }[0]
    $PnPSignedDriver = $PnPSignedDrivers.Where{ $_.DeviceId -eq $GPU.InstanceId }
    $SystemDriver = Get-CimAssociatedInstance -InputObject $PnPEntity -Association Win32_SystemDriverPNPEntity
    $DriverStoreFolder = Split-Path -Path $SystemDriver.PathName -Parent
    Write-Output -InputObject ('Found package {0}, copying DriverStore folder {1} to temporary directory' -f $PnPSignedDriver.InfName, (Split-Path $DriverStoreFolder -Leaf))
    Copy-Item -Path $DriverStoreFolder -Destination "$fTempFolder/System32/HostDriverStore/FileRepository/" -Recurse -Force

    # Get driver files from system32 etc and copy
    Write-Output -InputObject ('Done, getting files from System32 and SysWOW64')
    $DriverFiles = $SignedDriverFiles.Where{ $_.Antecedent.DeviceID -like $GPU.DeviceID }.Dependent.Name
    $System32Files = $DriverFiles.Where{ (Split-Path -Path $_ -Parent) -like "$Env:SYSTEMROOT\System32" }
    $SysWOW64Files = $DriverFiles.Where{ (Split-Path -Path $_ -Parent) -like "$Env:SYSTEMROOT\SysWOW64" }

    Write-Output -InputObject ('Found {0} files:' -f ($System32Files.Count + $SysWOW64Files.Count))
    $System32Files | ForEach-Object { Write-Output -InputObject (' - System32\{0}' -f (Split-Path -Path $_ -Leaf)) }
    $SysWOW64Files | ForEach-Object { Write-Output -InputObject (' - SysWOW64\{0}' -f (Split-Path -Path $_ -Leaf)) }

    Write-Output -InputObject ('Copying to temporary directory...')
    Copy-Item -Path $System32Files -Destination "$fTempFolder/System32/"
    Copy-Item -Path $SysWOW64Files -Destination "$fTempFolder/SysWOW64/"

    Write-Output -InputObject ('Finished gathering files for {0}' -f $GPU.FriendlyName)
    }
    Write-Output -InputObject ('All driver files have been collected, creating archive file')
    $Location = (Get-Location).Path
    Set-Location -Path (Split-Path -Path $fTempFolder -Parent)
    Compress-Archive -Path $fTempFolder -DestinationPath (Join-Path -Path $ArchiveFolder -ChildPath $ArchiveName) -CompressionLevel Fastest -Confirm:$false
    Set-Location -Path $Location
    Write-Output -InputObject ('GPU driver package has been created at path {0}\{1}' -f $ArchiveFolder, $ArchiveName)

    Write-Output -InputObject ('Cleaning up temporary directory {0}' -f $fTempFolder)
    Remove-Item -Recurse -Force -Path $fTempFolder
    }
    Write-Output -InputObject ('Driver package generation complete.')
    }
    118 changes: 0 additions & 118 deletions Make-PVGpuDriverZip.ps1
    Original file line number Diff line number Diff line change
    @@ -1,118 +0,0 @@
    # set work dir
    $sScriptRoot = if ($PSScriptRoot) { $PSScriptRoot } else { $PWD }
    $WorkDir = Join-Path -Path $sScriptRoot -ChildPath 'temp/GPUPDrivers'

    # Get all GPU-P capable GPUs in this system.
    $PvGPUs = (Get-VMPartitionableGpu).Name

    # warn user if there's more than one GPU
    if ($PvGPUs.Count -lt 1) {
    throw [System.Management.Automation.ItemNotFoundException]::new('Did not find any GPU-P capable GPUs in this system.')
    } elseif ($PvGPUs.Count -gt 1) {
    Write-Warning -Message (
    ("You have {0} GPU-P capable GPUs in this system. `n" -f $PvGPUs.Count) +
    " At present, there is no way to control which one is assigned to a given VM.`n" +
    " Unless one of the available GPUs is an intel IGP, it is highly recommended`n" +
    " that you disable the GPU(s) you do not wish to use.`n")
    $choices = '&Yes', '&No'
    $question = 'Do you wish to proceed without disabling the extra GPU(s)?'
    if ($Host.UI.PromptForChoice('', $question, $choices, 1) -eq 1) { throw [System.Management.Automation.ActionPreferenceStopException]::new('User requested to cancel.') }
    }

    Write-Output -InputObject "Getting device information for each GPU-P capable GPU..."
    # this expr strips the device instance ID out of the VMPartitionableGpu 'name' param
    $InstanceExpr = [regex]::New('^\\\\\?\\(.+)#.*$')
    # get all partitionable GPUs, and find their actual devices
    $PvCapableGPUs = $PvGPUs | ForEach-Object -Process { Get-PnpDevice -InstanceId $InstanceExpr.Replace($_, '$1').Replace('#', '\') }

    # Get CIM instances for all video controllers and PnP Entities
    $VideoControllers = Get-CimInstance -ClassName Win32_VideoController
    $PnPEntities = Get-CimInstance -ClassName Win32_PnPEntity

    # lets make a driver zip!

    #$DriverFiles = Import-PowerShellDataFile -Path "$sScriptRoot\DriverFiles.psd1"
    $DriverFiles = @{
    nVidia = @{
    System32 = @(
    'nvapi64.dll'
    'nvcuda.dll'
    'nvcuvid.dll'
    'nvdispco*.dll'
    'nvdispgenco*.dll'
    'nvEncodeAPI64.dll'
    'NvFBC64.dll'
    'NvIFR64.dll'
    'NvIFROpenGL.dll'
    'nvofapi64.dll'
    'OpenCL.dll'
    'vulkan-1.dll'
    'vulkan-1-*.dll'

    )
    SysWOW64 = @(
    'nvapi.dll'
    'nvcuda.dll'
    'nvcuvid.dll'
    'nvEncodeAPI.dll'
    'NvFBC.dll'
    'NvIFR.dll'
    'NvIFROpenGL.dll'
    'nvofapi.dll'
    'OpenCL.dll'
    'vulkan-1.dll'
    'vulkan-1-*.dll'
    )
    }
    }

    # make work dirs if not exist
    (New-Item -ItemType Directory -Path "$WorkDir/System32/HostDriverStore/FileRepository" -Force -ErrorAction SilentlyContinue | Out-Null)
    (New-Item -ItemType Directory -Path "$WorkDir/SysWOW64" -Force -ErrorAction SilentlyContinue | Out-Null)


    foreach ($PvCapableGPU in $PvCapableGPUs) {
    # Get the CIM instance for this GPU
    $VideoController = $VideoControllers.Where{ $_.PNPDeviceID -eq $PvCapableGPU.InstanceId }[0]
    $PnPEntity = $PnPEntities.Where{ $_.InstanceId -eq $PvCapableGPU.InstanceId }[0]
    $SystemDriver = Get-CimAssociatedInstance -InputObject $PnPEntity -Association Win32_SystemDriverPNPEntity

    Write-Output -InputObject ('Gathering driver files for GPU "{0}"...' -f $PnPEntity.FriendlyName)

    # am it nvidia or AMD?
    $GPUVendor = switch -wildcard ($PnPEntity.FriendlyName) {
    '*nvidia*' { "nVidia" }
    '*intel*' { "Intel" }
    '*AMD*' { "AMD" }
    '*ATI*' { "AMD" }
    Default { throw }
    }

    # now we're going to be very dirty. lets get the driverstore path
    $DriverFolder = Split-Path -Path $SystemDriver.PathName -Parent

    # copy all the files from the driver into the HostDriverStore we're building
    Copy-Item -Path $DriverFolder -Destination "$WorkDir/System32/HostDriverStore/FileRepository" -Recurse -Force

    # this is the tricky part... tbh i am cheating here
    $DriverFiles.$GPUVendor.System32 | ForEach-Object -Process {
    Copy-Item -Path ('{0}\System32\{1}' -f $Env:SystemRoot, $_) -Destination "$WorkDir/System32/" -Force
    }
    $DriverFiles.$GPUVendor.SysWOW64 | ForEach-Object -Process {
    Copy-Item -Path ('{0}\SysWOW64\{1}' -f $Env:SystemRoot, $_) -Destination "$WorkDir/SysWOW64/" -Force
    }
    Write-Output -InputObject "Files gathered for GPU."
    }

    Write-Output -InputObject "Creating .zip..."

    Set-Location -Path (Split-Path -Path $WorkDir -Parent)
    Compress-Archive -Path GPUPDrivers -DestinationPath "$sScriptRoot/GPUPDrivers.zip" -CompressionLevel Fastest -Confirm:$false -Force
    Set-Location -Path $sScriptRoot

    Write-Output -InputObject "Cleaning up..."
    Remove-Item -Recurse -Force -Path "$sScriptRoot/temp"

    Write-Output -InputObject "Driver zip created, extract it and copy the contents into C:\Windows\ on the guest VM."


    13 changes: 9 additions & 4 deletions readme.md
    Original file line number Diff line number Diff line change
    @@ -18,16 +18,21 @@ I am not in any way responsible for working this out, that credit goes to [Nimoa
    (You will likely need to create the ``HostDriverStore`` and ``FileRepository`` directories)
    2. Copy the two driver folders you collected from your host to this path.
    3. Copy ``nvapi64.dll`` to ``C:\Windows\system32\`` on the guest as well, and a few others - look at 'driver details' for your GPU in device manager to work out which ones
    5. Shut down the guest VM, make sure checkpoints are disabled and automatic stop action is set to 'Turn Off'
    5. Shut down the guest VM,
    6. Make sure the VM's checkpoints are disabled, and automatic stop action is set to 'Turn Off'
    (The VM creation script covers this, but never hurts to be sure)
    6. Boot your VM, and enjoy your hardware acceleration!

    ## Using the driver gathering script

    You also have the option to use `Make-PvGpuDriverZip.ps1` which will gather the various files for you, if you have an nVidia GPU.
    I've not yet worked out how to get the filename list device manager presents without abuse of devcon.exe or similar.
    You also have the option to use `Compress-GPUPDriverPackage.ps1`, which will gather the various files for you.

    Just run the script and it'll make you a GPUPDrivers.zip, extract it and dump the contents of the GPUPDrivers folder into C:\Windows\ then reboot.
    1. Run the script on your host, in an admin PowerShell session. It will create `GPUPDriverPackage-[date].zip` in the current directory.
    2. Copy the .zip to your guest VM and extract
    3. Copy the contents of the extracted `GPUPDriverPackage` folder into `C:\Windows\`
    4. Reboot the guest.

    This will work for nVidia GPUs, at least, and should also work for Intel/AMD ones.

    ----
    Windows features that must be enabled:
  19. neggles revised this gist Jun 23, 2021. 1 changed file with 5 additions and 4 deletions.
    9 changes: 5 additions & 4 deletions readme.md
    Original file line number Diff line number Diff line change
    @@ -4,11 +4,12 @@ This works on a Windows 10 version 2004 machine with an nVidia GPU. It should wo

    I am not in any way responsible for working this out, that credit goes to [Nimoa at cfx.re](https://forum.cfx.re/t/running-fivem-in-a-hyper-v-vm-with-full-gpu-performance-for-testing-gpu-partitioning/1281205) and [reddit users on r/hyperv](https://www.reddit.com/r/HyperV/comments/huy09l/gpupv_in_hyperv_with_windows_10/)

    ## Gathering driver files for the guest manually.

    1. Make sure you have Hyper-V enabled (no way!) - there's a list of other features below that you *might* need, enable those if it doesn't work.
    2. Create a new VM using `New-VMWithPartitionGpu.ps1` below and install Windows 10 on it.
    3. On your host system:
    1. Create a new VM using `New-VMWithPartitionGpu.ps1` below and install Windows 10 on it.
    1. Gather driver files for your guest using one of the two methods below:

    ## Gathering driver files manually
    1. On your host system:
    1. Browse to ``C:\Windows\system32\DriverStore\FileRepository``
    2. Find the ``nvdispsi.inf_amd64_<guid>`` and/or ``nvltsi.inf_amd64_<guid>`` folders, and copy them to a temporary folder
    3. Grab ``nvapi64.dll`` from ``C:\Windows\system32`` as well
  20. neggles revised this gist Jun 23, 2021. 1 changed file with 14 additions and 8 deletions.
    22 changes: 14 additions & 8 deletions readme.md
    Original file line number Diff line number Diff line change
    @@ -1,29 +1,35 @@
    ## Configuring GPU Partitioning / GPU-PV on Hyper-V
    # Configuring GPU-PV on Hyper-V

    This works on a Windows 10 version 2004 machine with an nVidia GPU. It should work on AMD and Intel GPUs too, but they need slightly different driver files copied and I haven't had time to work out which yet.

    I am not in any way responsible for working this out, that credit goes to [Nimoa at cfx.re](https://forum.cfx.re/t/running-fivem-in-a-hyper-v-vm-with-full-gpu-performance-for-testing-gpu-partitioning/1281205) and [reddit users on r/hyperv](https://www.reddit.com/r/HyperV/comments/huy09l/gpupv_in_hyperv_with_windows_10/)

    Steps:
    ## Gathering driver files for the guest manually.

    1. Make sure you have Hyper-V enabled (no way!) - there's a list of other features below that you *might* need, enable those if it doesn't work.
    2. Create a new VM using 'New-GPUPartitionVM.ps1' below and install Windows 10 on it.
    2. Create a new VM using `New-VMWithPartitionGpu.ps1` below and install Windows 10 on it.
    3. On your host system:
    1. Browse to ``C:\Windows\system32\DriverStore\FileRepository``
    2. Find the ``nvdispsi.inf_amd64_<guid>`` and ``nvltsi.inf_amd64_<guid>`` folders, and copy them to a temporary folder
    2. Find the ``nvdispsi.inf_amd64_<guid>`` and/or ``nvltsi.inf_amd64_<guid>`` folders, and copy them to a temporary folder
    3. Grab ``nvapi64.dll`` from ``C:\Windows\system32`` as well
    4. On your guest system:
    1. Browse to ``C:\Windows\system32\HostDriverStore\FileRepository``
    (You will likely need to create the ``HostDriverStore`` and ``FileRepository`` directories)
    2. Copy the two driver folders you collected from your host to this path.
    3. Copy ``nvapi64.dll`` to ``C:\Windows\system32\`` on the guest as well
    3. Copy ``nvapi64.dll`` to ``C:\Windows\system32\`` on the guest as well, and a few others - look at 'driver details' for your GPU in device manager to work out which ones
    5. Shut down the guest VM, make sure checkpoints are disabled and automatic stop action is set to 'Turn Off'
    (The VM creation script covers this, but never hurts to be sure)
    6. Boot your VM, and enjoy your hardware acceleration!

    ## Using the driver gathering script

    You also have the option to use `Make-PvGpuDriverZip.ps1` which will gather the various files for you, if you have an nVidia GPU.
    I've not yet worked out how to get the filename list device manager presents without abuse of devcon.exe or similar.

    Just run the script and it'll make you a GPUPDrivers.zip, extract it and dump the contents of the GPUPDrivers folder into C:\Windows\ then reboot.

    ----
    Windows features I had enabled:
    Windows features that must be enabled:
    * Hyper-V and all associated subfeatures
    * Windows Containers - don't know if you actually need this one
    * Windows Sandbox
    * Windows Subsystem for Linux
    * Virtual Machine Platform
  21. neggles revised this gist Jun 23, 2021. 2 changed files with 120 additions and 2 deletions.
    118 changes: 118 additions & 0 deletions Make-PVGpuDriverZip.ps1
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,118 @@
    # set work dir
    $sScriptRoot = if ($PSScriptRoot) { $PSScriptRoot } else { $PWD }
    $WorkDir = Join-Path -Path $sScriptRoot -ChildPath 'temp/GPUPDrivers'

    # Get all GPU-P capable GPUs in this system.
    $PvGPUs = (Get-VMPartitionableGpu).Name

    # warn user if there's more than one GPU
    if ($PvGPUs.Count -lt 1) {
    throw [System.Management.Automation.ItemNotFoundException]::new('Did not find any GPU-P capable GPUs in this system.')
    } elseif ($PvGPUs.Count -gt 1) {
    Write-Warning -Message (
    ("You have {0} GPU-P capable GPUs in this system. `n" -f $PvGPUs.Count) +
    " At present, there is no way to control which one is assigned to a given VM.`n" +
    " Unless one of the available GPUs is an intel IGP, it is highly recommended`n" +
    " that you disable the GPU(s) you do not wish to use.`n")
    $choices = '&Yes', '&No'
    $question = 'Do you wish to proceed without disabling the extra GPU(s)?'
    if ($Host.UI.PromptForChoice('', $question, $choices, 1) -eq 1) { throw [System.Management.Automation.ActionPreferenceStopException]::new('User requested to cancel.') }
    }

    Write-Output -InputObject "Getting device information for each GPU-P capable GPU..."
    # this expr strips the device instance ID out of the VMPartitionableGpu 'name' param
    $InstanceExpr = [regex]::New('^\\\\\?\\(.+)#.*$')
    # get all partitionable GPUs, and find their actual devices
    $PvCapableGPUs = $PvGPUs | ForEach-Object -Process { Get-PnpDevice -InstanceId $InstanceExpr.Replace($_, '$1').Replace('#', '\') }

    # Get CIM instances for all video controllers and PnP Entities
    $VideoControllers = Get-CimInstance -ClassName Win32_VideoController
    $PnPEntities = Get-CimInstance -ClassName Win32_PnPEntity

    # lets make a driver zip!

    #$DriverFiles = Import-PowerShellDataFile -Path "$sScriptRoot\DriverFiles.psd1"
    $DriverFiles = @{
    nVidia = @{
    System32 = @(
    'nvapi64.dll'
    'nvcuda.dll'
    'nvcuvid.dll'
    'nvdispco*.dll'
    'nvdispgenco*.dll'
    'nvEncodeAPI64.dll'
    'NvFBC64.dll'
    'NvIFR64.dll'
    'NvIFROpenGL.dll'
    'nvofapi64.dll'
    'OpenCL.dll'
    'vulkan-1.dll'
    'vulkan-1-*.dll'

    )
    SysWOW64 = @(
    'nvapi.dll'
    'nvcuda.dll'
    'nvcuvid.dll'
    'nvEncodeAPI.dll'
    'NvFBC.dll'
    'NvIFR.dll'
    'NvIFROpenGL.dll'
    'nvofapi.dll'
    'OpenCL.dll'
    'vulkan-1.dll'
    'vulkan-1-*.dll'
    )
    }
    }

    # make work dirs if not exist
    (New-Item -ItemType Directory -Path "$WorkDir/System32/HostDriverStore/FileRepository" -Force -ErrorAction SilentlyContinue | Out-Null)
    (New-Item -ItemType Directory -Path "$WorkDir/SysWOW64" -Force -ErrorAction SilentlyContinue | Out-Null)


    foreach ($PvCapableGPU in $PvCapableGPUs) {
    # Get the CIM instance for this GPU
    $VideoController = $VideoControllers.Where{ $_.PNPDeviceID -eq $PvCapableGPU.InstanceId }[0]
    $PnPEntity = $PnPEntities.Where{ $_.InstanceId -eq $PvCapableGPU.InstanceId }[0]
    $SystemDriver = Get-CimAssociatedInstance -InputObject $PnPEntity -Association Win32_SystemDriverPNPEntity

    Write-Output -InputObject ('Gathering driver files for GPU "{0}"...' -f $PnPEntity.FriendlyName)

    # am it nvidia or AMD?
    $GPUVendor = switch -wildcard ($PnPEntity.FriendlyName) {
    '*nvidia*' { "nVidia" }
    '*intel*' { "Intel" }
    '*AMD*' { "AMD" }
    '*ATI*' { "AMD" }
    Default { throw }
    }

    # now we're going to be very dirty. lets get the driverstore path
    $DriverFolder = Split-Path -Path $SystemDriver.PathName -Parent

    # copy all the files from the driver into the HostDriverStore we're building
    Copy-Item -Path $DriverFolder -Destination "$WorkDir/System32/HostDriverStore/FileRepository" -Recurse -Force

    # this is the tricky part... tbh i am cheating here
    $DriverFiles.$GPUVendor.System32 | ForEach-Object -Process {
    Copy-Item -Path ('{0}\System32\{1}' -f $Env:SystemRoot, $_) -Destination "$WorkDir/System32/" -Force
    }
    $DriverFiles.$GPUVendor.SysWOW64 | ForEach-Object -Process {
    Copy-Item -Path ('{0}\SysWOW64\{1}' -f $Env:SystemRoot, $_) -Destination "$WorkDir/SysWOW64/" -Force
    }
    Write-Output -InputObject "Files gathered for GPU."
    }

    Write-Output -InputObject "Creating .zip..."

    Set-Location -Path (Split-Path -Path $WorkDir -Parent)
    Compress-Archive -Path GPUPDrivers -DestinationPath "$sScriptRoot/GPUPDrivers.zip" -CompressionLevel Fastest -Confirm:$false -Force
    Set-Location -Path $sScriptRoot

    Write-Output -InputObject "Cleaning up..."
    Remove-Item -Recurse -Force -Path "$sScriptRoot/temp"

    Write-Output -InputObject "Driver zip created, extract it and copy the contents into C:\Windows\ on the guest VM."


    4 changes: 2 additions & 2 deletions New-GPUPartitionVM.ps1 → New-VMWithPartitionGpu.ps1
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,7 @@
    $Config = @{
    VMName = 'GPU-VM'
    VMName = 'GPU-VM' # pick a NAME BRO
    VMMemory = 8192MB # Set appropriately
    VMCores = 4
    VMCores = 4 # likewise
    MinRsrc = 80000000 # We don't really know what these values do - my GPU reports 100,000,000 available units...
    MaxRsrc = 100000000 # I suspect in the current implementation they do nothing, but this is known to work - play around if you like!
    OptimalRsrc = 100000000
  22. neggles revised this gist Aug 11, 2020. 1 changed file with 4 additions and 2 deletions.
    6 changes: 4 additions & 2 deletions New-GPUPartitionVM.ps1
    Original file line number Diff line number Diff line change
    @@ -10,23 +10,25 @@ $Config = @{
    try {
    # Create VM if needed
    if (-not (Get-VM -Name $Config.VMName)) {
    New-VM @{
    $NewVM = @{
    Name = $Config.VMName
    MemoryStartupBytes = $Config.VMMemory
    Generation = 2
    Version = 9.2 # Latest available virtual hardware version
    }
    New-VM @NewVM
    }

    # Enable VM features required for this to work
    Set-VM @{
    $SetParams = @{
    VMName = $Config.VMName
    GuestControlledCacheTypes = $true
    LowMemoryMappedIoSpace = 1Gb
    HighMemoryMappedIoSpace = 32GB
    AutomaticStopAction = 'TurnOff'
    CheckpointType = 'Disabled'
    }
    Set-VM @SetParams
    # Disable secure boot
    Set-VMFirmware -VMName $Config.VMName -EnableSecureBoot 'Off'

  23. neggles revised this gist Aug 5, 2020. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions readme.md
    Original file line number Diff line number Diff line change
    @@ -2,6 +2,8 @@

    This works on a Windows 10 version 2004 machine with an nVidia GPU. It should work on AMD and Intel GPUs too, but they need slightly different driver files copied and I haven't had time to work out which yet.

    I am not in any way responsible for working this out, that credit goes to [Nimoa at cfx.re](https://forum.cfx.re/t/running-fivem-in-a-hyper-v-vm-with-full-gpu-performance-for-testing-gpu-partitioning/1281205) and [reddit users on r/hyperv](https://www.reddit.com/r/HyperV/comments/huy09l/gpupv_in_hyperv_with_windows_10/)

    Steps:
    1. Make sure you have Hyper-V enabled (no way!) - there's a list of other features below that you *might* need, enable those if it doesn't work.
    2. Create a new VM using 'New-GPUPartitionVM.ps1' below and install Windows 10 on it.
  24. neggles revised this gist Aug 5, 2020. 3 changed files with 84 additions and 20 deletions.
    57 changes: 57 additions & 0 deletions New-GPUPartitionVM.ps1
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,57 @@
    $Config = @{
    VMName = 'GPU-VM'
    VMMemory = 8192MB # Set appropriately
    VMCores = 4
    MinRsrc = 80000000 # We don't really know what these values do - my GPU reports 100,000,000 available units...
    MaxRsrc = 100000000 # I suspect in the current implementation they do nothing, but this is known to work - play around if you like!
    OptimalRsrc = 100000000
    }

    try {
    # Create VM if needed
    if (-not (Get-VM -Name $Config.VMName)) {
    New-VM @{
    Name = $Config.VMName
    MemoryStartupBytes = $Config.VMMemory
    Generation = 2
    Version = 9.2 # Latest available virtual hardware version
    }
    }

    # Enable VM features required for this to work
    Set-VM @{
    VMName = $Config.VMName
    GuestControlledCacheTypes = $true
    LowMemoryMappedIoSpace = 1Gb
    HighMemoryMappedIoSpace = 32GB
    AutomaticStopAction = 'TurnOff'
    CheckpointType = 'Disabled'
    }
    # Disable secure boot
    Set-VMFirmware -VMName $Config.VMName -EnableSecureBoot 'Off'

    # Parameters for vAdapter
    $GPUParams = @{
    VMName = $Config.VMName
    MinPartitionVRAM = $Config.MinRsrc
    MaxPartitionVRAM = $Config.MaxRsrc
    OptimalPartitionVRAM = $Config.OptimalRsrc
    MinPartitionEncode = $Config.MinRsrc
    MaxPartitionEncode = $Config.MaxRsrc
    OptimalPartitionEncode = $Config.OptimalRsrc
    MinPartitionDecode = $Config.MinRsrc
    MaxPartitionDecode = $Config.MaxRsrc
    OptimalPartitionDecode = $Config.OptimalRsrc
    MinPartitionCompute = $Config.MinRsrc
    MaxPartitionCompute = $Config.MaxRsrc
    OptimalPartitionCompute = $Config.OptimalRsrc
    }
    # Add adapter if not present, update if present
    if (-not (Get-VMGpuPartitionAdapter -VMName $Config.VMName)) {
    Add-VMGpuPartitionAdapter @GPUParams
    } else {
    Set-VMGpuPartitionAdapter @GPUParams
    }
    } catch {
    throw $PSItem
    }
    20 changes: 0 additions & 20 deletions add-vmgpupartitionadapter.ps1
    Original file line number Diff line number Diff line change
    @@ -1,20 +0,0 @@
    $Min = 80000000
    $Max = 100000000
    $Optimal = 100000000
    $Params = @{
    VMName = 'NameOfTheVM'
    MinPartitionVRAM = $Min
    MaxPartitionVRAM = $Max
    OptimalPartitionVRAM = $Optimal
    MinPartitionEncode = $Min
    MaxPartitionEncode = $Max
    OptimalPartitionEncode = $Optimal
    MinPartitionDecode = $Min
    MaxPartitionDecode = $Max
    OptimalPartitionDecode = $Optimal
    MinPartitionCompute = $Min
    MaxPartitionCompute = $Max
    OptimalPartitionCompute = $Optimal
    }
    Add-VMGpuPartitionAdapter @Params
    Set-VM –VMName $Params.VMName -GuestControlledCacheTypes $true -LowMemoryMappedIoSpace 1Gb –HighMemoryMappedIoSpace 32GB
    27 changes: 27 additions & 0 deletions readme.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,27 @@
    ## Configuring GPU Partitioning / GPU-PV on Hyper-V

    This works on a Windows 10 version 2004 machine with an nVidia GPU. It should work on AMD and Intel GPUs too, but they need slightly different driver files copied and I haven't had time to work out which yet.

    Steps:
    1. Make sure you have Hyper-V enabled (no way!) - there's a list of other features below that you *might* need, enable those if it doesn't work.
    2. Create a new VM using 'New-GPUPartitionVM.ps1' below and install Windows 10 on it.
    3. On your host system:
    1. Browse to ``C:\Windows\system32\DriverStore\FileRepository``
    2. Find the ``nvdispsi.inf_amd64_<guid>`` and ``nvltsi.inf_amd64_<guid>`` folders, and copy them to a temporary folder
    3. Grab ``nvapi64.dll`` from ``C:\Windows\system32`` as well
    4. On your guest system:
    1. Browse to ``C:\Windows\system32\HostDriverStore\FileRepository``
    (You will likely need to create the ``HostDriverStore`` and ``FileRepository`` directories)
    2. Copy the two driver folders you collected from your host to this path.
    3. Copy ``nvapi64.dll`` to ``C:\Windows\system32\`` on the guest as well
    5. Shut down the guest VM, make sure checkpoints are disabled and automatic stop action is set to 'Turn Off'
    (The VM creation script covers this, but never hurts to be sure)
    6. Boot your VM, and enjoy your hardware acceleration!

    ----
    Windows features I had enabled:
    * Hyper-V and all associated subfeatures
    * Windows Containers - don't know if you actually need this one
    * Windows Sandbox
    * Windows Subsystem for Linux
    * Virtual Machine Platform
  25. neggles created this gist Aug 4, 2020.
    20 changes: 20 additions & 0 deletions add-vmgpupartitionadapter.ps1
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,20 @@
    $Min = 80000000
    $Max = 100000000
    $Optimal = 100000000
    $Params = @{
    VMName = 'NameOfTheVM'
    MinPartitionVRAM = $Min
    MaxPartitionVRAM = $Max
    OptimalPartitionVRAM = $Optimal
    MinPartitionEncode = $Min
    MaxPartitionEncode = $Max
    OptimalPartitionEncode = $Optimal
    MinPartitionDecode = $Min
    MaxPartitionDecode = $Max
    OptimalPartitionDecode = $Optimal
    MinPartitionCompute = $Min
    MaxPartitionCompute = $Max
    OptimalPartitionCompute = $Optimal
    }
    Add-VMGpuPartitionAdapter @Params
    Set-VM –VMName $Params.VMName -GuestControlledCacheTypes $true -LowMemoryMappedIoSpace 1Gb –HighMemoryMappedIoSpace 32GB