RefrEnv icon indicating copy to clipboard operation
RefrEnv copied to clipboard

[Feature Request] add RefrEnv_ResetPath to reset the path and RefrEnv_ResetEverything to reset everything

Open i486 opened this issue 1 year ago • 13 comments

I am having trouble refreshing my environment variable after using RefrEnv.bat.

I use the following PowerShell script to remove specific directories from my USER PATH environment variable:

$paths2delete = @(
    'C:\Users\PC\AppData\Local\Programs\Python\Python311;',
    'C:\Users\PC\AppData\Local\Programs\Python\Python311\Scripts;'
)

$systemPath = [Environment]::GetEnvironmentVariable('Path', 'User')

foreach ($path in $paths2delete) {
    $systemPath = $systemPath.replace($path, '')
}

$systemPath = $systemPath -split ';' | Where-Object { $_ -ne '' }
$systemPath = $systemPath -join ';'

[Environment]::SetEnvironmentVariable('Path', $systemPath, 'User')

Write-Host "Deleted from path!"
Write-Host $systemPath

Steps to Reproduce:

  1. Have these paths in your user path variable at the start C:\Users\PC\AppData\Local\Programs\Python\Python311; C:\Users\PC\AppData\Local\Programs\Python\Python311\Scripts;
  2. Open Command Prompt (Window 1) and leave it open.
  3. Open another Command Prompt (Window 2) and run the following command to execute the PowerShell script above:
    powershell -command "& '.\Remove-Python311.ps1'"
    
    or just mouse2 click on Remove-Python311.ps1 and select "Run with Powershell" to remove the two Python 3.11 paths from your user path variable.
  4. Manually check the USER PATH environment variable using the Windows Environment Variables manager to make sure they are removed correctly.
  5. Switch back to Command Prompt (Window 1) and run RefrEnv.bat.

Observed Behavior:

After running RefrEnv.bat in Window 1, the removed paths reappear at the end of the PATH variable, separated by an empty line, as shown below:

echo %path:;=&echo(%
C:\Program Files\ffmpeg\bin
C:\Program Files\ImageMagick
C:\Users\PC\AppData\Roaming\npm
C:\Users\PC\go\bin
C:\Users\PC\AppData\Local\Programs\Ollama

C:\Users\PC\AppData\Local\Programs\Python\Python311
C:\Users\PC\AppData\Local\Programs\Python\Python311\Scripts

Expected Behavior:

The paths C:\Users\PC\AppData\Local\Programs\Python\Python311 and C:\Users\PC\AppData\Local\Programs\Python\Python311\Scripts should be completely removed from the PATH variable.

Questions:

Am I doing something wrong, or is there an additional step needed to ensure that the PATH environment variable is updated correctly after using RefrEnv?

i486 avatar May 18 '24 18:05 i486

@i486 This is not how this works.

When you start a new shell the path is loaded from the registry, so unless you restart your shell, the change in the environment variables in that shell (or process) will remain the same. (Its stored in process memory and there is nothing you can do about that, for security reasons.)

eabase avatar May 19 '24 23:05 eabase

@eabase

Thank you for your response.

However, I did not make any changes within the current shell (or process). I used another tool to modify the system's user environment PATH variable.

Please refer to the "Steps to Reproduce" section for details.

I did not alter anything within the shell/process of Window 1.

i486 avatar May 20 '24 10:05 i486

@i486 As @eabase said, when you start Window1 and Window2, in both windows the path already contains the two directories you want to delete, and because RefrEnv does not reset the path but refresh the path you got that result.

in the README it states clearly that:

RefrEnv append the new path to the old path of the parent script which called RefrEnv. It is better than overwriting the old path, otherwise it will delete any newly added path by the parent script.

I think this is a safe/sane choice for the default use, you will not like your path to be reseted by RefrEnv, people in general want to add to the path what was added to the path by an external tool without reseting the actual path inside cmd.

The bash version of RefrEnv offers this variable to reset the path: RefrEnv_ResetPath=yes , I don't remember why I did not add the same for cmd and powershell, I will add it tonight if God wills

if you want to have this behavior (reset the path) as the default one, then change this line here from: NewPath = SystemPath & ";" & UserPath & ";" & VolatilePath & ";" & ProcessPath to: NewPath = SystemPath & ";" & UserPath & ";" & VolatilePath

badrelmers avatar May 20 '24 19:05 badrelmers

@badrelmers

I often want to "mirror" my already running shell/process environment to the system/user environment after modifying them with an external tool. I initially thought this might be a bug since Choco’s refreshenv tool successfully removed the paths. Now I understand that RefrEnv's default approach makes sense for most use cases to avoid losing any newly added paths.

Anyway, I have modified the script with your suggestion, and it now works exactly as I wanted. Thank you very much!

I have another question regarding variables. For example:

CUDA_PATH=C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8

This variable is added to my system variables by the Nvidia Cuda toolkit installer. I sometimes need to remove it if a project fails to build with this variable present. However, I also need to have this variable in my system environment because some other software uses it to determine the CUDA path.

I understand that tweaking variables within the current shell session or simply reopening the shell after modifying them with an external tool can solve these issues. But ideally, I’d like to just double-click Remove_CUDA_PATH.reg, then return to my session, run RefrEnv.bat, and continue.

I realize this might be an extreme use case, but would it be possible to update RefrEnv so it mirrors/resets everything, not just PATH? Perhaps adding a feature like RefrEnv_ResetEverything=no inside the script so people like me can switch it to yes?

Thank you again for your help and consideration.

i486 avatar May 20 '24 22:05 i486

@i486 i added RefrEnv_ResetPath environment variable, set it to "yes" before running RefrEnv to reset the path.

would it be possible to update RefrEnv so it mirrors/resets everything, not just PATH?

if i understood well , i think you want to reset all the vars like if you open a new cmd https://stackoverflow.com/questions/8261156/start-new-cmd-exe-and-not-inherit-environment

reset in your phrase means to remove any var if it does not exist in the registry. and Remove_CUDA_PATH.reg removes CUDA_PATH from the registry, correct me if i m worng.

this is a zone i never wanted to touch because it will be fragile because of the different environments in where cmd may be running: cmd my be running from inside cygwin/mintty, Cmder, ConEmu, ConsoleZ, Powershell/Pwsh ...etc , and those terminals adds there own variables, so reseting everything and setting only the registry variables will break something in those terminals for sure, reseting cmd does not seem a problem because we know from where to get the default environment vars he uses when he starts, but the other terminals auto create there vars when they start not from the registry, and supporting all those terminals is difficult if not impossible, and the big problem is what about the terminals which i do not even know about? so as you see this option will be very fragile.

but i will make a test and see how things go tomorrow if God wills, but even if i implement it i will keep it undocumented for the above reasons.

badrelmers avatar May 21 '24 04:05 badrelmers

Yeah, I totally agree with your points and I didn't mean resetting everything like opening a new cmd window. I mostly wanted an option for RefrEnv to prioritize the system environment.

Anyway, how about this scenario?

  1. I start by opening Cygwin and checking the environment variables with env. Here's what I see:
...............
CUDA_PATH=C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8 (from my system)
HOME=C:\cygwin64\home\PC (set by Cygwin session)
TERM=xterm (set by Cygwin session)
TERM_PROGRAM=mintty (set by Cygwin session)
...............
  1. Next, I type cmd in the Cygwin window, opening a Windows command prompt within the Cygwin session. Echoing %PATH% shows the same path above as expected.
...............
CUDA_PATH=C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8 (from my system)
HOME=C:\cygwin64\home\PC (set by Cygwin session)
TERM=xterm (set by Cygwin session)
TERM_PROGRAM=mintty (set by Cygwin session)
...............
  1. Within this cmd shell in the Cygwin session, I set a variable with set git=hub, resulting in:
...............
CUDA_PATH=C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8 (from my system)
HOME=C:\cygwin64\home\PC (set by Cygwin session)
TERM=xterm (set by Cygwin session)
TERM_PROGRAM`=mintty (set by Cygwin session)
git=hub (set by cmd shell within Cygwin)
...............
  1. Now, suppose I manually remove CUDA_PATH and add git=lmao via the Windows Environment Manager.

image

  1. After running RefrEnv and echoing %PATH%, here's the result I'm aiming for:

CUDA_PATH=C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8 - Removed by RefrEnv because it doesn't exist in system environment variables.

HOME=C:\cygwin64\home\PC (set by Cygwin session)
TERM=xterm (set by Cygwin session)
TERM_PROGRAM=mintty (set by Cygwin session)
git=lmao (it was "git=hub" previously, set by cmd shell but now overridden by RefrEnv)

Basically, this RefrEnv scenario mirrors everything from system variables to cmd but leaves all other variables, such as those set temporarily by the user or by a parent process like Cygwin, unchanged. However, if a variable key name clashes with one from the system environment, then the system environment takes priority. I believe this is much less risky, right?

i486 avatar May 21 '24 08:05 i486

@badrelmers

I'm still keen on exploring the RefrEnv_ResetEverything option you mentioned earlier! 😄

If it's possible to implement all these options, then I would have three different RefrEnv.bat files in my path:

  • RefrEnv_ResetPath.bat: This keeps the current functionality with RefrEnv_ResetPath=yes.
  • RefrEnv_ResetEnvironment.bat: This follows the scenario I described above, where system variables take priority but other variables set by the user or parent processes like Cygwin remain unchanged.
  • RefrEnv_ResetEverything.bat: This more radical option wipes everything out and reverts to system variables. It might be a bit daring, so like you said it could be an underground feature for advanced users who understand the risks. If you're keen on preserving parent terminal variables, I'm happy to put it to the test by diving into all the known Windows terminals and reporting back on the important variables.

While I see the potential in all these options, honestly, I'm quite content with just RefrEnv_ResetPath.bat for now. So, if these ideas end up being a hassle, don't sweat it.

Thanks for considering these suggestions! I really appreciate the effort.

i486 avatar May 21 '24 08:05 i486

Yes , I see, RefrEnv_ResetEnvironment could have been the best solution, but we arrived to RefrEnv_ResetEverything because RefrEnv_ResetEnvironment is impossible to implement, lets see why, you said:

Basically, this RefrEnv scenario mirrors everything from system variables to cmd but leaves all other variables, such as those set temporarily by the user or by a parent process like Cygwin, unchanged.

How can a program knows and differentiate between a variable set temporarily by the user and a variable set by another program that you no longer want to have? there is not way to know this, example: i want to remove CUDA_PATH but i want to keep git var, can you know which one I want to keep and which one I want to delete if I don't tell you? impossible. both variables does not exist in the registry so both have to be deleted (RefrEnv_ResetEverything) or both have to be kept (this is what RefrEnv does actually).

I think you agree with me that RefrEnv_ResetEverything is useless, so lets not implement it yet, I don't see any benefit of it and I think you are not interested in it too, if I'm wrong tell me.

The only safe solution I see for this problem is to implement a similar feature I was lazy to implement in the past, see this https://github.com/badrelmers/RefrEnv/issues/5 (it is a little bit similar). I mean to implement an option to specify which variables should be refreshed, because the user is the only person who knows which variables are important to him and which is not.
what do you think about having a variable lets call it RefrEnv_Var_Monitor (or other name, if you suggest a better name, my English is so bad :( ) , you can put there the variables you are interested in monitoring for example:

set "RefrEnv_Var_Monitor=CUDA_PATH;git;MyOtherVar"

now RefrEnv have to replicate/mirror those variables,if they exist in the registry then RefrEnv have to refresh them as he did always, but if they do not exist in registry then RefrEnv have to delete them. and the other variables set by the user or the terminal will not be removed if they are not set in the registry because we did not included them in RefrEnv_Var_Monitor . this gave the same result you want without the drawback we discussed above.

If you like this idea I'm more interested in implementing it than implementing RefrEnv_ResetEverything. If you have a better idea I'm all ears.

badrelmers avatar May 21 '24 13:05 badrelmers

Ok, TL;DR all. But to keep in mind that I never change System path temporarily.

Why?

  • Because the order of each path item matters, so any automation of making these changes, will most likely break the order, and subsequently what executable is used. (I.e. try mess with system path when you have multiple python versions installed!)

  • This is also why the VS developer shells are saving original path in another environment variable before making any changes.

eabase avatar May 22 '24 17:05 eabase

@eabase yes, good point. i made sure that the path reconstruction is similar to what windows does so you have to end up with the same order

badrelmers avatar May 22 '24 18:05 badrelmers

@badrelmers Not sure if I'm doing something wrong now, because this worked in the far past (before posh v7) and I haven't looked at this for a few years.

The issue seem to be that I was not able to run the Update-SessionEnvironment after having dot-sourced this file from within a function, in my own script (my.ps1), and and then using an alias.

I think this was related to the script scopes.
(See: Get-Help about_Scopes.)

# Update-SessionEnvironment
Update-SessionEnvironment: The term 'Update-SessionEnvironment' is not recognized as a name of a cmdlet, function, script file, or executable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

The solution was to prefix all the functions in your refrenv.ps1 file with global:, like this:

function global:Update-SessionEnvironment { ... }

Now I can source this file from within my own (my.ps1), with:

function global:loadRefreshEnv {
	if (Test-Path -Path $PROFILE.CurrentUserCurrentHost) {
		$e = "$PSScriptRoot\Update-SessionEnvironment.ps1"
		. $e
	} else {
		Write-Host "[WARN] `$PROFILE doesn't exist!"
	}
}
loadRefreshEnv
Set-alias refresh Update-SessionEnvironment

Perhaps you know this better, and have a better solution?

The only other way I know, would be to convert your script into a module (refrenv.psm1). This would import all the needed functions into the right local scope and thus available in the session.

PS. You may want to also update the version number in that file, as you made many changes now.


Apologies to @i486 for having partially hijacked/OT'd his thread. Thank you.

eabase avatar May 24 '24 14:05 eabase

@eabase If I understood well, it sounds like you encountered a scoping issue where the functions defined in the refrenv.ps1 file were not available in the global scope when you used an alias to call them.

Your solution of prefixing the functions with global: is fine. But using modules is generally better as they offer better encapsulation and scoping. I don't know any other method to do that, but I'm not a PowerShell professional either. For me I do it like this: I put refrenv.bat/sh/ps1 inside a folder called _bin in C:\_bin (with other useful terminal scripts/exe), and I put it in the PATH, now when I write refrenv in the terminal, the terminal shell will select the correct file, in cmd "bat" will be executed, in bash "sh" will be executed and in powershell "ps1" will be executed automatically. I found this easier to maintain and use.

badrelmers avatar May 24 '24 23:05 badrelmers

Yes , I see, RefrEnv_ResetEnvironment could have been the best solution, but we arrived to RefrEnv_ResetEverything because RefrEnv_ResetEnvironment is impossible to implement, lets see why, you said:

Basically, this RefrEnv scenario mirrors everything from system variables to cmd but leaves all other variables, such as those set temporarily by the user or by a parent process like Cygwin, unchanged.

How can a program knows and differentiate between a variable set temporarily by the user and a variable set by another program that you no longer want to have? there is not way to know this, example: i want to remove CUDA_PATH but i want to keep git var, can you know which one I want to keep and which one I want to delete if I don't tell you? impossible. both variables does not exist in the registry so both have to be deleted (RefrEnv_ResetEverything) or both have to be kept (this is what RefrEnv does actually).

I think you agree with me that RefrEnv_ResetEverything is useless, so lets not implement it yet, I don't see any benefit of it and I think you are not interested in it too, if I'm wrong tell me.

The only safe solution I see for this problem is to implement a similar feature I was lazy to implement in the past, see this #5 (it is a little bit similar). I mean to implement an option to specify which variables should be refreshed, because the user is the only person who knows which variables are important to him and which is not. what do you think about having a variable lets call it RefrEnv_Var_Monitor (or other name, if you suggest a better name, my English is so bad :( ) , you can put there the variables you are interested in monitoring for example:

set "RefrEnv_Var_Monitor=CUDA_PATH;git;MyOtherVar"

now RefrEnv have to replicate/mirror those variables,if they exist in the registry then RefrEnv have to refresh them as he did always, but if they do not exist in registry then RefrEnv have to delete them. and the other variables set by the user or the terminal will not be removed if they are not set in the registry because we did not included them in RefrEnv_Var_Monitor . this gave the same result you want without the drawback we discussed above.

If you like this idea I'm more interested in implementing it than implementing RefrEnv_ResetEverything. If you have a better idea I'm all ears.

Yeah, I now understand why RefrEnv_ResetEnvironment is impossible. Without registry entries, RefrEnv can't programmatically determine which temporary environment variables should be kept or deleted, unless specified through something like the RefrEnv_Var_Monitor concept you mentioned. I guess it would have been possible if the Windows command prompt could detect changes in the session environment and automatically save them somewhere in the registry. But that's just wishful thinking.

Regarding RefrEnv_ResetEverything, I find it more beneficial (personally) compared to RefrEnv_Var_Monitor. For me, the primary purpose of using this script is to reset the session environment back to my system's baseline. It could be helpful during troubleshooting issues, allowing me to start with a clean slate devoid of any erroneous or residual variables from previous sessions. Moreover, it offers the convenience of achieving this reset without the need to close and reopen the command prompt, all while retaining command history—another significant benefit. Admittedly, I've been essentially replicating RefrEnv_ResetEverything for a long time by frequently restarting cmd sessions. So, it's safe to say that RefrEnv_ResetEverything will definitely be a real time-saver for me.

i486 avatar May 26 '24 19:05 i486