How to Update Path Variable Permanently from Windows Command Line

Set a persistent environment variable from cmd.exe

Use the SETX command (note the 'x' suffix) to set variables that persist after the cmd window has been closed.

For example, to set an env var "foo" with value of "bar":

setx foo bar /m

Though it's worth reading the 'notes' that are displayed if you print the usage (setx /?), in particular:


  1. On a local system, variables created or modified by this tool will be available in future command windows but not in the current CMD.exe command window.

  2. On a remote system, variables created or modified by this tool will be available at the next logon session.

In PowerShell, the [Environment]::SetEnvironmentVariable command.

How to update system PATH variable permanently from cmd?

Type setx /? to get basic command help. You'll easily discover:

/M                     Specifies that the variable should be set in
the system wide (HKEY_LOCAL_MACHINE)
environment. The default is to set the
variable under the HKEY_CURRENT_USER
environment.

You need to run this from an elevated command prompt. Right-click the cmd shortcut and select Run as Administrator.

E.g.

setx /M PATH "%PATH%;C:\Something\bin"

Caution:

We may destroy the current system's PATH variable. Make sure you backup its value before you modify it.

How to update PATH variable permanently from Windows command line?

The documentation on how to do this can be found on MSDN. The key extract is this:

To programmatically add or modify system environment variables, add them to the HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment registry key, then broadcast a WM_SETTINGCHANGE message with lParam set to the string "Environment". This allows applications, such as the shell, to pick up your updates.

Note that your application will need elevated admin rights in order to be able to modify this key.

You indicate in the comments that you would be happy to modify just the per-user environment. Do this by editing the values in HKEY_CURRENT_USER\Environment. As before, make sure that you broadcast a WM_SETTINGCHANGE message.

You should be able to do this from your Java application easily enough using the JNI registry classes.

Adding a directory to the PATH environment variable in Windows

This only modifies the registry. An existing process won't use these values. A new process will do so if it is started after this change and doesn't inherit the old environment from its parent.

You didn't specify how you started the console session. The best way to ensure this is to exit the command shell and run it again. It should then inherit the updated PATH environment variable.

How do I set Windows Environment variables permanently?

Right click on Computer, Advanced system settings, select Advanced tab and click Environment variables.

Note: once you modify the environment variables, you will have to restart your applications, including CLI.

Setting Windows PowerShell environment variables

Changing the actual environment variables can be done by
using the env: namespace / drive information. For example, this
code will update the path environment variable:

$env:Path = "SomeRandomPath";             (replaces existing path) 
$env:Path += ";SomeRandomPath" (appends to existing path)

Making change permanent

There are ways to make environment settings permanent, but
if you are only using them from PowerShell, it's probably
a lot better to use Powershell profiles script.

Everytime a new instance of Powershell starts, it look for specific script files (named profile files) and execute them if they do exist. You can edit one of these profile to customize your enviroment.

To know where those profile scripts are located in your computer type:

$profile                                     
$profile.AllUsersAllHosts
$profile.AllUsersCurrentHost
$profile.CurrentUserAllHosts
$profile.CurrentUserCurrentHost

You can edit one of them, for example, by typing:

notepad $profile

Adding path permanently to windows using powershell doesn't appear to work

Note:

  • See the middle section for helper function Add-Path

  • See the bottom section for why use of setx.exe should be avoided for updating the Path environment variable.


The procedure in the linked blog post is effective in principle, but is missing a crucial piece of information / additional step:

If you modify environment variables directly via the registry - which, unfortunately, is the right way to do it for REG_EXPAND_SZ-based environment variables such as Path - you need to broadcast a WM_SETTINGCHANGE message so that the Windows (GUI) shell (and its components, File Explorer, the taskbar, the desktop, the Start Menu, all provided via explorer.exe processes) is notified of the environment change and reloads its environment variables from the registry. Applications launched afterwards then inherit the updated environment.

  • If this message is not sent, future PowerShell sessions (and other applications) won't see the modification until the next logon / reboot.

Unfortunately, there's no direct way to do this from PowerShell, but there are workarounds:

  • Brute-force workaround - simple, but visually disruptive and closes all open File Explorer windows:

    # Kills all explorer.exe processes, which restarts the Windows shell
    # components, forcing a reload of the environment from the registry.
    Stop-Process -Name explorer
  • Workaround via .NET APIs:

    # Create a random name assumed to be unique
    $dummyName = [guid]::NewGuid().ToString()
    # Set an environment variable by that name, which makes .NET
    # send a WM_SETTINGCHANGE broadcast
    [Environment]::SetEnvironmentVariable($dummyName, 'foo', 'User')
    # Now that the dummy variable has served its purpose, remove it again.
    # (This will trigger another broadcast, but its performance impact is negligible.)
    [Environment]::SetEnvironmentVariable($dummyName, [NullString]::value, 'User')
  • Workaround by calling the Windows API via an ad hoc-compiled P/Invoke call to SendMessageTimeout() in C#, via Add-Type:

    • While this is a proper solution, it invariably incurs a noticeable performance penalty due to the ad hoc-compilation the first time it is run in a session.

    • For details, see this blog post.

The approach in the blog post has another problematic aspect:

  • It retrieves the expanded environment-variable value from the registry, because that is what Get-ItemProperty and Get-ItemPropertyValue invariably do. That is, if directories in the value are defined in terms of other environment variables (e.g., %SystemRoot% or %JAVADIR%), the returned value no longer contains these variables, but their current values. See the bottom section for why this can be problematic.

The helper function discussed in the next section addresses all issues, while also ensuring that the modification takes effect for the current session too.


The following Add-Path helper function:

  • Adds (appends) a given, single directory path to the persistent user-level Path environment variable by default; use -Scope Machine to target the machine-level definition, which requires elevation (run as admin).

    • If the directory is already present in the target variable, no action is taken.

    • The relevant registry value is updated, which preserves its REG_EXPAND_SZ data type, based on the existing unexpanded value - that is, references to other environment variables are preserved as such (e.g., %SystemRoot%), and may also be used in the new entry being added.

  • Triggers a WM_SETTINGCHANGE message broadcast to inform the Windows shell of the change.

  • Also updates the current session's $env:Path variable value.

Note: By definition (due to use of the registry), this function is Windows-only.

With the function below defined, your desired Path addition could be performed as follows, modifying the current user's persistent Path definition:

Add-Path C:\Users\921479\AppData\Local\SumatraPDF

If you really want to update the machine-level definition (in the HKEY_LOCAL_MACHINE registry hive, which doesn't make sense with a user-specific path), add -Scope Machine, but not that you must then run with elevation (as admin).

Add-Path source code:

function Add-Path {

param(
[Parameter(Mandatory, Position=0)]
[string] $LiteralPath,
[ValidateSet('User', 'CurrentUser', 'Machine', 'LocalMachine')]
[string] $Scope
)

Set-StrictMode -Version 1; $ErrorActionPreference = 'Stop'

$isMachineLevel = $Scope -in 'Machine', 'LocalMachine'
if ($isMachineLevel -and -not $($ErrorActionPreference = 'Continue'; net session 2>$null)) { throw "You must run AS ADMIN to update the machine-level Path environment variable." }

$regPath = 'registry::' + ('HKEY_CURRENT_USER\Environment', 'HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment')[$isMachineLevel]

# Note the use of the .GetValue() method to ensure that the *unexpanded* value is returned.
$currDirs = (Get-Item -LiteralPath $regPath).GetValue('Path', '', 'DoNotExpandEnvironmentNames') -split ';' -ne ''

if ($LiteralPath -in $currDirs) {
Write-Verbose "Already present in the persistent $(('user', 'machine')[$isMachineLevel])-level Path: $LiteralPath"
return
}

$newValue = ($currDirs + $LiteralPath) -join ';'

# Update the registry.
Set-ItemProperty -Type ExpandString -LiteralPath $regPath Path $newValue

# Broadcast WM_SETTINGCHANGE to get the Windows shell to reload the
# updated environment, via a dummy [Environment]::SetEnvironmentVariable() operation.
$dummyName = [guid]::NewGuid().ToString()
[Environment]::SetEnvironmentVariable($dummyName, 'foo', 'User')
[Environment]::SetEnvironmentVariable($dummyName, [NullString]::value, 'User')

# Finally, also update the current session's `$env:Path` definition.
# Note: For simplicity, we always append to the in-process *composite* value,
# even though for a -Scope Machine update this isn't strictly the same.
$env:Path = ($env:Path -replace ';$') + ';' + $LiteralPath

Write-Verbose "`"$LiteralPath`" successfully appended to the persistent $(('user', 'machine')[$isMachineLevel])-level Path and also the current-process value."

}


The limitations of setx.exe and why it shouldn't be used to update the Path environment variable:

setx.exe has fundamental limitations that make it problematic, particularly for updating environment variables that are based on REG_EXPAND_SZ-typed registry values, such as Path:

  • Values are limited to 1024 characters, with additional ones getting truncated, albeit with a warning (as of at least Windows 10).

  • The environment variable that is (re)created is invariably of type REG_SZ, whereas Path is originally of type REG_EXPAND_SZ and contains directory paths based on other environment variables, such as %SystemRoot% and %JAVADIR%.

    • If the replacement value contains only literal paths (no environment-variable references) that may have no immediate ill effects, but, for an instance, an entry that originally depended on %JAVADIR% will stop working if the value of %JAVADIR% is later changed.
  • Additionally, if you base the updated value on the current session's $env:Path value, you'll end up duplicating entries, because the process-level $env:Path value is a composite of the machine-level and current-user-level values.

    • This increases the risk of running into the 1024-character limit, especially if the technique is used repeatedly. It also bears the risk of duplicate values lingering after the original entry is removed from the original scope.

    • While you can avoid this particular problem by retrieving the scope-specific value either directly from the registry or - invariably in expanded form - via [Environment]::GetEnvironmentVariable('Path', 'User') or [Environment]::GetEnvironmentVariable('Path', 'Machine'), that still doesn't solve the REG_EXPAND_SZ problem discussed above.



Related Topics



Leave a reply



Submit