Setting Windows PowerShell environment variables

Viewed 964572

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?

22 Answers

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

Pain-free, one-line, example solutions

Try these three commands to practice setting and deleting environmental variables in PowerShell.

Notes on usage:

  1. Run these commands on elevated PowerShell (e.g. with administrator privileges).
  2. 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.

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 ';'
            }
        }
    }
}

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.

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")

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

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")
Related