Search code examples
powershellchocolatey

Setting User level environment variables with Chocolatey


I'm writing a Chocolately package that needs to install my program and then set a User level environment variable that the program needs.

As recommended in the documentation, I've installed Chocolatey from a PowerShell terminal with elevated privileges. In my chocolatelyinstall.ps1 script I can set the environment variable with this command:

Install-ChocolateyEnvironmentVariable -VariableName "my_env_var" -VariableValue "Wibble" -VariableType User

However, when I install the package: choco install my_package -s . the environment variable is set at User level for the administrator account, rather than the standard user account.

Installing the package in a regular (non-elevated) PowerShell process, simply fails with:

Access to the path 'C:\ProgramData\chocolatey\lib\my_package\tools' is denied.

Is there any way to set the Env var on the standard user account, rather than the admin account?

All assistance is welcome!


Solution

  • Indeed (to recap), if your elevated process uses a different (of necessity administrative) user account than the current window-station user (the user that started the current OS user session), you cannot define environment variables for the windows-station user using the usual methods that target the HKEY_CURRENT_USER hive, as it reflects the elevating user's data.

    • Conversely, this means that if your window-station user is an administrator and therefore allowed to run with elevation themselves, the problem will not arise.

    Workaround (takes the place of your Install-ChocolateyEnvironmentVariable call):

    • Determine the identify of the window-station user in terms of its SID (security identify).

    • Use the SID to target the window-station user's specific registry hive, under HKEY_USERS.

    • Use a dummy user-level [Environment]::SetEnvironmentVariable() call so as to broadcast a notification of the environment change (modifying the registry directly doesn't do that), notably so that the Windows (GUI) shell refreshes its environment.

    # Get the window station user and split into domain name and user name.
    $domain, $user = (Get-CimInstance Win32_ComputerSystem).UserName -split '\\'
    
    # Obtain their SID.
    $sid = [System.Security.Principal.NTAccount]::new(
      $domain, 
      $user
    ).Translate([System.Security.Principal.SecurityIdentifier]).Value
    
    # Set an environment variable for them.
    Set-ItemProperty "registry::HKEY_USERS\$sid\Environment" my_env_var Wibble
    
    # Set and remove a dummy variable for the *current user*, 
    # so as to notify the GUI shell that the environment changed.
    ('unused', $null).ForEach({ 
      [Environment]::SetEnvironmentVariable("_PowerShell_$PID", $_, 'User') 
    })