Use PowerShell to Modify Your Environmental Path

Use PowerShell to Modify Your Environmental Path

  • Comments 11
  • Likes

Summary: Learn how to use Windows PowerShell to work with your environmental path variable.

 

Weekend Scripter: Use Windows PowerShell to Modify Your Path


Microsoft Scripting Guy Ed Wilson here. Welcome back to the weekend and Guest Blogger Sean Kearney. Read more about Sean and his previous guest blog posts. Now without further ado (or RDO or CDO), here is Sean.

 

Back in the good old days, we had a command called path in MS-DOS. It was used and abused heavily because that’s how you had to make things work. Either dump the application into a common folder or take its folder and do something like this:

PATH %PATH%;C:\THIS\;

This would allow you to now just run the application that was located in the this folder by just typing its name rather than adding an explicit path every time.

This is still available to us of course, but it is isolated to the current session and is only temporary. However, in the Land of Windows PowerShell, there is still a need to modify PATH. You may have a common script folder or an added console application folder (such as C:\Sysinternals)

For whatever reason, if you want to modify the path, accessing the data in the PATH variable is easy, but making a modification can be confusing. Windows PowerShell provides natively a one-way path to read the system environment variables using $ENV

$ENV:PATH

But try as you might, there is no built-in Set-Path cmdlet or even an Append-Path (of course Append is not an approved verb) or Find-Path-to-the-Yellow-Brick-Road.

Of course, I’m not sure that last one would have been useful for anybody other than Dorothy or Toto.

But there is no obvious way to do it. Even running CMD.EXE as an administrator only makes the change temporary. So how do we gain this capability?

We leverage the registry with Windows Powershell. The environment variables are all stored under:

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment

Therefore, it just becomes a matter of editing the key. Now keep in mind this is a permanent and global change. You will need to be running Windows PowerShell as an administrator.

First, to read the key with the content, you need to use the following command:

Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH

The previous command will dump four separate properties on the screen—the PSPath, PSParentPath, PSChildname, PSProvider—and the actual ItemProperty path. We just want the path so that we can modify the previous command to look like the following:

(Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH).Path

Now if we would like to add to that property, all we need to do is concatenate to the current value and set it back in the registry:

$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

Now you’ll have to restart Windows PowerShell to see the change, but now all applications (including Windows) will have access to the updated path. But wouldn’t it make more sense to make these into cmdlets or advanced functions? I would think so. So we can just use $ENV:PATH to read it, so no sense rebuilding the wheel. I just need to easily add to the path. But we can get fancier than the old path command from DOS. We can add in some error checking, like validating if it’s already in the path or if the folder even exists.

Function global:ADD-PATH()
{
[Cmdletbinding()]
param
(
[parameter(Mandatory=$True,
ValueFromPipeline=$True,
Position=0)]
[String[]]$AddedFolder
)

# Get the current search path from the environment keys in the registry.

$OldPath=(Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH).Path

# See if a new folder has been supplied.

IF (!$AddedFolder)
{ Return ‘No Folder Supplied. $ENV:PATH Unchanged’}

# See if the new folder exists on the file system.

IF (!(TEST-PATH $AddedFolder))
{ Return ‘Folder Does not Exist, Cannot be added to $ENV:PATH’ }

# See if the new Folder is already in the path.

IF ($ENV:PATH | Select-String -SimpleMatch $AddedFolder)
{ Return ‘Folder already within $ENV:PATH' }

# Set the New Path

$NewPath=$OldPath+’;’+$AddedFolder

Set-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH –Value $newPath

# Show our results back to the world

Return $NewPath
}

There you go: a nice new feature to add to your Windows PowerShell arsenal. If you want to get fancy and have a Get-Path, an alias should have done nicely for that, but it seems to hit the 260-character limit for some reason in the path. Therefore, we will cheat with this one-line function add-on:

FUNCTION GLOBAL:GET-PATH() { Return $ENV:PATH }

We can get even fancier now and have the ability to remove items from the path with a little magic from the –replace operator

Function global:REMOVE-PATH()
{
[Cmdletbinding()]
param
(
[parameter(Mandatory=$True,
ValueFromPipeline=$True,
Position=0)]
[String[]]$RemovedFolder
)

# Get the Current Search Path from the environment keys in the registry

$NewPath=(Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH).Path

# Find the value to remove, replace it with $NULL. If it’s not found, nothing will change.

$NewPath=$NewPath –replace $RemovedFolder,$NULL

# Update the Environment Path

Set-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH –Value $newPath

# Show what we just did

Return $NewPath

}

Remember you will have to reload Windows PowerShell to get the new changes, and these changes are permanent. So be careful! To simplify getting the code, I put the functions into a module, and uploaded it to the Scripting Center Script Repository.

 

Thank you, Sean, for providing an great article and module. Please join us tomorrow for more Windows PowerShell goodies from Sean.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

 

 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Sometimes we may just need to temporarily add to the path. This can be done easily:

    $env:path+='c:\this'

    We can use string editing methods to remove an item:

    $env:path=$env:path.Replace('c:\this','')

    We could eventokenize the path and sort its order or insert into the beginning.

    The path is changed for the current PowerShell session only.

  • Great posts Sean!

    As another variant:

    [Environment]::SetEnvironmentVariable("Path",$Env:Path + ";C:\SysinternalsSuiteset", "Machine")

    To jrv:

    $env:path+=';c:\this'

  • Kuzun - good catch.

    For everyone else this too: (Process only)

    [Environment]::SetEnvironmentVariable("HOTSOUP","Vichyssoise")

    Sean - you could add switches to your path library to cause it to update the user and machine settings.

  • @jrv and @kazun

    Whoa!  More great ideas ! Love it!  Actually the one thing I should add (since it can CHANGE something is our good friend -whatif )

    Sean

  • There's another little bonus here you may not have caught.    It's also pretty easy with a Split() and Sort-Object to find duplicate references to paths and null points.  (Believe it or not, if you install enough stuff, you get them.)

    Try doing a (GET-PATH).split(";") | SORT-OBJECT and you'll probably see what I mean.  I actually had THREE references to the Powershell folder created by different applications and 4 path entries (Two semicolons side by side)

    I'm not certain it made anything faster but I felt better with a clean path :)

  • Nice to have you back here, Sean!

    The good old path variable is really something that could be well wrapped into some Powershell functions!

    @jrv, katzun: Definitely right! We can use the .net framework "Environment" class to make permanent changes and we can use PS build in ability to manage temporary environment variables!

    I personally need only temporary changes to the path variable during the run of scripts, mostly to add some special versions of java to the beginning of the path!

    Long ago :-) ... I really have changed the global path variable frequently.

    Nowadays I do, what Sean mentioned before, change it after some installations that ruined the path or duplicated the entries manually.

    Klaus.

  • If only there was a <rimshot /> tag to put after "without further ado (or RDO or CDO)".  Love it!

  • If you leave a backslash as the last character, like in the text above

    "$newPath=$oldPath+’;C:\NewFolderToAddToTheList\’",

    the path will brake if you add something after that e.q. ";c:\myfolder" .

  • If decide to use the .NET method to change the environment variables....... Will this also change the registry or this is only a Powrshell session change that it will revoke after the session is destroy.

    This is the methonds

    [Environment]::SetEnvironmentVariable("TestVariable",$null,"Machine")

  • I found the code in this article did not work.

    The folder was getting added to the registry key however it was not being shown in the path environment variable.

    This could be seen by examining the PATH at the command prompt echo %PATH% or in Powershell $env:Path.

    Using the [System.Environment]::SetEnvironmentVariable method works fine.


    Modified function

    Function Add-FolderToPath()
    {
    [CmdletBinding(PositionalBinding=$false)]
    param(
    [parameter(Mandatory=$True,ValueFromPipeline=$True)] [ValidateScript({Test-Path -Path $_ -PathType Container})] [String] $Folder
    )

    Write-Verbose "Adding folder ""$Folder"" to SYSTEM path"

    # Get the current search path from the environment keys in the registry.
    $OldPath = [System.Environment]::GetEnvironmentVariable("path")

    # See if the new Folder is already in the path.
    IF ($OldPath | Select-String -SimpleMatch $Folder)
    {
    Write-Warning "Folder ""$Folder"" is already in the path"
    }
    else
    {
    # Set the New Path
    $NewPath = $OldPath+ ’;’ + $Folder

    [System.Environment]::SetEnvironmentVariable("path",$NewPath)

    # Show our results back to the world
    Return $NewPath
    }




  • Please note there is a bug in the code I posted before - this function only changes the path for the current process. You need to specify the machine as a target as per this code:



    Function Add-FolderToPath()
    {
    [CmdletBinding(PositionalBinding=$false)]
    param(
    [parameter(Mandatory=$True,ValueFromPipeline=$True)] [ValidateScript({Test-Path -Path $_ -PathType Container})] [String] $Folder
    )

    Write-Verbose "Adding folder ""$Folder"" to SYSTEM path"

    # Get the current search path from the environment keys in the registry.
    $OldPath = [System.Environment]::GetEnvironmentVariable("path")

    # See if the new Folder is already in the path.
    IF ($OldPath | Select-String -SimpleMatch $Folder)
    {
    Write-Warning "Folder ""$Folder"" is already in the path"
    }
    else
    {
    # Set the New Path
    $NewPath = $OldPath+ ’;’ + $Folder

    [System.Environment]::SetEnvironmentVariable("path",$NewPath,'Machine')

    # Show our results back to the world
    Return $NewPath
    }