44

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.

Mofi
  • 46,139
  • 17
  • 80
  • 143
Pablo Burgos
  • 1,194
  • 1
  • 10
  • 21
  • look here : http://stackoverflow.com/a/6985386/381149 – Loïc MICHEL Jan 17 '13 at 14:51
  • the powershell code related to the link privided by @Kayasax in my answer: http://stackoverflow.com/a/12669633/520612 – CB. Jan 17 '13 at 15:00
  • 1
    Christian and Kayasax: If they change the variable from the appropriate UI then the message gets sent anyway. Explorer updates its own variables (which is why all other processes started from Explorer inherit the change). – Joey Jan 17 '13 at 15:18

6 Answers6

39

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

Jaykul
  • 15,370
  • 8
  • 61
  • 70
  • 1
    This cut-and-paste worked in my script. Very nice. Good to run after your script installs Chocolatey or BoxStarter, so it can then call the tools. – Vimes Nov 07 '14 at 23:40
  • 2
    Note for stock Windows 7: The pipe to Set-Content didn't seem to work right on PowerShell v2.0. I had to do it long-hand, `| ForEach{ Set-Content -Path "Env:$($_.Name)" -Value $_.Value }` – Vimes Jan 20 '15 at 20:10
  • Ahh, how I love that .NET code in powershell, that first block is so nice. – Rakha Oct 18 '18 at 17:12
  • Very nice powershell script. I suggest you use an equal comparison with `$_.Name -eq 'Path'` or `$_.Name -match '^Path$'` because the script will fail if there are new environment variables that ending with `PATH` for example `ANDROID_PATH`. – Christos Lytras Feb 23 '21 at 01:59
15

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)

Joey
  • 344,408
  • 85
  • 689
  • 683
  • Great Answer. Only few changes i need to do. i am running powershell version 5.1 on windows 10 workstation. I needed to replace `Env:\Path += ";$value" ` to `$Env:Path += ";$value"` and `Set-Item -Path Env:\$name -Value $value` to `Set-Item -Path Env:$name -Value $value`. Amazing Answer. – SRJ Jun 22 '20 at 13:15
14

This is an old question, but if you use chocolatey, it also installs the handy refreshenv command, which works fine for me.

Eamon Nerbonne
  • 47,023
  • 20
  • 101
  • 166
13

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'.

Keith Hill
  • 194,368
  • 42
  • 353
  • 369
  • 5
    I understood the question more like »I changed an environment variable (from the GUI) but want the change to appear in an already-existing PowerShell session without having to restart it.« – Joey Jan 17 '13 at 17:12
5

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
        }
    }
}
Community
  • 1
  • 1
Pablo Burgos
  • 1,194
  • 1
  • 10
  • 21
5

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.

Kirk Munro
  • 1,190
  • 8
  • 9
  • You definitely can't just use `$EnvVarName -match 'path'` as how you detect PATH variables. There are built-ins like `HOMEPATH` (points to your profile dir) and other common ones like `GIT_LFS_PATH` and `ChocolateyPath` which point at one folder... – Jaykul Sep 12 '18 at 15:51
  • https://github.com/chocolatey/choco/blob/master/src/chocolatey.resources/helpers/functions/Update-SessionEnvironment.ps1 is the source code for `RefreshEnv` that might be helpful as well – visch May 10 '22 at 16:46