env-path not persisted with Powershell [duplicate] - powershell

I have found out that setting the PATH environment variable affects only the old command prompt. PowerShell seems to have different environment settings. How do I change the environment variables for PowerShell (v1)?
Note:
I want to make my changes permanent, so I don't have to set it every time I run PowerShell. Does PowerShell have a profile file? Something like Bash profile on Unix?

If, some time during a PowerShell session, you need to see or to temporarily modify the PATH environment variable , you can type one of these commands:
$env:Path # shows the actual content
$env:Path = 'C:\foo;' + $env:Path # attach to the beginning
$env:Path += ';C:\foo' # attach to the end

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

You can also modify user/system environment variables permanently (i.e. will be persistent across shell restarts) with the following:
Modify a system environment variable
[Environment]::SetEnvironmentVariable
("Path", $env:Path, [System.EnvironmentVariableTarget]::Machine)
Modify a user environment variable
[Environment]::SetEnvironmentVariable
("INCLUDE", $env:INCLUDE, [System.EnvironmentVariableTarget]::User)
Usage from comments - add to the system environment variable
[Environment]::SetEnvironmentVariable(
"Path",
[Environment]::GetEnvironmentVariable("Path", [EnvironmentVariableTarget]::Machine) + ";C:\bin",
[EnvironmentVariableTarget]::Machine)
String based solution is also possible if you don't want to write types
[Environment]::SetEnvironmentVariable("Path", $env:Path + ";C:\bin", "Machine")

WARNING: save a copy of your existing path by doing $env:path >> a.out in a PowerShell prompt, in case something goes wrong.
From the PowerShell prompt:
setx PATH "$env:path;\the\directory\to\add" -m
You should then see the text:
SUCCESS: Specified value was saved.
Restart your session, and the variable will be available. setx can also be used to set arbitrary variables. Type setx /? at the prompt for documentation.

Like JeanT's answer, I wanted an abstraction around adding to the path. Unlike JeanT's answer I needed it to run without user interaction. Other behavior I was looking for:
Updates $env:Path so the change takes effect in the current session
Persists the environment variable change for future sessions
Doesn't add a duplicate path when the same path already exists
In case it's useful, here it is:
function Add-EnvPath {
param(
[Parameter(Mandatory=$true)]
[string] $Path,
[ValidateSet('Machine', 'User', 'Session')]
[string] $Container = 'Session'
)
if ($Container -ne 'Session') {
$containerMapping = #{
Machine = [EnvironmentVariableTarget]::Machine
User = [EnvironmentVariableTarget]::User
}
$containerType = $containerMapping[$Container]
$persistedPaths = [Environment]::GetEnvironmentVariable('Path', $containerType) -split ';'
if ($persistedPaths -notcontains $Path) {
$persistedPaths = $persistedPaths + $Path | where { $_ }
[Environment]::SetEnvironmentVariable('Path', $persistedPaths -join ';', $containerType)
}
}
$envPaths = $env:Path -split ';'
if ($envPaths -notcontains $Path) {
$envPaths = $envPaths + $Path | where { $_ }
$env:Path = $envPaths -join ';'
}
}
Check out my gist for the corresponding Remove-EnvPath function.

Pain-free, one-line, example solutions
Try these three commands to practice setting and deleting environmental variables in PowerShell.
Notes on usage:
Run these commands on elevated PowerShell (e.g. with administrator privileges).
After every step, to make your commands work, close the session and open it again.
Add/create a permanent environment variable:
[Environment]::SetEnvironmentVariable("MyEnvVar", "NewEnvValue", "Machine")
Machine is an EnvironmentVariableTarget that will apply to current and future users, as opposed to the User target.
Modify/change environmental variable:
[Environment]::SetEnvironmentVariable("MyEnvVar", "NewerEnvValue", "Machine")
Delete/remove variable:
[Environment]::SetEnvironmentVariable("MyEnvVar", "", "Machine")

Although the current accepted answer works in the sense that the path variable gets permanently updated from the context of PowerShell, it doesn't actually update the environment variable stored in the Windows registry.
To achieve that, you can obviously use PowerShell as well:
$oldPath=(Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH).Path
$newPath=$oldPath+’;C:\NewFolderToAddToTheList\’
Set-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH –Value $newPath
More information is in blog post Use PowerShell to Modify Your Environmental Path
If you use PowerShell community extensions, the proper command to add a path to the environment variable path is:
Add-PathVariable "C:\NewFolderToAddToTheList" -Target Machine

All the answers suggesting a permanent change have the same problem: They break the path registry value.
SetEnvironmentVariable turns the REG_EXPAND_SZ value %SystemRoot%\system32 into a REG_SZ value of C:\Windows\system32.
Any other variables in the path are lost as well. Adding new ones using %myNewPath% won't work any more.
Here's a script Set-PathVariable.ps1 that I use to address this problem:
[CmdletBinding(SupportsShouldProcess=$true)]
param(
[parameter(Mandatory=$true)]
[string]$NewLocation)
Begin
{
#requires –runasadministrator
$regPath = "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"
$hklm = [Microsoft.Win32.Registry]::LocalMachine
Function GetOldPath()
{
$regKey = $hklm.OpenSubKey($regPath, $FALSE)
$envpath = $regKey.GetValue("Path", "", [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
return $envPath
}
}
Process
{
# Win32API error codes
$ERROR_SUCCESS = 0
$ERROR_DUP_NAME = 34
$ERROR_INVALID_DATA = 13
$NewLocation = $NewLocation.Trim();
If ($NewLocation -eq "" -or $NewLocation -eq $null)
{
Exit $ERROR_INVALID_DATA
}
[string]$oldPath = GetOldPath
Write-Verbose "Old Path: $oldPath"
# Check whether the new location is already in the path
$parts = $oldPath.split(";")
If ($parts -contains $NewLocation)
{
Write-Warning "The new location is already in the path"
Exit $ERROR_DUP_NAME
}
# Build the new path, make sure we don't have double semicolons
$newPath = $oldPath + ";" + $NewLocation
$newPath = $newPath -replace ";;",""
if ($pscmdlet.ShouldProcess("%Path%", "Add $NewLocation")){
# Add to the current session
$env:path += ";$NewLocation"
# Save into registry
$regKey = $hklm.OpenSubKey($regPath, $True)
$regKey.SetValue("Path", $newPath, [Microsoft.Win32.RegistryValueKind]::ExpandString)
Write-Output "The operation completed successfully."
}
Exit $ERROR_SUCCESS
}
I explain the problem in more detail in a blog post.

This sets the path for the current session and prompts the user to add it permanently:
function Set-Path {
param([string]$x)
$Env:Path+= ";" + $x
Write-Output $Env:Path
$write = Read-Host 'Set PATH permanently ? (yes|no)'
if ($write -eq "yes")
{
[Environment]::SetEnvironmentVariable("Path",$env:Path, [System.EnvironmentVariableTarget]::User)
Write-Output 'PATH updated'
}
}
You can add this function to your default profile, (Microsoft.PowerShell_profile.ps1), usually located at %USERPROFILE%\Documents\WindowsPowerShell.

Building on #Michael Kropat's answer I added a parameter to prepend the new path to the existing PATHvariable and a check to avoid the addition of a non-existing path:
function Add-EnvPath {
param(
[Parameter(Mandatory=$true)]
[string] $Path,
[ValidateSet('Machine', 'User', 'Session')]
[string] $Container = 'Session',
[Parameter(Mandatory=$False)]
[Switch] $Prepend
)
if (Test-Path -path "$Path") {
if ($Container -ne 'Session') {
$containerMapping = #{
Machine = [EnvironmentVariableTarget]::Machine
User = [EnvironmentVariableTarget]::User
}
$containerType = $containerMapping[$Container]
$persistedPaths = [Environment]::GetEnvironmentVariable('Path', $containerType) -split ';'
if ($persistedPaths -notcontains $Path) {
if ($Prepend) {
$persistedPaths = ,$Path + $persistedPaths | where { $_ }
[Environment]::SetEnvironmentVariable('Path', $persistedPaths -join ';', $containerType)
}
else {
$persistedPaths = $persistedPaths + $Path | where { $_ }
[Environment]::SetEnvironmentVariable('Path', $persistedPaths -join ';', $containerType)
}
}
}
$envPaths = $env:Path -split ';'
if ($envPaths -notcontains $Path) {
if ($Prepend) {
$envPaths = ,$Path + $envPaths | where { $_ }
$env:Path = $envPaths -join ';'
}
else {
$envPaths = $envPaths + $Path | where { $_ }
$env:Path = $envPaths -join ';'
}
}
}
}

My suggestion is this one:
I have tested this to add C:\oracle\x64\bin to environment variable Path permanently and this works fine.
$ENV:PATH
The first way is simply to do:
$ENV:PATH=”$ENV:PATH;c:\path\to\folder”
But this change isn’t permanent. $env:path will default back to what it was before as soon as you close your PowerShell terminal and reopen it again. That’s because you have applied the change at the session level and not at the source level (which is the registry level). To view the global value of $env:path, do:
Get-ItemProperty -Path ‘Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment’ -Name PATH
Or more specifically:
(Get-ItemProperty -Path ‘Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment’ -Name PATH).path
Now to change this, first we capture the original path that needs to be modified:
$oldpath = (Get-ItemProperty -Path ‘Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment’ -Name PATH).path
Now we define what the new path should look like. In this case we are appending a new folder:
$newpath = “$oldpath;c:\path\to\folder”
Note: Be sure that the $newpath looks how you want it to look. If not, then you could damage your OS.
Now apply the new value:
Set-ItemProperty -Path ‘Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment’ -Name PATH -Value $newPath
Now do one final check that it looks like how you expect it to:
(Get-ItemProperty -Path ‘Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment’ -Name PATH).Path
You can now restart your PowerShell terminal (or even reboot the machine) and see that it doesn’t rollback to its old value again.
Note the ordering of the paths may change so that it’s in alphabetical order, so make sure you check the whole line. To make it easier, you can split the output into rows by using the semi-colon as a delimiter:
($env:path).split(“;”)

Only the answers that push the value into the registry affect a permanent change (so the majority of answers on this thread, including the accepted answer, do not permanently affect the Path).
The following function works for both Path / PSModulePath and for User / System types. It will also add the new path to the current session by default.
function AddTo-Path {
param (
[string]$PathToAdd,
[Parameter(Mandatory=$true)][ValidateSet('System','User')][string]$UserType,
[Parameter(Mandatory=$true)][ValidateSet('Path','PSModulePath')][string]$PathType
)
# AddTo-Path "C:\XXX" "PSModulePath" 'System'
if ($UserType -eq "System" ) { $RegPropertyLocation = 'HKLM:\System\CurrentControlSet\Control\Session Manager\Environment' }
if ($UserType -eq "User" ) { $RegPropertyLocation = 'HKCU:\Environment' } # also note: Registry::HKEY_LOCAL_MACHINE\ format
$PathOld = (Get-ItemProperty -Path $RegPropertyLocation -Name $PathType).$PathType
"`n$UserType $PathType Before:`n$PathOld`n"
$PathArray = $PathOld -Split ";" -replace "\\+$", ""
if ($PathArray -notcontains $PathToAdd) {
"$UserType $PathType Now:" # ; sleep -Milliseconds 100 # Might need pause to prevent text being after Path output(!)
$PathNew = "$PathOld;$PathToAdd"
Set-ItemProperty -Path $RegPropertyLocation -Name $PathType -Value $PathNew
Get-ItemProperty -Path $RegPropertyLocation -Name $PathType | select -ExpandProperty $PathType
if ($PathType -eq "Path") { $env:Path += ";$PathToAdd" } # Add to Path also for this current session
if ($PathType -eq "PSModulePath") { $env:PSModulePath += ";$PathToAdd" } # Add to PSModulePath also for this current session
"`n$PathToAdd has been added to the $UserType $PathType"
}
else {
"'$PathToAdd' is already in the $UserType $PathType. Nothing to do."
}
}
# Add "C:\XXX" to User Path (but only if not already present)
AddTo-Path "C:\XXX" "User" "Path"
# Just show the current status by putting an empty path
AddTo-Path "" "User" "Path"

Within PowerShell, one can navigate to the environment variable directory by typing:
Set-Location Env:
This will bring you to the Env:> directory. From within this directory:
To see all environment variables, type:
Env:\> Get-ChildItem
To see a specific environment variable, type:
Env:\> $Env:<variable name>, e.g. $Env:Path
To set an environment variable, type:
Env:\> $Env:<variable name> = "<new-value>", e.g. $Env:Path="C:\Users\"
To remove an environment variable, type:
Env:\> remove-item Env:<variable name>, e.g. remove-item Env:SECRET_KEY
More information is in About Environment Variables.

As Jonathan Leaders mentioned here, it is important to run the command/script elevated to be able to change environment variables for 'machine', but running some commands elevated doesn't have to be done with the Community Extensions, so I'd like to modify and extend JeanT's answer in a way, that changing machine variables also can be performed even if the script itself isn't run elevated:
function Set-Path ([string]$newPath, [bool]$permanent=$false, [bool]$forMachine=$false )
{
$Env:Path += ";$newPath"
$scope = if ($forMachine) { 'Machine' } else { 'User' }
if ($permanent)
{
$command = "[Environment]::SetEnvironmentVariable('PATH', $env:Path, $scope)"
Start-Process -FilePath powershell.exe -ArgumentList "-noprofile -command $Command" -Verb runas
}
}

Most answers aren't addressing UAC. This covers UAC issues.
First install PowerShell Community Extensions: choco install pscx via http://chocolatey.org/ (you may have to restart your shell environment).
Then enable pscx
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser #allows scripts to run from the interwebs, such as pcsx
Then use Invoke-Elevated
Invoke-Elevated {Add-PathVariable $args[0] -Target Machine} -ArgumentList $MY_NEW_DIR

To be clear, the 1990's Windows way of click on Start, right click on This PC, and choose Properties, and then select Advanced system settings, and then in the dialog box that pops up, select Environment Variables, and in the list double clicking on PATH and then using the New, Edit, Move Up and Move Down all still work for changing the PATH. Power shell, and the rest of Windows get whatever you set here.
Yes you can use these new methods, but the old one still works. And at the base level all of the permanent change methods are controlled ways of editing your registry files.

Open PowerShell and run:
[Environment]::SetEnvironmentVariable("PATH", "$ENV:PATH;<path to exe>", "USER")

These scripts are idempotent (can be run more than once).
They update both the Windows path and the current / future Powershell sessions:
Permanently add path
$targetDir="c:\bin"
$oldPath = [System.Environment]::GetEnvironmentVariable("Path","Machine")
$oldPathArray=($oldPath) -split ';'
if(-Not($oldPathArray -Contains "$targetDir")) {
write-host "Adding $targetDir to Machine Path"
$newPath = "$oldPath;$targetDir" -replace ';+', ';'
[System.Environment]::SetEnvironmentVariable("Path",$newPath,"Machine")
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","User"),[System.Environment]::GetEnvironmentVariable("Path","Machine") -join ";"
}
write-host "Windows paths:"
($env:Path).Replace(';',"`n")
Permanently remove path
$targetDir="c:\bin"
$oldPath = [System.Environment]::GetEnvironmentVariable("Path","Machine")
$oldPathArray=($oldPath) -split ';'
if($oldPathArray -Contains "$targetDir") {
write-host "Removing $targetDir from Machine path"
$newPathArray = $oldPathArray | Where-Object { $_ –ne "$targetDir" }
$newPath = $newPathArray -join ";"
[System.Environment]::SetEnvironmentVariable("Path",$newPath,"Machine")
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","User"),[System.Environment]::GetEnvironmentVariable("Path","Machine") -join ";"
}
write-host "Windows paths:"
($env:Path).Replace(';',"`n")

The simplest solution that I found to add C:\vcpkg permanently to my PATH env variable without drawbacks was :
$current_PATH = [Environment]::GetEnvironmentVariable("PATH", "USER");[Environment]::SetEnvironmentVariable("PATH", "$current_PATH;C:\vcpkg;", "USER")
You can change "USER" to "MACHINE" to change system env vars (need an admin terminal and you may need to change Environment to System.Environment) or even to "PROCESS" to only change local PATH env var (not permanently changed). Respectively "USER"=1 "MACHINE"=2 and "PROCESS"=0 here are the documentation about those commands: GetEnvironmentVariable SetEnvironmentVariable
Two other answers that I found but have major drawbacks and I do not recommend to use them. Both use SETX as it is implemented by PowerShell to change permanently env variable. The down side of those commandes are that you will duplicate your system PATH into your locale PATH and you need PowerShell to use them:
setx PATH "$($Env:PATH);C:\vcpkg;"
Longer but allow an usage with other env variable:
$($Env:PATH).Split(';') | %{ $str += "$($_.Trim('"'));" }; %{ $str += "C:\vcpkg;" } ; setx PATH $str; %{ $str = "" }

I tried to optimise SBF's and Michael's code a bit to make it more compact.
I am relying on PowerShell's type coercion where it automatically converts strings to enum values, so I didn't define the lookup dictionary.
I also pulled out the block that adds the new path to the list based on a condition, so that work is done once and stored in a variable for re-use.
It is then applied permanently or just to the Session depending on the $PathContainer parameter.
We can put the block of code in a function or a ps1 file that we call directly from the command prompt. I went with DevEnvAddPath.ps1.
param(
[Parameter(Position=0,Mandatory=$true)][String]$PathChange,
[ValidateSet('Machine', 'User', 'Session')]
[Parameter(Position=1,Mandatory=$false)][String]$PathContainer='Session',
[Parameter(Position=2,Mandatory=$false)][Boolean]$PathPrepend=$false
)
[String]$ConstructedEnvPath = switch ($PathContainer) { "Session"{${env:Path};} default{[Environment]::GetEnvironmentVariable('Path', $containerType);} };
$PathPersisted = $ConstructedEnvPath -split ';';
if ($PathPersisted -notcontains $PathChange) {
$PathPersisted = $(switch ($PathPrepend) { $true{,$PathChange + $PathPersisted;} default{$PathPersisted + $PathChange;} }) | Where-Object { $_ };
$ConstructedEnvPath = $PathPersisted -join ";";
}
if ($PathContainer -ne 'Session')
{
# Save permanently to Machine, User
[Environment]::SetEnvironmentVariable("Path", $ConstructedEnvPath, $PathContainer);
}
# Update the current session
${env:Path} = $ConstructedEnvPath;
I do something similar for a DevEnvRemovePath.ps1.
param(
[Parameter(Position=0,Mandatory=$true)][String]$PathChange,
[ValidateSet('Machine', 'User', 'Session')]
[Parameter(Position=1,Mandatory=$false)][String]$PathContainer='Session'
)
[String]$ConstructedEnvPath = switch ($PathContainer) { "Session"{${env:Path};} default{[Environment]::GetEnvironmentVariable('Path', $containerType);} };
$PathPersisted = $ConstructedEnvPath -split ';';
if ($PathPersisted -contains $PathChange) {
$PathPersisted = $PathPersisted | Where-Object { $_ -ne $PathChange };
$ConstructedEnvPath = $PathPersisted -join ";";
}
if ($PathContainer -ne 'Session')
{
# Save permanently to Machine, User
[Environment]::SetEnvironmentVariable("Path", $ConstructedEnvPath, $PathContainer);
}
# Update the current session
${env:Path} = $ConstructedEnvPath;
So far, they seem to work.

Lots of examples of appending, or overwriting. Here is an example of prepending a path on powershell for Linux, Ubuntu 18.04 with pwsh 7.1.3
$ENV:PATH = "/home/linuxbrew/.linuxbrew/bin:$ENV:PATH"
I'm specifically adding the linuxbrew (homebrew for linux) bin directory to take precedence over the system installed. It helped solve an issue I was having and although this was the most helpful place, it also left me "experimenting".
Note that the : is Linux path separator, whereas on Windows (or at least my windows) you would use ; for powershell typically.

Editing the registry key in #ali Darabi's answer worked best for me, but
When I didn't have the right permissions to do it from Powershell. So I edited it directly in regedit.
I want to expand further on the subject in this answer.
Restarting Powershell also wasn't sufficient to propagate the change. I had to Open Task Manager and restart explorer.exe to trigger a reload of the registry.
It can be quite tedious to navigate the registry so in order to maintain an user friendly experience you can execute this from Powershell:
REG ADD "HKCU\Software\Microsoft\Windows\CurrentVersion\Applets\Regedit" /v "LastKey" /d "HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment" /f; regedit
It sets the last opened window to a certain registry path, so that when you open regedit the next time it opens at the proper key.

If you need to set variable name dynamically and for session only, then use:
New-Item env:\$key -Value $value -Force | Out-Null

Related

How can I find and set the system PATH using PowerShell [duplicate]

I have found out that setting the PATH environment variable affects only the old command prompt. PowerShell seems to have different environment settings. How do I change the environment variables for PowerShell (v1)?
Note:
I want to make my changes permanent, so I don't have to set it every time I run PowerShell. Does PowerShell have a profile file? Something like Bash profile on Unix?
If, some time during a PowerShell session, you need to see or to temporarily modify the PATH environment variable , you can type one of these commands:
$env:Path # shows the actual content
$env:Path = 'C:\foo;' + $env:Path # attach to the beginning
$env:Path += ';C:\foo' # attach to the end
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
You can also modify user/system environment variables permanently (i.e. will be persistent across shell restarts) with the following:
Modify a system environment variable
[Environment]::SetEnvironmentVariable
("Path", $env:Path, [System.EnvironmentVariableTarget]::Machine)
Modify a user environment variable
[Environment]::SetEnvironmentVariable
("INCLUDE", $env:INCLUDE, [System.EnvironmentVariableTarget]::User)
Usage from comments - add to the system environment variable
[Environment]::SetEnvironmentVariable(
"Path",
[Environment]::GetEnvironmentVariable("Path", [EnvironmentVariableTarget]::Machine) + ";C:\bin",
[EnvironmentVariableTarget]::Machine)
String based solution is also possible if you don't want to write types
[Environment]::SetEnvironmentVariable("Path", $env:Path + ";C:\bin", "Machine")
WARNING: save a copy of your existing path by doing $env:path >> a.out in a PowerShell prompt, in case something goes wrong.
From the PowerShell prompt:
setx PATH "$env:path;\the\directory\to\add" -m
You should then see the text:
SUCCESS: Specified value was saved.
Restart your session, and the variable will be available. setx can also be used to set arbitrary variables. Type setx /? at the prompt for documentation.
Like JeanT's answer, I wanted an abstraction around adding to the path. Unlike JeanT's answer I needed it to run without user interaction. Other behavior I was looking for:
Updates $env:Path so the change takes effect in the current session
Persists the environment variable change for future sessions
Doesn't add a duplicate path when the same path already exists
In case it's useful, here it is:
function Add-EnvPath {
param(
[Parameter(Mandatory=$true)]
[string] $Path,
[ValidateSet('Machine', 'User', 'Session')]
[string] $Container = 'Session'
)
if ($Container -ne 'Session') {
$containerMapping = #{
Machine = [EnvironmentVariableTarget]::Machine
User = [EnvironmentVariableTarget]::User
}
$containerType = $containerMapping[$Container]
$persistedPaths = [Environment]::GetEnvironmentVariable('Path', $containerType) -split ';'
if ($persistedPaths -notcontains $Path) {
$persistedPaths = $persistedPaths + $Path | where { $_ }
[Environment]::SetEnvironmentVariable('Path', $persistedPaths -join ';', $containerType)
}
}
$envPaths = $env:Path -split ';'
if ($envPaths -notcontains $Path) {
$envPaths = $envPaths + $Path | where { $_ }
$env:Path = $envPaths -join ';'
}
}
Check out my gist for the corresponding Remove-EnvPath function.
Pain-free, one-line, example solutions
Try these three commands to practice setting and deleting environmental variables in PowerShell.
Notes on usage:
Run these commands on elevated PowerShell (e.g. with administrator privileges).
After every step, to make your commands work, close the session and open it again.
Add/create a permanent environment variable:
[Environment]::SetEnvironmentVariable("MyEnvVar", "NewEnvValue", "Machine")
Machine is an EnvironmentVariableTarget that will apply to current and future users, as opposed to the User target.
Modify/change environmental variable:
[Environment]::SetEnvironmentVariable("MyEnvVar", "NewerEnvValue", "Machine")
Delete/remove variable:
[Environment]::SetEnvironmentVariable("MyEnvVar", "", "Machine")
All the answers suggesting a permanent change have the same problem: They break the path registry value.
SetEnvironmentVariable turns the REG_EXPAND_SZ value %SystemRoot%\system32 into a REG_SZ value of C:\Windows\system32.
Any other variables in the path are lost as well. Adding new ones using %myNewPath% won't work any more.
Here's a script Set-PathVariable.ps1 that I use to address this problem:
[CmdletBinding(SupportsShouldProcess=$true)]
param(
[parameter(Mandatory=$true)]
[string]$NewLocation)
Begin
{
#requires –runasadministrator
$regPath = "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"
$hklm = [Microsoft.Win32.Registry]::LocalMachine
Function GetOldPath()
{
$regKey = $hklm.OpenSubKey($regPath, $FALSE)
$envpath = $regKey.GetValue("Path", "", [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
return $envPath
}
}
Process
{
# Win32API error codes
$ERROR_SUCCESS = 0
$ERROR_DUP_NAME = 34
$ERROR_INVALID_DATA = 13
$NewLocation = $NewLocation.Trim();
If ($NewLocation -eq "" -or $NewLocation -eq $null)
{
Exit $ERROR_INVALID_DATA
}
[string]$oldPath = GetOldPath
Write-Verbose "Old Path: $oldPath"
# Check whether the new location is already in the path
$parts = $oldPath.split(";")
If ($parts -contains $NewLocation)
{
Write-Warning "The new location is already in the path"
Exit $ERROR_DUP_NAME
}
# Build the new path, make sure we don't have double semicolons
$newPath = $oldPath + ";" + $NewLocation
$newPath = $newPath -replace ";;",""
if ($pscmdlet.ShouldProcess("%Path%", "Add $NewLocation")){
# Add to the current session
$env:path += ";$NewLocation"
# Save into registry
$regKey = $hklm.OpenSubKey($regPath, $True)
$regKey.SetValue("Path", $newPath, [Microsoft.Win32.RegistryValueKind]::ExpandString)
Write-Output "The operation completed successfully."
}
Exit $ERROR_SUCCESS
}
I explain the problem in more detail in a blog post.
Although the current accepted answer works in the sense that the path variable gets permanently updated from the context of PowerShell, it doesn't actually update the environment variable stored in the Windows registry.
To achieve that, you can obviously use PowerShell as well:
$oldPath=(Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH).Path
$newPath=$oldPath+’;C:\NewFolderToAddToTheList\’
Set-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH –Value $newPath
More information is in blog post Use PowerShell to Modify Your Environmental Path
If you use PowerShell community extensions, the proper command to add a path to the environment variable path is:
Add-PathVariable "C:\NewFolderToAddToTheList" -Target Machine
This sets the path for the current session and prompts the user to add it permanently:
function Set-Path {
param([string]$x)
$Env:Path+= ";" + $x
Write-Output $Env:Path
$write = Read-Host 'Set PATH permanently ? (yes|no)'
if ($write -eq "yes")
{
[Environment]::SetEnvironmentVariable("Path",$env:Path, [System.EnvironmentVariableTarget]::User)
Write-Output 'PATH updated'
}
}
You can add this function to your default profile, (Microsoft.PowerShell_profile.ps1), usually located at %USERPROFILE%\Documents\WindowsPowerShell.
Building on #Michael Kropat's answer I added a parameter to prepend the new path to the existing PATHvariable and a check to avoid the addition of a non-existing path:
function Add-EnvPath {
param(
[Parameter(Mandatory=$true)]
[string] $Path,
[ValidateSet('Machine', 'User', 'Session')]
[string] $Container = 'Session',
[Parameter(Mandatory=$False)]
[Switch] $Prepend
)
if (Test-Path -path "$Path") {
if ($Container -ne 'Session') {
$containerMapping = #{
Machine = [EnvironmentVariableTarget]::Machine
User = [EnvironmentVariableTarget]::User
}
$containerType = $containerMapping[$Container]
$persistedPaths = [Environment]::GetEnvironmentVariable('Path', $containerType) -split ';'
if ($persistedPaths -notcontains $Path) {
if ($Prepend) {
$persistedPaths = ,$Path + $persistedPaths | where { $_ }
[Environment]::SetEnvironmentVariable('Path', $persistedPaths -join ';', $containerType)
}
else {
$persistedPaths = $persistedPaths + $Path | where { $_ }
[Environment]::SetEnvironmentVariable('Path', $persistedPaths -join ';', $containerType)
}
}
}
$envPaths = $env:Path -split ';'
if ($envPaths -notcontains $Path) {
if ($Prepend) {
$envPaths = ,$Path + $envPaths | where { $_ }
$env:Path = $envPaths -join ';'
}
else {
$envPaths = $envPaths + $Path | where { $_ }
$env:Path = $envPaths -join ';'
}
}
}
}
My suggestion is this one:
I have tested this to add C:\oracle\x64\bin to environment variable Path permanently and this works fine.
$ENV:PATH
The first way is simply to do:
$ENV:PATH=”$ENV:PATH;c:\path\to\folder”
But this change isn’t permanent. $env:path will default back to what it was before as soon as you close your PowerShell terminal and reopen it again. That’s because you have applied the change at the session level and not at the source level (which is the registry level). To view the global value of $env:path, do:
Get-ItemProperty -Path ‘Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment’ -Name PATH
Or more specifically:
(Get-ItemProperty -Path ‘Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment’ -Name PATH).path
Now to change this, first we capture the original path that needs to be modified:
$oldpath = (Get-ItemProperty -Path ‘Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment’ -Name PATH).path
Now we define what the new path should look like. In this case we are appending a new folder:
$newpath = “$oldpath;c:\path\to\folder”
Note: Be sure that the $newpath looks how you want it to look. If not, then you could damage your OS.
Now apply the new value:
Set-ItemProperty -Path ‘Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment’ -Name PATH -Value $newPath
Now do one final check that it looks like how you expect it to:
(Get-ItemProperty -Path ‘Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment’ -Name PATH).Path
You can now restart your PowerShell terminal (or even reboot the machine) and see that it doesn’t rollback to its old value again.
Note the ordering of the paths may change so that it’s in alphabetical order, so make sure you check the whole line. To make it easier, you can split the output into rows by using the semi-colon as a delimiter:
($env:path).split(“;”)
Only the answers that push the value into the registry affect a permanent change (so the majority of answers on this thread, including the accepted answer, do not permanently affect the Path).
The following function works for both Path / PSModulePath and for User / System types. It will also add the new path to the current session by default.
function AddTo-Path {
param (
[string]$PathToAdd,
[Parameter(Mandatory=$true)][ValidateSet('System','User')][string]$UserType,
[Parameter(Mandatory=$true)][ValidateSet('Path','PSModulePath')][string]$PathType
)
# AddTo-Path "C:\XXX" "PSModulePath" 'System'
if ($UserType -eq "System" ) { $RegPropertyLocation = 'HKLM:\System\CurrentControlSet\Control\Session Manager\Environment' }
if ($UserType -eq "User" ) { $RegPropertyLocation = 'HKCU:\Environment' } # also note: Registry::HKEY_LOCAL_MACHINE\ format
$PathOld = (Get-ItemProperty -Path $RegPropertyLocation -Name $PathType).$PathType
"`n$UserType $PathType Before:`n$PathOld`n"
$PathArray = $PathOld -Split ";" -replace "\\+$", ""
if ($PathArray -notcontains $PathToAdd) {
"$UserType $PathType Now:" # ; sleep -Milliseconds 100 # Might need pause to prevent text being after Path output(!)
$PathNew = "$PathOld;$PathToAdd"
Set-ItemProperty -Path $RegPropertyLocation -Name $PathType -Value $PathNew
Get-ItemProperty -Path $RegPropertyLocation -Name $PathType | select -ExpandProperty $PathType
if ($PathType -eq "Path") { $env:Path += ";$PathToAdd" } # Add to Path also for this current session
if ($PathType -eq "PSModulePath") { $env:PSModulePath += ";$PathToAdd" } # Add to PSModulePath also for this current session
"`n$PathToAdd has been added to the $UserType $PathType"
}
else {
"'$PathToAdd' is already in the $UserType $PathType. Nothing to do."
}
}
# Add "C:\XXX" to User Path (but only if not already present)
AddTo-Path "C:\XXX" "User" "Path"
# Just show the current status by putting an empty path
AddTo-Path "" "User" "Path"
Within PowerShell, one can navigate to the environment variable directory by typing:
Set-Location Env:
This will bring you to the Env:> directory. From within this directory:
To see all environment variables, type:
Env:\> Get-ChildItem
To see a specific environment variable, type:
Env:\> $Env:<variable name>, e.g. $Env:Path
To set an environment variable, type:
Env:\> $Env:<variable name> = "<new-value>", e.g. $Env:Path="C:\Users\"
To remove an environment variable, type:
Env:\> remove-item Env:<variable name>, e.g. remove-item Env:SECRET_KEY
More information is in About Environment Variables.
As Jonathan Leaders mentioned here, it is important to run the command/script elevated to be able to change environment variables for 'machine', but running some commands elevated doesn't have to be done with the Community Extensions, so I'd like to modify and extend JeanT's answer in a way, that changing machine variables also can be performed even if the script itself isn't run elevated:
function Set-Path ([string]$newPath, [bool]$permanent=$false, [bool]$forMachine=$false )
{
$Env:Path += ";$newPath"
$scope = if ($forMachine) { 'Machine' } else { 'User' }
if ($permanent)
{
$command = "[Environment]::SetEnvironmentVariable('PATH', $env:Path, $scope)"
Start-Process -FilePath powershell.exe -ArgumentList "-noprofile -command $Command" -Verb runas
}
}
Most answers aren't addressing UAC. This covers UAC issues.
First install PowerShell Community Extensions: choco install pscx via http://chocolatey.org/ (you may have to restart your shell environment).
Then enable pscx
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser #allows scripts to run from the interwebs, such as pcsx
Then use Invoke-Elevated
Invoke-Elevated {Add-PathVariable $args[0] -Target Machine} -ArgumentList $MY_NEW_DIR
To be clear, the 1990's Windows way of click on Start, right click on This PC, and choose Properties, and then select Advanced system settings, and then in the dialog box that pops up, select Environment Variables, and in the list double clicking on PATH and then using the New, Edit, Move Up and Move Down all still work for changing the PATH. Power shell, and the rest of Windows get whatever you set here.
Yes you can use these new methods, but the old one still works. And at the base level all of the permanent change methods are controlled ways of editing your registry files.
Open PowerShell and run:
[Environment]::SetEnvironmentVariable("PATH", "$ENV:PATH;<path to exe>", "USER")
These scripts are idempotent (can be run more than once).
They update both the Windows path and the current / future Powershell sessions:
Permanently add path
$targetDir="c:\bin"
$oldPath = [System.Environment]::GetEnvironmentVariable("Path","Machine")
$oldPathArray=($oldPath) -split ';'
if(-Not($oldPathArray -Contains "$targetDir")) {
write-host "Adding $targetDir to Machine Path"
$newPath = "$oldPath;$targetDir" -replace ';+', ';'
[System.Environment]::SetEnvironmentVariable("Path",$newPath,"Machine")
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","User"),[System.Environment]::GetEnvironmentVariable("Path","Machine") -join ";"
}
write-host "Windows paths:"
($env:Path).Replace(';',"`n")
Permanently remove path
$targetDir="c:\bin"
$oldPath = [System.Environment]::GetEnvironmentVariable("Path","Machine")
$oldPathArray=($oldPath) -split ';'
if($oldPathArray -Contains "$targetDir") {
write-host "Removing $targetDir from Machine path"
$newPathArray = $oldPathArray | Where-Object { $_ –ne "$targetDir" }
$newPath = $newPathArray -join ";"
[System.Environment]::SetEnvironmentVariable("Path",$newPath,"Machine")
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","User"),[System.Environment]::GetEnvironmentVariable("Path","Machine") -join ";"
}
write-host "Windows paths:"
($env:Path).Replace(';',"`n")
The simplest solution that I found to add C:\vcpkg permanently to my PATH env variable without drawbacks was :
$current_PATH = [Environment]::GetEnvironmentVariable("PATH", "USER");[Environment]::SetEnvironmentVariable("PATH", "$current_PATH;C:\vcpkg;", "USER")
You can change "USER" to "MACHINE" to change system env vars (need an admin terminal and you may need to change Environment to System.Environment) or even to "PROCESS" to only change local PATH env var (not permanently changed). Respectively "USER"=1 "MACHINE"=2 and "PROCESS"=0 here are the documentation about those commands: GetEnvironmentVariable SetEnvironmentVariable
Two other answers that I found but have major drawbacks and I do not recommend to use them. Both use SETX as it is implemented by PowerShell to change permanently env variable. The down side of those commandes are that you will duplicate your system PATH into your locale PATH and you need PowerShell to use them:
setx PATH "$($Env:PATH);C:\vcpkg;"
Longer but allow an usage with other env variable:
$($Env:PATH).Split(';') | %{ $str += "$($_.Trim('"'));" }; %{ $str += "C:\vcpkg;" } ; setx PATH $str; %{ $str = "" }
I tried to optimise SBF's and Michael's code a bit to make it more compact.
I am relying on PowerShell's type coercion where it automatically converts strings to enum values, so I didn't define the lookup dictionary.
I also pulled out the block that adds the new path to the list based on a condition, so that work is done once and stored in a variable for re-use.
It is then applied permanently or just to the Session depending on the $PathContainer parameter.
We can put the block of code in a function or a ps1 file that we call directly from the command prompt. I went with DevEnvAddPath.ps1.
param(
[Parameter(Position=0,Mandatory=$true)][String]$PathChange,
[ValidateSet('Machine', 'User', 'Session')]
[Parameter(Position=1,Mandatory=$false)][String]$PathContainer='Session',
[Parameter(Position=2,Mandatory=$false)][Boolean]$PathPrepend=$false
)
[String]$ConstructedEnvPath = switch ($PathContainer) { "Session"{${env:Path};} default{[Environment]::GetEnvironmentVariable('Path', $containerType);} };
$PathPersisted = $ConstructedEnvPath -split ';';
if ($PathPersisted -notcontains $PathChange) {
$PathPersisted = $(switch ($PathPrepend) { $true{,$PathChange + $PathPersisted;} default{$PathPersisted + $PathChange;} }) | Where-Object { $_ };
$ConstructedEnvPath = $PathPersisted -join ";";
}
if ($PathContainer -ne 'Session')
{
# Save permanently to Machine, User
[Environment]::SetEnvironmentVariable("Path", $ConstructedEnvPath, $PathContainer);
}
# Update the current session
${env:Path} = $ConstructedEnvPath;
I do something similar for a DevEnvRemovePath.ps1.
param(
[Parameter(Position=0,Mandatory=$true)][String]$PathChange,
[ValidateSet('Machine', 'User', 'Session')]
[Parameter(Position=1,Mandatory=$false)][String]$PathContainer='Session'
)
[String]$ConstructedEnvPath = switch ($PathContainer) { "Session"{${env:Path};} default{[Environment]::GetEnvironmentVariable('Path', $containerType);} };
$PathPersisted = $ConstructedEnvPath -split ';';
if ($PathPersisted -contains $PathChange) {
$PathPersisted = $PathPersisted | Where-Object { $_ -ne $PathChange };
$ConstructedEnvPath = $PathPersisted -join ";";
}
if ($PathContainer -ne 'Session')
{
# Save permanently to Machine, User
[Environment]::SetEnvironmentVariable("Path", $ConstructedEnvPath, $PathContainer);
}
# Update the current session
${env:Path} = $ConstructedEnvPath;
So far, they seem to work.
Lots of examples of appending, or overwriting. Here is an example of prepending a path on powershell for Linux, Ubuntu 18.04 with pwsh 7.1.3
$ENV:PATH = "/home/linuxbrew/.linuxbrew/bin:$ENV:PATH"
I'm specifically adding the linuxbrew (homebrew for linux) bin directory to take precedence over the system installed. It helped solve an issue I was having and although this was the most helpful place, it also left me "experimenting".
Note that the : is Linux path separator, whereas on Windows (or at least my windows) you would use ; for powershell typically.
Editing the registry key in #ali Darabi's answer worked best for me, but
When I didn't have the right permissions to do it from Powershell. So I edited it directly in regedit.
I want to expand further on the subject in this answer.
Restarting Powershell also wasn't sufficient to propagate the change. I had to Open Task Manager and restart explorer.exe to trigger a reload of the registry.
It can be quite tedious to navigate the registry so in order to maintain an user friendly experience you can execute this from Powershell:
REG ADD "HKCU\Software\Microsoft\Windows\CurrentVersion\Applets\Regedit" /v "LastKey" /d "HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment" /f; regedit
It sets the last opened window to a certain registry path, so that when you open regedit the next time it opens at the proper key.
If you need to set variable name dynamically and for session only, then use:
New-Item env:\$key -Value $value -Force | Out-Null

Hidden variable Password parameter - powershell 4.0

I am creating a release definition with a powershell script to replace a file with env variables from the release definition it works but It doesn't seem to catch the password variable which is hidden in the release definition. is there a way to tell powershell to look for hidden variables?
UPDATE: Here is the script it finds all the variables in $paramsFilePath that are not hidden my password in In environmental variables in Release definition is hidden and the script doesn't find it.
param(
[string]$paramsFilePath,
)
Write-Verbose -Verbose "Entering script Replace-SetParameters.ps1"
Write-Verbose -Verbose ("Path to SetParametersFile: {0}" -f $paramsFilePath)
# get the environment variables
$vars = Get-ChildItem -path env:*
# read in the setParameters file
$contents = Get-Content -Path $paramsFilePath
# perform a regex replacement
$newContents = "";
$contents | % {
$line = $_
if ($_ -match "__(\w+)__") {
$setting = Get-ChildItem -path env:* | ? { $_.Name -eq $Matches[1] }
if ($setting) {
Write-Verbose -Verbose ("Replacing key {0} with value from environment" -f $setting.Name)
$line = $_ -replace "__(\w+)__", $setting.Value
}
}
$newContents += $line + [Environment]::NewLine
}
Write-Verbose -Verbose "Overwriting SetParameters file with new values"
Set-Content $paramsFilePath -Value $newContents
Write-Verbose -Verbose "Exiting script Replace-SetParameters.ps1"
Unlike the normal variable, the password you are trying to get is secret variable.
Secret Variables
We recommend that you make the variable Secret if it contains a
password, keys, or some other kind of data that you need to avoid
exposing.
The variable replacement we do is on the inputs on the tasks, we don't parse the scripts. To use secret variables you will have to take those as inputs into your script we explicitly do not populate those into the environment. You could take a look at this discuss: Use hidden / secret variables in commands

powershell refresh env path for the active session [duplicate]

I have found out that setting the PATH environment variable affects only the old command prompt. PowerShell seems to have different environment settings. How do I change the environment variables for PowerShell (v1)?
Note:
I want to make my changes permanent, so I don't have to set it every time I run PowerShell. Does PowerShell have a profile file? Something like Bash profile on Unix?
If, some time during a PowerShell session, you need to see or to temporarily modify the PATH environment variable , you can type one of these commands:
$env:Path # shows the actual content
$env:Path = 'C:\foo;' + $env:Path # attach to the beginning
$env:Path += ';C:\foo' # attach to the end
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
You can also modify user/system environment variables permanently (i.e. will be persistent across shell restarts) with the following:
Modify a system environment variable
[Environment]::SetEnvironmentVariable
("Path", $env:Path, [System.EnvironmentVariableTarget]::Machine)
Modify a user environment variable
[Environment]::SetEnvironmentVariable
("INCLUDE", $env:INCLUDE, [System.EnvironmentVariableTarget]::User)
Usage from comments - add to the system environment variable
[Environment]::SetEnvironmentVariable(
"Path",
[Environment]::GetEnvironmentVariable("Path", [EnvironmentVariableTarget]::Machine) + ";C:\bin",
[EnvironmentVariableTarget]::Machine)
String based solution is also possible if you don't want to write types
[Environment]::SetEnvironmentVariable("Path", $env:Path + ";C:\bin", "Machine")
WARNING: save a copy of your existing path by doing $env:path >> a.out in a PowerShell prompt, in case something goes wrong.
From the PowerShell prompt:
setx PATH "$env:path;\the\directory\to\add" -m
You should then see the text:
SUCCESS: Specified value was saved.
Restart your session, and the variable will be available. setx can also be used to set arbitrary variables. Type setx /? at the prompt for documentation.
Like JeanT's answer, I wanted an abstraction around adding to the path. Unlike JeanT's answer I needed it to run without user interaction. Other behavior I was looking for:
Updates $env:Path so the change takes effect in the current session
Persists the environment variable change for future sessions
Doesn't add a duplicate path when the same path already exists
In case it's useful, here it is:
function Add-EnvPath {
param(
[Parameter(Mandatory=$true)]
[string] $Path,
[ValidateSet('Machine', 'User', 'Session')]
[string] $Container = 'Session'
)
if ($Container -ne 'Session') {
$containerMapping = #{
Machine = [EnvironmentVariableTarget]::Machine
User = [EnvironmentVariableTarget]::User
}
$containerType = $containerMapping[$Container]
$persistedPaths = [Environment]::GetEnvironmentVariable('Path', $containerType) -split ';'
if ($persistedPaths -notcontains $Path) {
$persistedPaths = $persistedPaths + $Path | where { $_ }
[Environment]::SetEnvironmentVariable('Path', $persistedPaths -join ';', $containerType)
}
}
$envPaths = $env:Path -split ';'
if ($envPaths -notcontains $Path) {
$envPaths = $envPaths + $Path | where { $_ }
$env:Path = $envPaths -join ';'
}
}
Check out my gist for the corresponding Remove-EnvPath function.
Pain-free, one-line, example solutions
Try these three commands to practice setting and deleting environmental variables in PowerShell.
Notes on usage:
Run these commands on elevated PowerShell (e.g. with administrator privileges).
After every step, to make your commands work, close the session and open it again.
Add/create a permanent environment variable:
[Environment]::SetEnvironmentVariable("MyEnvVar", "NewEnvValue", "Machine")
Machine is an EnvironmentVariableTarget that will apply to current and future users, as opposed to the User target.
Modify/change environmental variable:
[Environment]::SetEnvironmentVariable("MyEnvVar", "NewerEnvValue", "Machine")
Delete/remove variable:
[Environment]::SetEnvironmentVariable("MyEnvVar", "", "Machine")
All the answers suggesting a permanent change have the same problem: They break the path registry value.
SetEnvironmentVariable turns the REG_EXPAND_SZ value %SystemRoot%\system32 into a REG_SZ value of C:\Windows\system32.
Any other variables in the path are lost as well. Adding new ones using %myNewPath% won't work any more.
Here's a script Set-PathVariable.ps1 that I use to address this problem:
[CmdletBinding(SupportsShouldProcess=$true)]
param(
[parameter(Mandatory=$true)]
[string]$NewLocation)
Begin
{
#requires –runasadministrator
$regPath = "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"
$hklm = [Microsoft.Win32.Registry]::LocalMachine
Function GetOldPath()
{
$regKey = $hklm.OpenSubKey($regPath, $FALSE)
$envpath = $regKey.GetValue("Path", "", [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
return $envPath
}
}
Process
{
# Win32API error codes
$ERROR_SUCCESS = 0
$ERROR_DUP_NAME = 34
$ERROR_INVALID_DATA = 13
$NewLocation = $NewLocation.Trim();
If ($NewLocation -eq "" -or $NewLocation -eq $null)
{
Exit $ERROR_INVALID_DATA
}
[string]$oldPath = GetOldPath
Write-Verbose "Old Path: $oldPath"
# Check whether the new location is already in the path
$parts = $oldPath.split(";")
If ($parts -contains $NewLocation)
{
Write-Warning "The new location is already in the path"
Exit $ERROR_DUP_NAME
}
# Build the new path, make sure we don't have double semicolons
$newPath = $oldPath + ";" + $NewLocation
$newPath = $newPath -replace ";;",""
if ($pscmdlet.ShouldProcess("%Path%", "Add $NewLocation")){
# Add to the current session
$env:path += ";$NewLocation"
# Save into registry
$regKey = $hklm.OpenSubKey($regPath, $True)
$regKey.SetValue("Path", $newPath, [Microsoft.Win32.RegistryValueKind]::ExpandString)
Write-Output "The operation completed successfully."
}
Exit $ERROR_SUCCESS
}
I explain the problem in more detail in a blog post.
Although the current accepted answer works in the sense that the path variable gets permanently updated from the context of PowerShell, it doesn't actually update the environment variable stored in the Windows registry.
To achieve that, you can obviously use PowerShell as well:
$oldPath=(Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH).Path
$newPath=$oldPath+’;C:\NewFolderToAddToTheList\’
Set-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH –Value $newPath
More information is in blog post Use PowerShell to Modify Your Environmental Path
If you use PowerShell community extensions, the proper command to add a path to the environment variable path is:
Add-PathVariable "C:\NewFolderToAddToTheList" -Target Machine
This sets the path for the current session and prompts the user to add it permanently:
function Set-Path {
param([string]$x)
$Env:Path+= ";" + $x
Write-Output $Env:Path
$write = Read-Host 'Set PATH permanently ? (yes|no)'
if ($write -eq "yes")
{
[Environment]::SetEnvironmentVariable("Path",$env:Path, [System.EnvironmentVariableTarget]::User)
Write-Output 'PATH updated'
}
}
You can add this function to your default profile, (Microsoft.PowerShell_profile.ps1), usually located at %USERPROFILE%\Documents\WindowsPowerShell.
Building on #Michael Kropat's answer I added a parameter to prepend the new path to the existing PATHvariable and a check to avoid the addition of a non-existing path:
function Add-EnvPath {
param(
[Parameter(Mandatory=$true)]
[string] $Path,
[ValidateSet('Machine', 'User', 'Session')]
[string] $Container = 'Session',
[Parameter(Mandatory=$False)]
[Switch] $Prepend
)
if (Test-Path -path "$Path") {
if ($Container -ne 'Session') {
$containerMapping = #{
Machine = [EnvironmentVariableTarget]::Machine
User = [EnvironmentVariableTarget]::User
}
$containerType = $containerMapping[$Container]
$persistedPaths = [Environment]::GetEnvironmentVariable('Path', $containerType) -split ';'
if ($persistedPaths -notcontains $Path) {
if ($Prepend) {
$persistedPaths = ,$Path + $persistedPaths | where { $_ }
[Environment]::SetEnvironmentVariable('Path', $persistedPaths -join ';', $containerType)
}
else {
$persistedPaths = $persistedPaths + $Path | where { $_ }
[Environment]::SetEnvironmentVariable('Path', $persistedPaths -join ';', $containerType)
}
}
}
$envPaths = $env:Path -split ';'
if ($envPaths -notcontains $Path) {
if ($Prepend) {
$envPaths = ,$Path + $envPaths | where { $_ }
$env:Path = $envPaths -join ';'
}
else {
$envPaths = $envPaths + $Path | where { $_ }
$env:Path = $envPaths -join ';'
}
}
}
}
My suggestion is this one:
I have tested this to add C:\oracle\x64\bin to environment variable Path permanently and this works fine.
$ENV:PATH
The first way is simply to do:
$ENV:PATH=”$ENV:PATH;c:\path\to\folder”
But this change isn’t permanent. $env:path will default back to what it was before as soon as you close your PowerShell terminal and reopen it again. That’s because you have applied the change at the session level and not at the source level (which is the registry level). To view the global value of $env:path, do:
Get-ItemProperty -Path ‘Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment’ -Name PATH
Or more specifically:
(Get-ItemProperty -Path ‘Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment’ -Name PATH).path
Now to change this, first we capture the original path that needs to be modified:
$oldpath = (Get-ItemProperty -Path ‘Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment’ -Name PATH).path
Now we define what the new path should look like. In this case we are appending a new folder:
$newpath = “$oldpath;c:\path\to\folder”
Note: Be sure that the $newpath looks how you want it to look. If not, then you could damage your OS.
Now apply the new value:
Set-ItemProperty -Path ‘Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment’ -Name PATH -Value $newPath
Now do one final check that it looks like how you expect it to:
(Get-ItemProperty -Path ‘Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment’ -Name PATH).Path
You can now restart your PowerShell terminal (or even reboot the machine) and see that it doesn’t rollback to its old value again.
Note the ordering of the paths may change so that it’s in alphabetical order, so make sure you check the whole line. To make it easier, you can split the output into rows by using the semi-colon as a delimiter:
($env:path).split(“;”)
Only the answers that push the value into the registry affect a permanent change (so the majority of answers on this thread, including the accepted answer, do not permanently affect the Path).
The following function works for both Path / PSModulePath and for User / System types. It will also add the new path to the current session by default.
function AddTo-Path {
param (
[string]$PathToAdd,
[Parameter(Mandatory=$true)][ValidateSet('System','User')][string]$UserType,
[Parameter(Mandatory=$true)][ValidateSet('Path','PSModulePath')][string]$PathType
)
# AddTo-Path "C:\XXX" "PSModulePath" 'System'
if ($UserType -eq "System" ) { $RegPropertyLocation = 'HKLM:\System\CurrentControlSet\Control\Session Manager\Environment' }
if ($UserType -eq "User" ) { $RegPropertyLocation = 'HKCU:\Environment' } # also note: Registry::HKEY_LOCAL_MACHINE\ format
$PathOld = (Get-ItemProperty -Path $RegPropertyLocation -Name $PathType).$PathType
"`n$UserType $PathType Before:`n$PathOld`n"
$PathArray = $PathOld -Split ";" -replace "\\+$", ""
if ($PathArray -notcontains $PathToAdd) {
"$UserType $PathType Now:" # ; sleep -Milliseconds 100 # Might need pause to prevent text being after Path output(!)
$PathNew = "$PathOld;$PathToAdd"
Set-ItemProperty -Path $RegPropertyLocation -Name $PathType -Value $PathNew
Get-ItemProperty -Path $RegPropertyLocation -Name $PathType | select -ExpandProperty $PathType
if ($PathType -eq "Path") { $env:Path += ";$PathToAdd" } # Add to Path also for this current session
if ($PathType -eq "PSModulePath") { $env:PSModulePath += ";$PathToAdd" } # Add to PSModulePath also for this current session
"`n$PathToAdd has been added to the $UserType $PathType"
}
else {
"'$PathToAdd' is already in the $UserType $PathType. Nothing to do."
}
}
# Add "C:\XXX" to User Path (but only if not already present)
AddTo-Path "C:\XXX" "User" "Path"
# Just show the current status by putting an empty path
AddTo-Path "" "User" "Path"
Within PowerShell, one can navigate to the environment variable directory by typing:
Set-Location Env:
This will bring you to the Env:> directory. From within this directory:
To see all environment variables, type:
Env:\> Get-ChildItem
To see a specific environment variable, type:
Env:\> $Env:<variable name>, e.g. $Env:Path
To set an environment variable, type:
Env:\> $Env:<variable name> = "<new-value>", e.g. $Env:Path="C:\Users\"
To remove an environment variable, type:
Env:\> remove-item Env:<variable name>, e.g. remove-item Env:SECRET_KEY
More information is in About Environment Variables.
As Jonathan Leaders mentioned here, it is important to run the command/script elevated to be able to change environment variables for 'machine', but running some commands elevated doesn't have to be done with the Community Extensions, so I'd like to modify and extend JeanT's answer in a way, that changing machine variables also can be performed even if the script itself isn't run elevated:
function Set-Path ([string]$newPath, [bool]$permanent=$false, [bool]$forMachine=$false )
{
$Env:Path += ";$newPath"
$scope = if ($forMachine) { 'Machine' } else { 'User' }
if ($permanent)
{
$command = "[Environment]::SetEnvironmentVariable('PATH', $env:Path, $scope)"
Start-Process -FilePath powershell.exe -ArgumentList "-noprofile -command $Command" -Verb runas
}
}
Most answers aren't addressing UAC. This covers UAC issues.
First install PowerShell Community Extensions: choco install pscx via http://chocolatey.org/ (you may have to restart your shell environment).
Then enable pscx
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser #allows scripts to run from the interwebs, such as pcsx
Then use Invoke-Elevated
Invoke-Elevated {Add-PathVariable $args[0] -Target Machine} -ArgumentList $MY_NEW_DIR
To be clear, the 1990's Windows way of click on Start, right click on This PC, and choose Properties, and then select Advanced system settings, and then in the dialog box that pops up, select Environment Variables, and in the list double clicking on PATH and then using the New, Edit, Move Up and Move Down all still work for changing the PATH. Power shell, and the rest of Windows get whatever you set here.
Yes you can use these new methods, but the old one still works. And at the base level all of the permanent change methods are controlled ways of editing your registry files.
Open PowerShell and run:
[Environment]::SetEnvironmentVariable("PATH", "$ENV:PATH;<path to exe>", "USER")
These scripts are idempotent (can be run more than once).
They update both the Windows path and the current / future Powershell sessions:
Permanently add path
$targetDir="c:\bin"
$oldPath = [System.Environment]::GetEnvironmentVariable("Path","Machine")
$oldPathArray=($oldPath) -split ';'
if(-Not($oldPathArray -Contains "$targetDir")) {
write-host "Adding $targetDir to Machine Path"
$newPath = "$oldPath;$targetDir" -replace ';+', ';'
[System.Environment]::SetEnvironmentVariable("Path",$newPath,"Machine")
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","User"),[System.Environment]::GetEnvironmentVariable("Path","Machine") -join ";"
}
write-host "Windows paths:"
($env:Path).Replace(';',"`n")
Permanently remove path
$targetDir="c:\bin"
$oldPath = [System.Environment]::GetEnvironmentVariable("Path","Machine")
$oldPathArray=($oldPath) -split ';'
if($oldPathArray -Contains "$targetDir") {
write-host "Removing $targetDir from Machine path"
$newPathArray = $oldPathArray | Where-Object { $_ –ne "$targetDir" }
$newPath = $newPathArray -join ";"
[System.Environment]::SetEnvironmentVariable("Path",$newPath,"Machine")
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","User"),[System.Environment]::GetEnvironmentVariable("Path","Machine") -join ";"
}
write-host "Windows paths:"
($env:Path).Replace(';',"`n")
The simplest solution that I found to add C:\vcpkg permanently to my PATH env variable without drawbacks was :
$current_PATH = [Environment]::GetEnvironmentVariable("PATH", "USER");[Environment]::SetEnvironmentVariable("PATH", "$current_PATH;C:\vcpkg;", "USER")
You can change "USER" to "MACHINE" to change system env vars (need an admin terminal and you may need to change Environment to System.Environment) or even to "PROCESS" to only change local PATH env var (not permanently changed). Respectively "USER"=1 "MACHINE"=2 and "PROCESS"=0 here are the documentation about those commands: GetEnvironmentVariable SetEnvironmentVariable
Two other answers that I found but have major drawbacks and I do not recommend to use them. Both use SETX as it is implemented by PowerShell to change permanently env variable. The down side of those commandes are that you will duplicate your system PATH into your locale PATH and you need PowerShell to use them:
setx PATH "$($Env:PATH);C:\vcpkg;"
Longer but allow an usage with other env variable:
$($Env:PATH).Split(';') | %{ $str += "$($_.Trim('"'));" }; %{ $str += "C:\vcpkg;" } ; setx PATH $str; %{ $str = "" }
I tried to optimise SBF's and Michael's code a bit to make it more compact.
I am relying on PowerShell's type coercion where it automatically converts strings to enum values, so I didn't define the lookup dictionary.
I also pulled out the block that adds the new path to the list based on a condition, so that work is done once and stored in a variable for re-use.
It is then applied permanently or just to the Session depending on the $PathContainer parameter.
We can put the block of code in a function or a ps1 file that we call directly from the command prompt. I went with DevEnvAddPath.ps1.
param(
[Parameter(Position=0,Mandatory=$true)][String]$PathChange,
[ValidateSet('Machine', 'User', 'Session')]
[Parameter(Position=1,Mandatory=$false)][String]$PathContainer='Session',
[Parameter(Position=2,Mandatory=$false)][Boolean]$PathPrepend=$false
)
[String]$ConstructedEnvPath = switch ($PathContainer) { "Session"{${env:Path};} default{[Environment]::GetEnvironmentVariable('Path', $containerType);} };
$PathPersisted = $ConstructedEnvPath -split ';';
if ($PathPersisted -notcontains $PathChange) {
$PathPersisted = $(switch ($PathPrepend) { $true{,$PathChange + $PathPersisted;} default{$PathPersisted + $PathChange;} }) | Where-Object { $_ };
$ConstructedEnvPath = $PathPersisted -join ";";
}
if ($PathContainer -ne 'Session')
{
# Save permanently to Machine, User
[Environment]::SetEnvironmentVariable("Path", $ConstructedEnvPath, $PathContainer);
}
# Update the current session
${env:Path} = $ConstructedEnvPath;
I do something similar for a DevEnvRemovePath.ps1.
param(
[Parameter(Position=0,Mandatory=$true)][String]$PathChange,
[ValidateSet('Machine', 'User', 'Session')]
[Parameter(Position=1,Mandatory=$false)][String]$PathContainer='Session'
)
[String]$ConstructedEnvPath = switch ($PathContainer) { "Session"{${env:Path};} default{[Environment]::GetEnvironmentVariable('Path', $containerType);} };
$PathPersisted = $ConstructedEnvPath -split ';';
if ($PathPersisted -contains $PathChange) {
$PathPersisted = $PathPersisted | Where-Object { $_ -ne $PathChange };
$ConstructedEnvPath = $PathPersisted -join ";";
}
if ($PathContainer -ne 'Session')
{
# Save permanently to Machine, User
[Environment]::SetEnvironmentVariable("Path", $ConstructedEnvPath, $PathContainer);
}
# Update the current session
${env:Path} = $ConstructedEnvPath;
So far, they seem to work.
Lots of examples of appending, or overwriting. Here is an example of prepending a path on powershell for Linux, Ubuntu 18.04 with pwsh 7.1.3
$ENV:PATH = "/home/linuxbrew/.linuxbrew/bin:$ENV:PATH"
I'm specifically adding the linuxbrew (homebrew for linux) bin directory to take precedence over the system installed. It helped solve an issue I was having and although this was the most helpful place, it also left me "experimenting".
Note that the : is Linux path separator, whereas on Windows (or at least my windows) you would use ; for powershell typically.
Editing the registry key in #ali Darabi's answer worked best for me, but
When I didn't have the right permissions to do it from Powershell. So I edited it directly in regedit.
I want to expand further on the subject in this answer.
Restarting Powershell also wasn't sufficient to propagate the change. I had to Open Task Manager and restart explorer.exe to trigger a reload of the registry.
It can be quite tedious to navigate the registry so in order to maintain an user friendly experience you can execute this from Powershell:
REG ADD "HKCU\Software\Microsoft\Windows\CurrentVersion\Applets\Regedit" /v "LastKey" /d "HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment" /f; regedit
It sets the last opened window to a certain registry path, so that when you open regedit the next time it opens at the proper key.
If you need to set variable name dynamically and for session only, then use:
New-Item env:\$key -Value $value -Force | Out-Null

Is it possible to create a user defined PowerShell environment?

I would like to clear a PowerShell session of mostly all alias definitions, except for common aliases like cd, sort, mkdir, ...
After I finished my session, I would like to restore all previous known the aliases.
There is no need to unload modules or to unregister CmdLets. I just want to clear the alias namespace for my session.
I could specify the allowed aliases in a list like this:
$AllowedAliases = #(
"cd", "mkdir", "rm", "rmdir",
"cd", "mkdir", "rm", "rmdir",
"where", "select",
"sort"
)
How can I save the aliases and restore them?
or
How can I start a clean PoSh and load only basic aliases?
What I have tested so far:
The following lines are from my example module called poc.psm1.
$Aliases = #()
function Register-PoC
{ foreach ($a in (Get-Item Alias:))
{ $script:Aliases += $a
Write-Host "$($a.Name) => $($a.ReferencedCommand) ($($a.Visibility))"
Remove-Item "Alias:$($a.Name)" -Force
}
}
function Unregister-PoC
{ foreach ($a in $script:Aliases)
{ Write-Host "$($a.Name) <= $($a.ReferencedCommand)"
if (Test-Path "Alias:$($a.Name)")
{ Write-Host "$($a.Name) exists." }
else
{ Set-Alias -Name $a.Name -Value $a.ReferencedCommand -Scope $a.Visibility }
}
if (Test-Path Alias:quit) { Remove-Item Alias:quit }
Remove-Module PoC
}
Export-ModuleMember -Function 'Register-PoC'
Export-ModuleMember -Function 'Unregister-PoC'
Register-PoC
Set-Alias -Name quit -Value Unregister-PoC -Description "Unload this module." -Scope Global
Usage example:
Import-Module .\poc.psm1
dir Alias:
quit
dir Alias:
Unfortunately, dir Alias: is not empty after invoking my script...
Another thing is, that I should preserve some settings of these aliases, because manual test showed, that dir does not behave like dir in before:
Remove-Item dir
Set-Alias dir Get-Item
dir
Cmdlet Get-Item an der Befehlspipelineposition 1
Geben Sie Werte für die folgenden Parameter an:
Path[0]:
So dir seams to append a default path to Get-Item if non is set to the alias.
Aliases are scoped. When you remove all aliases within a function, the aliases in the global scope aren't affected. Here is code that worked for me (simplifies your code a bit, although I didn't touch unregister-PoC, which could also be simplified I think):
function Register-PoC {
$script:aliases = get-item alias:
remove-item alias:* -force
}
function Unregister-PoC
{ foreach ($a in $script:Aliases)
{ Write-Host "$($a.Name) <= $($a.ReferencedCommand)"
if (Test-Path "Alias:$($a.Name)")
{ Write-Host "$($a.Name) exists." }
else
{ Set-Alias -Name $a.Name -Value $a.ReferencedCommand -Scope $a.Visibility }
}
if (Test-Path Alias:quit) { Remove-Item Alias:quit }
Remove-Module PoC
}
. Register-PoC
Set-Alias -Name quit -Value Unregister-PoC -Description "Unload this module." -Scope Global
Note the dot operator on Register-PoC. You will need to dot source quit to restore the aliases to global scope.
BTW, rather than the foreach loop in Unregister-PoC you could use copy-item.
For your situation, I would recommend using PowerShell profiles. These can be defined per user, per machine, and other situations. You can automatically run functions stored in the profile just by calling the function after it's defined in the profile.
For just your current user on the current machine, see Example 3 here.
New-Item -Path $PROFILE -ItemType File -Force
For other profile options, check out Understanding the Six PowerShell Profiles.
To ignore a profile, you can do that by directly running powershell.exe -NoProfile -NoExit but beware of nested sessions when doing that in another PowerShell session.
For a way to wipe all aliases except your desired list, you can export the aliases and re-import them after wiping all aliases. This can be added to the profile, if desired. Otherwise assign it to a function in the profile and call as needed. Change path if not using the profile folder(or wherever you like to keep things):
$allowedaliases = "cd","dir","rm","rmdir","where","select","sort"
Export-Alias -Path "$((Get-ChildItem $PROFILE).DirectoryName)\aliases.csv" -Name $allowedaliases
Remove-Item Alias:* -Force
Import-Alias -Path "$((Get-ChildItem $PROFILE).DirectoryName)\aliases.csv" -Force
Note: One of the original listed aliases(mkdir) is a function, not an alias, at least in Powershell v5.0. Also added dir into the list since that was mentioned by the OP.

How to update Windows PowerShell session environment variables from registry?

This could be a simple question, but I couldn't find a proper answer.
How do you update the environment variables from within a current Windows PowerShell session without closing the current one?
Up to now, when I modify for example the PATH environment variable from Control Panel > System, I have to close current session and open a new one, so that variables are refreshed, or issue a SetEnviromentVariable which is cumbersome.
I'm coming from the Linux world, so I'm looking for something like source command.
There's no need to go dumpster diving in the registry for this stuff:
foreach($level in "Machine","User") {
[Environment]::GetEnvironmentVariables($level)
}
If you want to make PATH variables work right, you'd have to treat them specially (that is, concatenate the (potentially) new stuff to what you already have, and not lose anything, because you can't tell for sure what you should remove).
foreach($level in "Machine","User") {
[Environment]::GetEnvironmentVariables($level).GetEnumerator() | % {
# For Path variables, append the new values, if they're not already in there
if($_.Name -match 'Path$') {
$_.Value = ($((Get-Content "Env:$($_.Name)") + ";$($_.Value)") -split ';' | Select -unique) -join ';'
}
$_
} | Set-Content -Path { "Env:$($_.Name)" }
}
Notice that Set-Content actually sets the variables in your process environment, the same as doing something like $env:Temp = Convert-Path ~\AppData\Local\Temp
The environment gets populated on process start-up which is why you'll need to restart the process.
You can update the environment by reading it from the registry. Maybe with a script like the following:
function Update-Environment {
$locations = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment',
'HKCU:\Environment'
$locations | ForEach-Object {
$k = Get-Item $_
$k.GetValueNames() | ForEach-Object {
$name = $_
$value = $k.GetValue($_)
if ($userLocation -and $name -ieq 'PATH') {
Env:\Path += ";$value"
} else {
Set-Item -Path Env:\$name -Value $value
}
}
$userLocation = $true
}
}
(untested)
This is an old question, but if you use chocolatey, it also installs the handy refreshenv command, which works fine for me.
If you want to update the Path for your current PowerShell session only, do this:
$env:Path += ";<new path>"
If you need to update the PATH env variable so that it is persistent across new PowerShell sessions use:
[Environment]::SetEnvironmentVariable("PATH", $env:Path + ";<new path>", 'User')
If you want to change the path for all users, then change 'User' to 'Machine'.
Thank you #Joey for your answer (I marked that correct :) ) Just to note that, given I want to refresh all variables according to the registry in that moment, I got rid of the if-else clause...
I double checked the registry variable location and are ok according to Where are environment variables stored in registry?
Snippet used is here just for completeness....
function Update-Environment {
$locations = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment',
'HKCU:\Environment'
$locations | ForEach-Object {
$k = Get-Item $_
$k.GetValueNames() | ForEach-Object {
$name = $_
$value = $k.GetValue($_)
Set-Item -Path Env:\$name -Value $value
}
}
}
Unfortunately the other answers are missing some important details. PSModulePath has some special handling in PowerShell that needs to be considered when updating environment variables. Also, variables may be added, modified, or removed. And you need to take path environment variables into consideration.
To make it worse, depending on what product/host you are in when you do this, that product may have some special environment variable handling of its own that should be considered, and aside from hoping that Path environment variables (those that contain multiple values, semi-colon delimited) actually contain "Path" as part of their name, you may miss out on something (unless of course this is how Windows handles those environment variables internally, which could be, I didn't dig that far down the rabbit hole).
Here is a script that I came up with that is about as close as I think you'll get to properly updating environment variables in your current PowerShell session without restarting it:
# Get all environment variables for the current Process, as well as System and User environment variable values
$processValues = [Environment]::GetEnvironmentVariables('Process')
$machineValues = [Environment]::GetEnvironmentVariables('Machine')
$userValues = [Environment]::GetEnvironmentVariables('User')
# Identify the entire list of environment variable names first
$envVarNames = ($machineValues.Keys + $userValues.Keys + 'PSModulePath') | Sort-Object | Select-Object -Unique
# Now process all of those keys, updating what exists and adding what is new
foreach ($envVarName in $envVarNames) {
if ($envVarName -eq 'PSModulePath') {
$pieces = #()
if ($PSVersionTable.PSVersion -ge [System.Version]'4.0') {
$pieces += Join-Path -Path ${env:ProgramFiles} -ChildPath 'WindowsPowerShell\Modules'
}
if (-not $userValues.ContainsKey($envVarName)) {
$pieces += Join-Path -Path ([Environment]::GetFolderPath('Documents')) -ChildPath 'WindowsPowerShell\Modules'
} else {
$pieces += $userValues[$envVarName] -split ';'
}
if ($machineValues.ContainsKey($envVarName)) {
$pieces += $machineValues[$envVarName] -split ';'
}
[Environment]::SetEnvironmentVariable($envVarName,($pieces -join ';'),'Process')
} elseif ($envVarName -match 'path') {
$pieces = #()
if ($userValues.ContainsKey($envVarName)) {
$pieces += $userValues[$envVarName] -split ';'
}
if ($machineValues.ContainsKey($envVarName)) {
$pieces += $machineValues[$envVarName] -split ';'
}
[Environment]::SetEnvironmentVariable($envVarName,($pieces -join ';'),'Process')
} elseif ($userValues.ContainsKey($envVarName)) {
[Environment]::SetEnvironmentVariable($envVarName,$userValue[$envVarName],'Process')
} elseif ($machineValues.ContainsKey($envVarName)) {
[Environment]::SetEnvironmentVariable($envVarName,$machineValue[$envVarName],'Process')
}
}
# Lastly remove the environment variables that no longer exist
foreach ($envVarName in $processValues.Keys | Where-Object {$envVarNames -notcontains $_}) {
Remove-Item -LiteralPath "env:${envVarName}"
}
Note that this is largely untested however the principle is sound and it is based on work I have done in the past in this area.