in Powershell I want to get a list of Environment Variables with their values AND types (machine or user) from ALL user-profiles.

If I use

gci env:

I'll get all environment variables that are currently set, but I can't see which variable of those is set for the machine and which variable is set for user. And of course I can't see ALL environment variables that are (or have been) set from any users that are working on the current computer (Win10).

Is there a way how such a listing can be coded in Powershell?

2

There are 2 best solutions below

1
mklement0 On BEST ANSWER
Preface:

Note that this answer fundamentally applies to Windows only.

  • The machine-scope environment variables, as persistently defined in the registry, can be obtained via [System.Environment]::GetEnvironmentVariables(), which returns an unordered dictionary of environment-variable name-value pairs: [System.Environment]::GetEnvironmentVariables('Machine')

    • By definition, these definitions are shared by all user accounts.
  • For the current user only, the user-scope environment variables, as persistently defined in the registry, can be obtained with
    [System.Environment]::GetEnvironmentVariables('User')

  • Any given process sees a composite of these two scopes (for the user in whose context the process is created):

    • While [System.Environment]::GetEnvironmentVariables('Process') can be used to retrieve the process-scope variables, in PowerShell it is simplest to use Get-ChildItem Env:, which has the added advantage of returning the variables sorted by name.

    • Similarly, to access a given process-scope variable by name in PowerShell, it simplest to use namespace variable notation, e.g. $env:OS for variable OS - see the conceptual about_Environment_Variables help topic.

    • While user-scope environment variable generally override machine-scope ones of the same name, the PATH environment is special: its effective value is the concatenation of the machine- and user-scope values, in that order.

Note:

  • It follows from the above that the user and machine scopes exclusively relate to persisted (persistently predefined) environment variables, which in combination form the runtime process scope, often indirectly, by inheriting the parent process' process-scope environment. At the process level, you cannot reliably tell which persisted scope - if any - a given value came from: it may be a value created or modified by a parent process, or it may be a composite value merging the user- and machine-scope values, as in the case of PATH. Additionally, there are the so-called volatile (non-persisted, dynamic) environment variables such as USERPROFILE and APPDATA that are created on demand at user login.

  • All methods above report expanded environment-variable values, i.e. any nested environment variable references in persisted-in-the-registry definitions using REG_EXPAND_SZ registry values were expanded to their values.

  • As of this writing (.NET 8 / PowerShell (Core) 7.4.x), neither the [System.Environment .NET API nor PowerShell's Environment provider support getting or setting unexpanded values.

  • Doing the latter requires working directly with the .NET registry APIs; e.g., the following retrieves the unexpanded definition of the current user's TEMP environment variable:

    # -> '%USERPROFILE%\AppData\Local\Temp' - note the nested %USERPROFILE% reference.
    (Get-Item -Path HKCU:\Environment).GetValue('TEMP', $null, 'DoNotExpandEnvironmentNames')
    
  • The problematic absence of support for unexpanded environment-variable values in the dedicated APIs is the subject of:


Getting user-scope environment variables across all local user profiles:
  • The solution below gets the persistently defined user-scope environment variables from all local user profiles.

    • Any process is free to modify its inherited persisted environment at runtime. To get the runtime, process-scope environment variables of a given process programmatically, nontrivial effort via system (WinAPI) calls is needed; this C++-based answer may offer a solution. Interactively, you can use SysInternals' Process Explorer (procexp.exe) utility, as described in this answer.
      Either way you need to run with elevation, as without it you'll only be able to inspect your own, non-elevated processes.
  • As js2010 notes, in order to access other users' persistently defined user-scope environment variable definitions, you must explicitly load their profiles into the registry, via the (hidden) ntuser.dat file in the respective profile directories.

  • Doing so requires elevation (running as administrator).

The following code demonstrates this approach:

  • It - temporarily - loads other users' local profiles into the HKEY_USERS registry hive on demand, into a subkey name for each user's SID in order to extract the target user's user-scope environment-variables.

    • For simplicity, it does so with the help of reg.exe load / unload - there may be faster, in-process techniques to achieve the same.
  • It outputs a sorted dictionary, keyed by username, with each entry containing a nested dictionary of environment-variable key-value pairs for that user.

    • E.g., to query the (unexpanded) TEMP variable definition of user jdoe, use the following:

      $userEnvironments['jdoe'].TEMP
      
  • Note:

    • The reported values are unexpanded values; that is, nested environment-variable references are not expanded.

    • Doing the latter properly would require nontrivial additional effort, as the code doing the expansion would have to take the target user's environment into account; by contrast, using expansion in the caller's environment will lead to false results.

      • E.g., caller jdoe asking for jsmith's definition of TEMP and doing expansion of %USERPROFILE%\AppData\Local\Temp in the context of jdoe, will incorrectly report C:\Users\jdoe\AppData\Local\Temp rather than C:\Users\jsmith\AppData\Local\Temp
#requires -RunAsAdministrator

# Initialize the result dictionary.
$userEnvironments = [System.Collections.Generic.SortedDictionary[string, System.Collections.IDictionary]]::new()

# Process all local user local profiles.
Get-CimInstance Win32_UserProfile | 
  ForEach-Object {
    $sid = $_.Sid
    $userName = try {
     (([System.Security.Principal.SecurityIdentifier] $sid).Translate([System.Security.Principal.NTAccount]).Value -split '\\')[-1]
    } catch {
      # SID doesn't identify an existing user account -> ignore it.
      return 
    }
    # See if the target user's registry hive is already loaded, which is
    # only the case for the current user, system accounts, and service accounts.
    $userHivePath = "registry::HKEY_USERS\$sid"
    if ($mustLoad = -not (Test-Path -LiteralPath $userHivePath)) {
      $localProfilePath = Join-Path $_.LocalPath ntuser.dat
      Write-Verbose "Loading registry hive for user $sid from $localProfilePath..."
      reg.exe load "HKU\$sid" $localProfilePath >$null
      if ($LASTEXITCODE) { throw "reg.exe load failed with error code $LASTEXITCODE" }
    }
    # Create a sorted dictionary with the environment-variable key-value pairs.
    $envVars = [System.Collections.Generic.SortedDictionary[string, string]]::new()        
    $regKey = Get-Item -LiteralPath "registry::HKEY_USERS\$sid\Environment"
    $regKey.GetValueNames() |
      ForEach-Object { $envVars[$_] = $regKey.GetValue($_, $null, 'DoNotExpandEnvironmentNames') }
    $regKey.Close() # Necessary to allow unloading.
      # Add it to the result dictionary, keyed by username. 
    $userEnvironments[$userName] = $envVars
    # Unload the registry hive again, if it had to be loaded on demand.
    if ($mustLoad) {
      Write-Verbose "Unloading registry hive of user $sid..."
      reg.exe unload "HKU\$sid" >$null
      if ($LASTEXITCODE) { throw "reg.exe unload failed with error code $LASTEXITCODE" }
    }
    }

# Output the result dictionary.
# To access, e.g., user "jdoe"'s environment variable, use:
#      $userEnvironments['jdoe']
# To get a given user's specific variable's value, e.g. 'TEMP'
#      $userEnvironments['jdoe'].TEMP
$userEnvironments
1
Theo On

You can use .NET here to get the environment variables per scope as Hashtables:

User scope

[System.Environment]::GetEnvironmentVariables([System.EnvironmentVariableTarget]::User)

Machine scope

[System.Environment]::GetEnvironmentVariables([System.EnvironmentVariableTarget]::Machine)

Process scope

[System.Environment]::GetEnvironmentVariables([System.EnvironmentVariableTarget]::Process)