Updates to $env:EnvVarName
affect the current process only - no persistent changes via the registry are made:
$env:EnvVarName = 'foo'
is the equivalent of calling .NET method System.Environment.SetEnvironmentVariable
as follows:
[Environment]::SetEnvironmentVariable('EnvVarName', 'foo', 'Process')
That is, the scope of the update is the current process.
Only if you substitute 'User'
or 'Machine'
for 'Process'
in the above call (supported on Windows only[1]) do you persistently update environment variables in the registry (for the current user or the local machine (all users), respectively), for future sessions (processes)[2].
As of PowerShell [Core] 7.2, there is no PowerShell-native way to persistently update environment variables, but introducing one is being discussed on GitHub.
In other words: if you want to update not only the registry-based definition but also the value in the current process, you need to make both calls; e.g., for the current user:
# Windows only: Update / create a persistent definition for the current user,
# stored in the registry.
[Environment]::SetEnvironmentVariable('EnvVarName', 'foo', 'User')
# Update value for current process too.
$env:EnvVarName = 'foo'
Or, more in the spirit of DRY:
'User', 'Process' | foreach {
[Environment]::SetEnvironmentVariable('EnvVarName', 'foo', $_)
}
If the new value is to be based on the existing one from a given registry scope, retrieve the scope-specific value via System.Environment.GetEnvironmentVariable
; e.g.:
# Get the registry-based *user* value
[Environment]::GetEnvironmentVariable('Path', 'User')
Caveat: Non-support for Windows environment variables based on REG_EXPAND_SZ
registry values:
On Windows, persistently defined environment variables may be defined based on other environment variables, namely if the underlying registry value defining the variable is of type REG_EXPAND_SZ
.
As of .NET 6, the System.Environment
type's methods do not (directly) support such environment variables:
On getting such a variable's value, its expanded form is invariably returned; that is, references to other environment variables such as %SystemRoot%
are replaced by their values.
On setting environment variables, REG_SZ
registry values are invariably created, i.e. static, verbatim values - even when updating an existing REG_EXPAND_SZ
value.
While quietly converting REG_EXPAND_SZ
environment variables to static REG_SZ
ones may often have no ill effects (as long as the new value only contains literal values), it certainly can: for instance, say a variable is defined in terms of %JAVADIR%
; if that variable is converted to a static value based on the then-current value of %JAVADIR%
, it will stop working if the value of %JAVADIR%
is later changed.
Unfortunately, retrieval of raw REG_EXPAND_SZ
environment variables and proper updating of their values currently requires direct registry access, which is quite cumbersome (not even the Windows API seems to have support for it) - see this answer.
Important considerations for the Path
environment variable ($env:PATH
) on Windows:
The Path
environment variable is special in that it is a composite value: when a process starts, the in-process value is the concatenation of the Machine
(local machine, for all users) value and the User
(current user) value.
- Note that since the machine-level value comes first, its entries take precedence over the user-level value's entries.
Therefore, if you want to modify (append to) the existing Path
, it's better not to define the new value simply by appending to the existing in-process value ($env:Path
), because you'll be duplicating the Machine
or User
values, depending on which scope you target.
Instead, retrieve the existing value from the target scope selectively, modify that value (typically by appending a directory, and then write the modified value back to the same scope.
To robustly make the same modification effective in the current process too is nontrivial, given that the in-process copy of $env:Path
may have been modified; however, in the simple case of appending a new directory to the user's path, you can simply do $env:Path += ';' + $newDir
; you may get away with this simple approach in other cases too, but note that the behavior may be different, given that the order in which the directories are listed in $env:Path
matters.
Important: The Path
environment variable on Windows is REG_EXPAND_SZ
-based by default, so the caveats re the quiet conversion to a static REG_SZ
-based value that the code below below performs apply - again, see this answer for a proper, but much more complex solution.
Example:
# New dir. to add to the *user's* path
$newDir = 'c:\foo\bin'
# Get current value *from the registry*
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
# Append the new dir and save back to the registry.
[Environment]::SetEnvironmentVariable('Path', ($userPath + ';' + $newDir), 'User')
# To also update the current process, append $newDir to the in-process
# variable, $env:Path
$env:Path += ';' + $newDir
As an aside: On Unix-like platforms, the separator is :
, not ;
(reflected in [System.IO.Path]::PathSeparator
, and the case-sensitive variable name is Path
. As stated, .NET fundamentally doesn't offer persistent environment-variable definitions on Unix-like platforms (as of .NET Core 3.1), because the various platforms have no unified native mechanism for doing so.
[1] On Unix-like platforms, targeting User
or Machine
is quietly ignored as of .NET Core 3.1
[2] Caveat: New processes created directly by the current PowerShell session (direct invocation, Start-Process
, Start-Job
) do not yet see the registry changes, because the inherit the current session's environment.