Updated 12th August 2024:
- Post install code snippets updated
- Article wording updated
With the move from the Standard driver model to the DCH (Declarative Componentized Hardware) driver model implemented from GRID 15.0 (Windows driver version 527.41) and above, the NVIDIA Control Panel app is now distributed through the Microsoft Store as an Appx Package. Even though the base Appx Package is added as part of the driver package install, it might fail to be installed per user if the Microsoft Store is disabled, the system is not connected to the Internet, or installation of apps from the Microsoft Store is blocked by your system settings. Other than the Administrative user that installed the driver, this is not installed by default for other users using Windows Server 2019 and Windows 10 LTSC 2019 (version 1809).
Furthermore, even when the Appx Package is registered, users and local Administrators cannot directly launch the nvcplui.exe and NvGpuUtilization.exe tools.
They will receive an “Access to the path ‘C:\Program Files\WindowsApps’ is denied'” error when launching from a command line. An example of the path:
C:\Program Files\WindowsApps\NVIDIACorp.NVIDIAControlPanel_8.1.963.0_x64__56jybvy8sckqj\nvcplui.exe C:\Program Files\WindowsApps\NVIDIACorp.NVIDIAControlPanel_8.1.963.0_x64__56jybvy8sckqj\NvGpuUtilization.exe
They will receive a “Windows cannot access the specified device, path, or file. You may not have the appropriate permissions to access the item.” error when launching when via File Explorer.
This is by design for the WindowsApps folder. Even though you may read about others changing the permissions on this folder structure, I don’t feel it’s appropriate, especially from an Enterprise Support point of view. There are reasons why we shouldn’t change these Operating System permissions that are locked down for a purpose.
The new correct way to start the Microsoft Store NVIDIA Control Panel (nvcplui.exe) app is via the following command line:
explorer.exe shell:appsFolder\<PackageFamilyName>!NVIDIACorp.NVIDIAControlPanel
The PackageFamilyName can be found from the output of the following cmdlet: Get-AppxPackage -Name “Nvidia*”
Although not ideal, I can deal with that and update my tools as needed. However, as per NVIDIA bug #4644253 as referenced in Case #00697597, the NvGpuUtilization.exe tool cannot be launched this way because an application ID is missing from the AppxManifest.xml.
This was an “Are you kidding me?” moment for me. For all their profits and brilliant Engineering, they certainly didn’t put enough thought into re-engineering how the Control Panel and supporting tools should work. You can see in the following screen shot that the NvGpuUtilization.exe tool is installed as part of the Appx Package, but you can’t launch it. What’s the point of that? How did that even pass development and QA testing?
To achieve the outcome needed we do two things if the OS Version is 10.0.17763 or greater and driver build is 527.41 or greater:
- Post driver install script
- User logon script
1. Post Driver Install Script
I add the following two snippets of code to the driver deployment script to run AFTER the driver has installed.
The first snippet of code will copy the “new” Control Panel items back to the “C:\Program Files\NVIDIA Corporation\Control Panel Client” folder at the time of installation. This will provide users with familiarity, and continue to allow any 3rd party tools I and others have developed to directly execute these tools.
$NVIDIAControlPanelFolder = $null Try { # We can use either Get-AppxPackage or the Get-ChildItem cmdlet to get the exact installation location of the Appx Package $NVIDIAControlPanelFolder = Get-AppxPackage -Name "NVIDIACorp.NVIDIAControlPanel*" | Select-Object -ExpandProperty InstallLocation #$NVIDIAControlPanelFolder = Get-ChildItem -Path "${env:ProgramFiles}\WindowsApps" -ErrorAction "Stop" | Where-object {$_.psIsContainer -eq $true -AND $_.Name -Like "NVIDIACorp.NVIDIAControlPanel*"} | Select-Object -ExpandProperty FullName } Catch [System.Exception]{ write-warning "$($Error[0].Exception.Message)" -verbose } If (!([String]::IsNullOrEmpty($NVIDIAControlPanelFolder))) { # If we copy it in too quickly after the install, they will be deleted by the nvdisplay.container.exe process as per the following output from the LOG.NVDisplay.Container.exe.log # DEBUG: [UXDriver.ApiX.Features.StartupFeatures] 5241@Nvidia::UXDriver::ApiX::Features::StartupFeatures::DeleteControlPanelClient : Entered. # The nvdisplay.container.exe gets it's list of default processes from the "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\nvlddmkm\Global\Startup" registry key. # The RemoveControlPanelClient subkey is the specific key of concern here. # Key: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\nvlddmkm\Global\Startup\RemoveControlPanelClient # Type: DWORD # Vale: (Default) # Data: 1 # Once the 'Default' value is set to 1, you can then copy the control panel files back in and they won't be deleted. # The following code simply waits for the value to be set to 1 before proceeding: $Key = "HKLM:\SYSTEM\CurrentControlSet\Services\nvlddmkm\Global\Startup\RemoveControlPanelClient" $Value = "(Default)" $Data = 1 Write-Verbose "Checking to see if the RemoveControlPanelClient default value has been set to 1" -Verbose $KeyExists = $False $ValueExists = $False Do { $ErrorActionPreference = "stop" try { Get-Item -Path "$Key" | Out-Null $KeyExists = $true } Catch [System.Exception]{ write-warning "$($Error[0].Exception.Message)" -verbose } $ErrorActionPreference = "Continue" If ($KeyExists) { $ErrorActionPreference = "stop" try { If ((Get-ItemProperty -Path "$Key" | Select-Object -ExpandProperty "$Value") -eq $Data) { Write-Verbose "- The RemoveControlPanelClient default value is set to 1." -Verbose $ValueExists = $True } } Catch [System.Exception]{ write-warning "$($Error[0].Exception.Message)" -verbose } $ErrorActionPreference = "Continue" } If ($ValueExists -eq $False) { Write-Verbose "- waiting for the RemoveControlPanelClient default value to change to 1." -Verbose Start-Sleep -Seconds 1 } } Until ($ValueExists -eq $true) If (-not(TEST-PATH "${env:ProgramFiles}\NVIDIA Corporation\Control Panel Client")) { New-Item -Path "${env:ProgramFiles}\NVIDIA Corporation\Control Panel Client" -ItemType Directory | out-null } Copy-Item -Path "$NVIDIAControlPanelFolder\nvcplui.exe" -Destination "${env:ProgramFiles}\NVIDIA Corporation\Control Panel Client\" -Recurse -Force -Verbose Copy-Item -Path "$NVIDIAControlPanelFolder\nvcpluir.dll" -Destination "${env:ProgramFiles}\NVIDIA Corporation\Control Panel Client\" -Recurse -Force -Verbose Copy-Item -Path "$NVIDIAControlPanelFolder\NvGpuUtilization.exe" -Destination "${env:ProgramFiles}\NVIDIA Corporation\Control Panel Client\" -Recurse -Force -Verbose Copy-Item -Path "$NVIDIAControlPanelFolder\NvStereoUtilityOGL.exe" -Destination "${env:ProgramFiles}\NVIDIA Corporation\Control Panel Client\" -Recurse -Force -Verbose }
As documented in the above code, if we copy them in too quickly after the install, they will be deleted by the nvdisplay.container.exe process during it’s initialisation phase as per the following output from the LOG.NVDisplay.Container.exe.log.
DEBUG: [UXDriver.ApiX.Features.StartupFeatures] 5241@Nvidia::UXDriver::ApiX::Features::StartupFeatures::DeleteControlPanelClient : Entered.
I found that the nvdisplay.container.exe gets its list of default processes from the “HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\nvlddmkm\Global\Startup” registry key. The RemoveControlPanelClient subkey is the specific key of concern here.
Key: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\nvlddmkm\Global\Startup\RemoveControlPanelClient Type: DWORD Vale: (Default) Data: 1
Once the ‘Default’ value is set to 1, you can then copy the control panel files back in and they won’t be deleted again. My code above allows for this.
The second snippet of code will create a new registry key I made up called NVControlPanelAppXInformation under the “HKEY_LOCAL_MACHINE\SOFTWARE\NVIDIA Corporation” key which holds some values for the Appx Package, such as Version, PackageFullName, InstallLocation, PackageFamilyName and PublisherId. This allows us to lookup these values on the fly, removing the need to hardcode them in other scripts and tools.
$NVControlPanelAppXInformationKey = "HKLM:\SOFTWARE\NVIDIA Corporation\NVControlPanelAppXInformation" $NvidiaAppxPackage = $null Try { $NvidiaAppxPackage = Get-AppxPackage -Name "NVIDIACorp.NVIDIAControlPanel*" } Catch [System.Exception]{ write-warning "$($Error[0].Exception.Message)" -verbose } If ($null -ne $NvidiaAppxPackage) { $KeyExists = $False $ErrorActionPreference = "stop" try { Get-Item -Path "$NVControlPanelAppXInformationKey" | Out-Null $KeyExists = $true } catch { # } $ErrorActionPreference = "Continue" If ($KeyExists -eq $False) { New-Item -Path "$NVControlPanelAppXInformationKey" -Force | Out-Null } write-verbose "Setting values under the `"$($NVControlPanelAppXInformationKey.Replace(':',''))`" registry key..." -verbose write-verbose "- Version: $($NvidiaAppxPackage.Version)" -verbose Set-ItemProperty -Path "$NVControlPanelAppXInformationKey" -Name Version -Type String -Value $NvidiaAppxPackage.Version Force write-verbose "- PackageFullName: $($NvidiaAppxPackage.PackageFullName)" -verbose Set-ItemProperty -Path "$NVControlPanelAppXInformationKey" -Name PackageFullName -Type String -Value $NvidiaAppxPackage.PackageFullName Force write-verbose "- InstallLocation: $($NvidiaAppxPackage.InstallLocation)" -verbose Set-ItemProperty -Path "$NVControlPanelAppXInformationKey" -Name InstallLocation -Type String -Value $NvidiaAppxPackage.InstallLocation Force write-verbose "- PackageFamilyName: $($NvidiaAppxPackage.PackageFamilyName)" -verbose Set-ItemProperty -Path "$NVControlPanelAppXInformationKey" -Name PackageFamilyName -Type String -Value $NvidiaAppxPackage.PackageFamilyName Force write-verbose "- PublisherId: $($NvidiaAppxPackage.PublisherId)" -verbose Set-ItemProperty -Path "$NVControlPanelAppXInformationKey" -Name PublisherId -Type String -Value $NvidiaAppxPackage.PublisherId Force }
2. User Logon Script
Even though we have re-instated the original Control Panel folder structure, we still register the new NVIDIA Control Panel Appx Package per user as NVIDIA would expect us to do. This ensures we have all bases covered from a usability and supportability point of view.
The script will also accept the NVIDIA Control Panel EULA and enables the Control Panel from the Desktop Context Menu.
Here is the Add NVIDIA Control Panel (59 downloads) script that registers the Appx Package per user.
<# This script will add the NVIDIA Control Panel Appx Package References: - https://www.vjonathan.com/post/dem-non-persistent-vdi-deployment-and-nvidia-control-panel-missing/ - https://www.jhouseconsulting.com/jhouseconsulting/2024/07/14/restoring-the-nvidia-control-panel-and-tools-after-the-appx-package-change-2903 We get the PackageFullName value for the Appx Package from the "HKEY_LOCAL_MACHINE\SOFTWARE\NVIDIA Corporation\NVControlPanelAppXInformation" registry key. This is set as a post install part of the driver installation process. Accept the NVIDIA Control Panel EULA Key: HKEY_CURRENT_USER\SOFTWARE\NVIDIA Corporation\NVControlPanel2\Client Type: REG_DWORD Value: ShowSedoanEula Data: 1 Add NVIDIA Control Panel to the Desktop Context Menu Key: HKEY_CURRENT_USER\SOFTWARE\NVIDIA Corporation\Global\NvCplApi\Policies Type: REG_DWORD Value: ContextUIPolicy Data: 2 Script name: Add-NVIDIA_Control_Panel.ps1 Release 1.0 Written by Jeremy Saunders (jeremy@jhouseconsulting.com) 6th May 2024 #> #------------------------------------------------------------- # Set Powershell Compatibility Mode Set-StrictMode -Version 2.0 # Enable verbose, warning and error mode $VerbosePreference = 'Continue' $WarningPreference = 'Continue' $ErrorPreference = 'Continue' #------------------------------------------------------------- $StartDTM = (Get-Date) # Set the working folder to the users TEMP folder $LogFolder = [System.IO.Path]::GetTempPath() # Get the script name $ScriptName = [System.IO.Path]::GetFilenameWithoutExtension($MyInvocation.MyCommand.Path.ToString()) # Start the transcript try { Start-Transcript "$LogFolder$ScriptName.log" } catch { write-verbose "$(Get-Date): This host does not support transcription" } #------------------------------------------------------------- # The DCH (Declarative Componentized Hardware) driver model started from GRID 15.0 (527.41) [float]$StartingVersion = 527.41 #------------------------------------------------------------- Function Create-RegKey { param ( [String]$Path ) $KeyExists = $False $ErrorActionPreference = "stop" try { Get-Item -Path "$Path" | Out-Null $KeyExists = $true } catch { # } $ErrorActionPreference = "Continue" If ($KeyExists -eq $False) { write-verbose "Creating the `"$Path`" registry key" -verbose New-Item -Path "$path" -Force | Out-Null } } Function Create-RegValue { param ( [String]$Path, [String]$Value, [String]$Data, [String]$Type ) $ValueExists = $False $ErrorActionPreference = "stop" try { If ((Get-ItemProperty -Path "$Path" | Select-Object -ExpandProperty "$Value") -ne $null) { $ValueExists = $True } } catch { # } $ErrorActionPreference = "Continue" If ($ValueExists) { write-verbose "- Setting the $Value registry value" -verbose Set-ItemProperty -Path "$Path" -Name "$Value" -Type $Type -Value $Data Force } Else { write-verbose "- Creating the $Value registry value" -verbose New-ItemProperty -Path "$Path" -Name "$Value" -PropertyType $Type -Value $Data | Out-Null } } #------------------------------------------------------------- $PackageFullName = "" $ErrorActionPreference = "stop" Try { $PackageFullName = (Get-ItemProperty -Path "HKLM:\SOFTWARE\NVIDIA Corporation\NVControlPanelAppXInformation" -Name PackageFullName).PackageFullName } Catch [System.Exception]{ write-warning "$($Error[0].Exception.Message)" -verbose } $ErrorActionPreference = "Continue" [float]$DriverVersion = 0.00 Try { If (TEST-PATH "${env:SystemRoot}\System32\nvidia-smi.exe") { $DriverVersion = & "${env:SystemRoot}\System32\nvidia-smi.exe" --query-gpu=driver_version --format=csv,noheader } } Catch [System.Exception]{ write-warning "$($Error[0].Exception.Message)" -verbose } If ($DriverVersion -ge $StartingVersion) { write-verbose "Driver $($DriverVersion.ToString()) is a DCH (Declarative Componentized Hardware) driver and uses the Control Panel Appx Package" -verbose If (!([String]::IsNullOrEmpty($PackageFullName))) { Try { write-verbose "Registering the existing NVIDIA Control Panel Appx Package from the `"${env:ProgramFiles}\WindowsApps\$PackageFullName$PackageFullName`" folder" -verbose Add-AppxPackage -Register "${env:ProgramFiles}\WindowsApps\$PackageFullName\AppxManifest.xml" -DisableDevelopmentMode } Catch [System.Exception]{ write-warning "$($Error[0].Exception.Message)" -verbose } } Else { write-warning "Unable to register the NVIDIA Control Panel Appx Package because the PackageFullName was not set." -verbose } } #------------------------------------------------------------- # Accept the NVIDIA Control Panel EULA Create-RegKey -Path:"HKCU:\SOFTWARE\NVIDIA Corporation\NVControlPanel2\Client" Create-RegValue -Path:"HKCU:\SOFTWARE\NVIDIA Corporation\NVControlPanel2\Client" -Value:"ShowSedoanEula" -Data:1 -Type:DWORD # Add NVIDIA Control Panel to the Desktop Context Menu Create-RegKey -Path:"HKCU:\SOFTWARE\NVIDIA Corporation\Global\NvCplApi\Policies" Create-RegValue -Path:"HKCU:\SOFTWARE\NVIDIA Corporation\Global\NvCplApi\Policies" -Value:"ContextUIPolicy" -Data:2 -Type:DWORD #------------------------------------------------------------- Write-Verbose "Stop logging" -Verbose $EndDTM = (Get-Date) Write-Verbose "Elapsed Time: $(($EndDTM-$StartDTM).TotalSeconds) Seconds" -Verbose Write-Verbose "Elapsed Time: $(($EndDTM-$StartDTM).TotalMinutes) Minutes" -Verbose # Stop the transcript try { Stop-Transcript } catch { write-verbose "$(Get-Date): This host does not support transcription" }
References:
- NVIDIA Enterprise Support Article Number 4730: NVIDIA Control Panel not found on System
- Jonathan Carraro (AKA vJonathan) published an article on 19th Feb 2023: DEM: Non-Persistent VDI Deployment and NVIDIA Control Panel Is Not Found
- NVIDIA bug #4644253
Summary
The solution I’ve provided respects both Microsoft and NVIDIA support, allowing you to continue as business as usual. As you may tell, I’m not overly impressed with what NVIDIA have done here. Hopefully someone from NVIDIA will read this article and see that what they’ve created is not ideal.
Hope you find this helpful.