Restoring the NVIDIA Control Panel and Tools after the Appx Package Change

by Jeremy Saunders on July 14, 2024

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.

NVIDIA Control Panel and 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.

Error when launching the nvcplui directly

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*”

Get-AppxPackage for 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.

NVIDIA 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?

NVIDIA AppxPackage Files

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:

  1. Post driver install script
  2. 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.

Reinstate the Control Panel Client folder

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
}

NVControlPanelAppXInformation key and values

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 (70 downloads) script that registers the Appx Package per user.

&lt;#
  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

#&gt;

#-------------------------------------------------------------

# 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 = &amp; "${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:

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.

Jeremy Saunders

Jeremy Saunders

Technical Architect | DevOps Evangelist | Software Developer | Microsoft, NVIDIA, Citrix and Desktop Virtualisation (VDI) Specialist/Expert | Rapper | Improvisor | Comedian | Property Investor | Kayaking enthusiast at J House Consulting
Jeremy Saunders is the Problem Terminator. He is a highly respected IT Professional with over 35 years’ experience in the industry. Using his exceptional design and problem solving skills with precise methodologies applied at both technical and business levels he is always focused on achieving the best business outcomes. He worked as an independent consultant until September 2017, when he took up a full time role at BHP, one of the largest and most innovative global mining companies. With a diverse skill set, high ethical standards, and attention to detail, coupled with a friendly nature and great sense of humour, Jeremy aligns to industry and vendor best practices, which puts him amongst the leaders of his field. He is intensely passionate about solving technology problems for his organisation, their customers and the tech community, to improve the user experience, reliability and operational support. Views and IP shared on this site belong to Jeremy.
Jeremy Saunders
Jeremy Saunders

Previous post:

Next post: