Feature Request: Terminal should listen for the WM_SETTINGCHANGE for environment variable updates #1495

Closed
opened 2026-01-30 22:28:42 +00:00 by claunia · 43 comments
Owner

Originally created by @rkeithhill on GitHub (Jun 3, 2019).

Summary of the new feature/enhancement

When I install an app that updates the path or I update the path manually, it isn't enough to kill an individual tab in the Terminal and start a new one. The new tab still inherits the old, unchanged environment variable values. This means I have to kill/restart the Terminal and lose all my tabs.

Proposed technical implementation details (optional)

Add a Windows message handler for WM_SETTINGCHANGE and update the Terminal process's environment block so that any new tabs get the updated environment variables.

Originally created by @rkeithhill on GitHub (Jun 3, 2019). # Summary of the new feature/enhancement When I install an app that updates the path or I update the path manually, it isn't enough to kill an individual tab in the Terminal and start a new one. The new tab still inherits the old, unchanged environment variable values. This means I have to kill/restart the Terminal and lose all my tabs. # Proposed technical implementation details (optional) Add a Windows message handler for `WM_SETTINGCHANGE` and update the Terminal process's environment block so that any new tabs get the updated environment variables.
Author
Owner

@ysc3839 commented on GitHub (Jun 5, 2019):

Related discussion in ConEmu. https://github.com/Maximus5/ConEmu/issues/468

@ysc3839 commented on GitHub (Jun 5, 2019): Related discussion in ConEmu. https://github.com/Maximus5/ConEmu/issues/468
Author
Owner

@EdinaLewis commented on GitHub (Jun 22, 2019):

Opening a new tab should definitely inherit from the current system environment.

@EdinaLewis commented on GitHub (Jun 22, 2019): Opening a new tab should definitely inherit from the current system environment.
Author
Owner

@zadjii-msft commented on GitHub (Jan 9, 2020):

<This was a kind of unfocused train of thought, though I'm happy with the conclusion at the bottom. leaving my own notes so I can remember whenever I get back to this>

Huh, this might be a bit trickier than I thought. WM_SETTINGCHANGE doesn't tell you which variable changed, only that the "Environment" changed. If we call GetEnvironmentStrings, we're still going to just get our launch environment variables.

We could call CreateEnvironmentBlock with nullptr as the hToken to get a fresh system environment block, but that's probably wrong, since we want the user's environment block. That involves also calling LogonUser. That's presumably doable.

Though, if the user launched the Terminal from one process with some extra variables set, and expected it to inherit variables from the parent process, then we probably shouldn't blow away the env vars for the first tab that's created. I suppose we could only do this refreshing of the environment block when we do get a WM_SETTINGCHANGE. Subsequent tabs would all use the user's default env block, not the one inherited from the parent. That's a little funky - it sorta creates scenarios that aren't totally expected, because sometimes the parent's values would persist to subsequent tabs.

I guess the most unified solution would be to just use a fresh environment block for all connections created after startup. That would at least be consistent behavior. We'd probably want to wait for #4023 to merge first, and set some internal flag to use a fresh env block, only after all the startup actions are processed. Then we wouldn't even need to use WM_SETTINGCHANGE, we'd just assume that you wanted a fresh one.

@zadjii-msft commented on GitHub (Jan 9, 2020): &lt;This was a kind of unfocused train of thought, though I'm happy with the conclusion at the bottom. leaving my own notes so I can remember whenever I get back to this> Huh, this might be a bit trickier than I thought. `WM_SETTINGCHANGE` doesn't tell you which variable changed, only that the `"Environment"` changed. If we call `GetEnvironmentStrings`, we're still going to just get our launch environment variables. We could call `CreateEnvironmentBlock` with `nullptr` as the `hToken` to get a fresh system environment block, but that's probably wrong, since we want the user's environment block. That involves also calling `LogonUser`. That's presumably doable. Though, if the user launched the Terminal from one process with some extra variables set, and _expected_ it to inherit variables from the parent process, then we probably shouldn't blow away the env vars for the first tab that's created. I suppose we could only do this refreshing of the environment block when we do get a `WM_SETTINGCHANGE`. Subsequent tabs would all use the user's default env block, not the one inherited from the parent. That's a little funky - it sorta creates scenarios that aren't totally expected, because _sometimes_ the parent's values would persist to subsequent tabs. I guess the most unified solution would be to just use a fresh environment block for all connections created after startup. That would at least be consistent behavior. We'd probably want to wait for #4023 to merge first, and set some internal flag to use a fresh env block, only after all the startup actions are processed. Then we wouldn't even need to use `WM_SETTINGCHANGE`, we'd just assume that you wanted a fresh one.
Author
Owner

@yitzhaks commented on GitHub (Feb 13, 2020):

Opening a new tab should definitely inherit from the current system environment.

nit: I would suggest the current user environment. explorer.exe does this correctly when launching new processes, so I hope it's not too complicated.

@yitzhaks commented on GitHub (Feb 13, 2020): > Opening a new tab should definitely inherit from the current system environment. nit: I would suggest the current **user** environment. explorer.exe does this correctly when launching new processes, so I hope it's not too complicated.
Author
Owner

@DHowett-MSFT commented on GitHub (Feb 28, 2020):

Make sure when we reload this, we also reload settings. That'll probably fix the keyboard issue in 4735.

@DHowett-MSFT commented on GitHub (Feb 28, 2020): Make sure when we reload this, we also reload settings. That'll probably fix the keyboard issue in 4735.
Author
Owner

@KalleOlaviNiemitalo commented on GitHub (Jun 10, 2020):

We could call CreateEnvironmentBlock with nullptr as the hToken to get a fresh system environment block, but that's probably wrong, since we want the user's environment block. That involves also calling LogonUser.

CreateEnvironmentBlock(&lpEnvironment, GetCurrentProcessToken(), FALSE) seems to work OK without LogonUser, although the documentation of CreateEnvironmentBlock does not quite allow this use.

@KalleOlaviNiemitalo commented on GitHub (Jun 10, 2020): > We could call `CreateEnvironmentBlock` with `nullptr` as the `hToken` to get a fresh system environment block, but that's probably wrong, since we want the user's environment block. That involves also calling `LogonUser`. `CreateEnvironmentBlock(&lpEnvironment, GetCurrentProcessToken(), FALSE)` seems to work OK without `LogonUser`, although the documentation of `CreateEnvironmentBlock` does not quite allow this use.
Author
Owner

@TBBle commented on GitHub (Jun 24, 2020):

If this one is solved by loading a new environment block on each new tab creation, then we'll still need separately to handle WM_SETTINGCHANGE to rectify the currently-closed-as-duplicate-of-this #4735.

It's not something I'm volunteering to implement myself, but if at startup the CreateEnvironmentBlock state was captured but not applied, then on WM_SETTINGCHANGE for Environment, we could CreateEnvironmentBlock again, and apply a 3-way diff (resolving conflicts in favour of the existing value: those're user-overrides) to the environment used for new tabs. That way existing user changes and overrides could be maintained, as we'd only change things that had that value in the default env block. Potentially, certain env-vars could be handled as lists, e.g., that'd make PATH do nice things if you append to the end of it as an override, and then install something that adds itself to the PATH. I assume there's some way an env-var works as a list, based on the Environment settings letting you treat some env-vars as a list.

Unless of course the user has overridden a value to be the same as the default env-block, and expects that override to survive if the Environment settings are changed. Then you get a less-nice outcome.

Conversely, this might have the nice effect that if the user had overridden a value, then changes the Environment to match it, and then changes the Environment again, their override is gone, and the second Environment value wins. Probably what they wanted, since they changed it after the override.

I am 100% open to not being informed that I have not used the word "nice" correctly here. ^_^

@TBBle commented on GitHub (Jun 24, 2020): If this one is solved by loading a new environment block on each new tab creation, then we'll still need separately to handle `WM_SETTINGCHANGE` to rectify the currently-closed-as-duplicate-of-this #4735. It's not something I'm volunteering to implement myself, but if at startup the `CreateEnvironmentBlock` state was captured but not applied, then on `WM_SETTINGCHANGE` for Environment, we could `CreateEnvironmentBlock` again, and apply a 3-way diff (resolving conflicts in favour of the existing value: those're user-overrides) to the environment used for new tabs. That way existing user changes and overrides could be maintained, as we'd only change things that had that value in the default env block. Potentially, certain env-vars could be handled as lists, e.g., that'd make `PATH` do nice things if you append to the end of it as an override, and _then_ install something that adds itself to the `PATH`. I assume there's some way an env-var works as a list, based on the Environment settings letting you treat some env-vars as a list. Unless of course the user has overridden a value to be the _same_ as the default env-block, and expects that override to survive if the Environment settings are changed. Then you get a less-nice outcome. Conversely, this might have the nice effect that if the user had overridden a value, then changes the Environment to match it, and _then_ changes the Environment again, their override is gone, and the second Environment value wins. Probably what they wanted, since they changed it after the override. I am 100% open to not being informed that I have not used the word "nice" correctly here. ^_^
Author
Owner

@KalleOlaviNiemitalo commented on GitHub (Jun 24, 2020):

Perhaps wt could "just use a fresh environment block for all connections created after startup" as previously suggested, but let the command line or settings.json list some environment variables that need to be copied from the environment of the wt process instead. Deliberately running wt with environment variables that differ from the user's defaults is a niche scenario. I don't think a three-way diff should be implemented.

@KalleOlaviNiemitalo commented on GitHub (Jun 24, 2020): Perhaps `wt` could "just use a fresh environment block for all connections created after startup" as previously suggested, but let the command line or `settings.json` list some environment variables that need to be copied from the environment of the `wt` process instead. Deliberately running `wt` with environment variables that differ from the user's defaults is a niche scenario. I don't think a three-way diff should be implemented.
Author
Owner

@DHowett commented on GitHub (Aug 12, 2020):

The environment variables bit of this was addressed in #7243; I'm leaving this issue open for the other environment settings.

@DHowett commented on GitHub (Aug 12, 2020): The environment variables bit of this was addressed in #7243; I'm leaving this issue open for the other environment settings.
Author
Owner

@TBBle commented on GitHub (Nov 30, 2020):

The environment variables bit of this was addressed in #7243; I'm leaving this issue open for the other environment settings.

I think it makes more sense to have a new ticket for WM_SETTINGCHANGE handling, the vast majority of tickets duplicated to this, and discussion on this ticket, were about env-vars specifically. They were mostly $ENV:PATH in fact.

I think it's only #1230, and #4735 that were interested in other uses of WM_SETTINGCHANGE. And #6491, but that's a super-set of #1230.

@TBBle commented on GitHub (Nov 30, 2020): > The environment variables bit of this was addressed in #7243; I'm leaving this issue open for the other environment settings. I think it makes more sense to have a new ticket for `WM_SETTINGCHANGE` handling, the vast majority of tickets duplicated to this, and discussion on this ticket, were about env-vars specifically. They were mostly `$ENV:PATH` in fact. I think it's only #1230, and #4735 that were interested in other uses of `WM_SETTINGCHANGE`. And #6491, but that's a super-set of #1230.
Author
Owner

@iSazonov commented on GitHub (Nov 27, 2021):

Add a Windows message handler for WM_SETTINGCHANGE and update the Terminal process's environment block so that any new tabs get the updated environment variables.

Windows API allows to create new process with custom env block. So there is no need to handle events. PowerShell does this in some scenarios.

@iSazonov commented on GitHub (Nov 27, 2021): > Add a Windows message handler for `WM_SETTINGCHANGE` and update the Terminal process's environment block so that any new tabs get the updated environment variables. Windows API allows to [create new process](https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw) with custom env block. So there is no need to handle events. PowerShell does this in some scenarios.
Author
Owner

@TBBle commented on GitHub (Nov 30, 2021):

That's the approach used in #7243, but it caused problems and had to be reverted; see #7418 and e.g., 616a71dd23

@TBBle commented on GitHub (Nov 30, 2021): That's the approach used in #7243, but it caused problems and had to be reverted; see #7418 and e.g., 616a71dd23e8fff717919c20c382edeaf59f433a
Author
Owner

@iSazonov commented on GitHub (Dec 1, 2021):

That's the approach used in #7243, but it caused problems and had to be reverted; see #7418 and e.g., 616a71d

@TBBle Thanks for pointing #7418. I leave a comment there. I guess there was a wrong implementation :-(

@iSazonov commented on GitHub (Dec 1, 2021): > That's the approach used in #7243, but it caused problems and had to be reverted; see #7418 and e.g., [616a71d](https://github.com/microsoft/terminal/commit/616a71dd23e8fff717919c20c382edeaf59f433a) @TBBle Thanks for pointing #7418. I leave a comment there. I guess there was a wrong implementation :-(
Author
Owner

@eryksun commented on GitHub (Dec 16, 2021):

Windows API allows to create new process with custom env block. So there is no need to handle events. PowerShell does this in some scenarios.

The environment should only be reloaded when some process in the session requests a reload by broadcasting WM_SETTINGCHANGE for an "Environment" update, which is when Explorer reloads its environment. It should not be reloaded for every new tab. It should change in lock step with Explorer.

The major problem is getting access to a public API that updates the environment using the same sequence as the shell API's private RegenerateUserEnvironment() function. There's a need for a public version of RegenerateUserEnvironment(), or CreateEnvironmentBlock2().

@eryksun commented on GitHub (Dec 16, 2021): > Windows API allows to [create new process](https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw) with custom env block. So there is no need to handle events. PowerShell does this in some scenarios. The environment should only be reloaded when some process in the session requests a reload by broadcasting `WM_SETTINGCHANGE` for an "Environment" update, which is when Explorer reloads its environment. It should not be reloaded for every new tab. It should change in lock step with Explorer. The major problem is getting access to a public API that updates the environment using the same sequence as the shell API's private `RegenerateUserEnvironment()` function. There's a need for a public version of `RegenerateUserEnvironment()`, or `CreateEnvironmentBlock2()`.
Author
Owner

@DHowett commented on GitHub (Dec 16, 2021):

For those of you who can follow along, I've filed MSFT-37424054 to track documenting and making available RegenerateUserEnvironment. Right now it's an unanswered ask on the team that owns shell32. 😄

@DHowett commented on GitHub (Dec 16, 2021): For those of you who can follow along, I've filed MSFT-37424054 to track documenting and making available `RegenerateUserEnvironment`. Right now it's an unanswered ask on the team that owns shell32. :smile:
Author
Owner

@alexchandel commented on GitHub (Dec 17, 2021):

Please, just check for new environment variables when a new tab is created.

@alexchandel commented on GitHub (Dec 17, 2021): Please, just check for new environment variables when a new tab is created.
Author
Owner

@eryksun commented on GitHub (Dec 18, 2021):

Please, just check for new environment variables when a new tab is created.

I thought the point was to behave like running a console application from Explorer, which inherits its environment from Explorer.

If a user or application modifies environment variables in the registry without broadcasting WM_SETTINGCHANGE "Environment", then it's not meant for the current session. Otherwise, for the past 25 years or so, the shell API would also have called RegenerateUserEnvironment() when spawning a new process.

The common ways for a user to update the environment in the registry all broadcast WM_SETTINGCHANGE, including the GUI environment variable editor, PowerShell [System.Environment]::SetEnvironmentVariable(), and setx.exe.

@eryksun commented on GitHub (Dec 18, 2021): > Please, just check for new environment variables when a new tab is created. I thought the point was to behave like running a console application from Explorer, which inherits its environment from Explorer. If a user or application modifies environment variables in the registry without broadcasting `WM_SETTINGCHANGE` "Environment", then it's not meant for the current session. Otherwise, for the past 25 years or so, the shell API would also have called `RegenerateUserEnvironment()` when spawning a new process. The common ways for a user to update the environment in the registry all broadcast `WM_SETTINGCHANGE`, including the GUI environment variable editor, PowerShell `[System.Environment]::SetEnvironmentVariable()`, and setx.exe.
Author
Owner

@DHowett commented on GitHub (Jan 4, 2022):

@ErykSun Pending us getting RegenerateUserEnvironment documented/publicly available, do you have any thoughts on whether Terminal should try to maintain environment variables it inherited that were different from the user environment?

There have been a couple reports that WT Preview no longer supports e.g. FOO_BAR=1 wt cmd ... echo %FOO_BAR%.

It seems like we'd be playing with fire, trying to reconstitute what the user wants in that case. I'm failing to think of a better thing for us to do, unless we just want to say "plz don't do that, just accept that you can't inherit environment variables."

After all, inheritance becomes ~ ~ strange ~ ~ when one Terminal window can act as the destination for any number of wt commands from any number of sources.

Maybe that's my answer.

@DHowett commented on GitHub (Jan 4, 2022): @ErykSun Pending us getting `RegenerateUserEnvironment` documented/publicly available, do you have any thoughts on whether Terminal should try to maintain environment variables it inherited that were _different_ from the user environment? There have been a couple reports that WT Preview no longer supports e.g. `FOO_BAR=1 wt cmd` ... `echo %FOO_BAR%`. It seems like we'd be playing with fire, trying to reconstitute what the user wants in that case. I'm failing to think of a better thing for us to do, unless we just want to say "plz don't do that, just accept that you can't inherit environment variables." After all, inheritance becomes ~ ~ strange ~ ~ when one Terminal window can act as the destination for any number of `wt` commands from any number of sources. Maybe that's my answer.
Author
Owner

@DHowett commented on GitHub (Jan 4, 2022):

@miniksa is looking at some of our options here and will report back.

@DHowett commented on GitHub (Jan 4, 2022): @miniksa is looking at some of our options here and will report back.
Author
Owner

@zadjii-msft commented on GitHub (Jan 4, 2022):

Just so we have this all in one place

@zadjii-msft commented on GitHub (Jan 4, 2022): Just so we have this all in one place * original issue: #1125 * Original PR: #7243 * PowerShell Issue that made it apparent that `CreateEnvironmentBlock` was insufficient: #7418 * Internal tracking `RegenerateUserEnvironment`: MSFT:37424054 * Implement env vars in settings (like in VScode) (original request: #2785, PR: #9287) * Also, this thread: https://github.com/microsoft/terminal/issues/9741#issuecomment-816758495 * Slightly different, but still related: #11777
Author
Owner

@eryksun commented on GitHub (Jan 20, 2022):

@zadjii-msft, note my comment in https://github.com/microsoft/terminal/issues/12157#issuecomment-1012606082. When Terminal is run from the start menu or "Open in Windows Terminal", a system service does the work of spawning the process. The service creates a new environment block for the user via CreateEnvironmentBlock(). So Terminal's initial environment may not be expanded the same as Explorer's. I think it should immediately call RegenerateUserEnvironment(), or something equivalent to that. Don't wait for a WM_SETTINGCHANGE message.

When using just CreateEnvironmentBlock(), a second expansion of the environment block using RtlExpandEnvironmentStrings() may help if a REG_EXPAND_SZ variable wasn't expanded completely when the environment was created. This is what the "appinfo" service does when Terminal is run from the start menu.

For example, a system REG_EXPAND_SZ variable can't depend on system variables that CreateEnvironmentBlock() hasn't set yet, such as "COMPUTERNAME", "ProgramFiles" and "CommonProgramFiles", but these variables should be defined the second time around. Similarly a user REG_EXPAND_SZ variable can't rely on volatile user variables that CreateEnvironmentBlock() hasn't set yet, such as "USERNAME", "HOMEDRIVE", and "HOMEPATH", but they should be defined when the environment is expanded the second time.

A second expansion does not guarantee, however, that REG_EXPAND_SZ variables can arbitrarily reference other REG_EXPAND_SZ variables that are defined in the same "Environment" key. For example, assume that REG_EXPAND_SZ variable "X" is defined as "%Y%"; REG_EXPAND_SZ variable "Y" is defined as "%Z"; and REG_SZ variable "Z" is defined as "spam". When the environment is created, if "X" is set before "Y" is set (based on the arbitrary enumeration order of the registry key), the value of "X" won't be expanded, but for sure the value of variable "Y" will be expanded to "spam" since REG_SZ "Z" is loaded first. In this case, a second expansion fixes the value of "X". On the other hand, say these are system variables, and the raw value of "Y" is changed to "%ProgramFiles%". In this case, "Y" will never be expanded when the environment is created since CreateEnvironmentBlock() doesn't set "ProgramFiles" in the environment block until after the system "Environment" key is loaded. Thus a second expansion sets the value of "X" to "%ProgramFiles%", which will require a third expansion.

@eryksun commented on GitHub (Jan 20, 2022): @zadjii-msft, note my comment in https://github.com/microsoft/terminal/issues/12157#issuecomment-1012606082. When Terminal is run from the start menu or "Open in Windows Terminal", a system service does the work of spawning the process. The service creates a new environment block for the user via `CreateEnvironmentBlock()`. So Terminal's initial environment may not be expanded the same as Explorer's. I think it should immediately call `RegenerateUserEnvironment()`, or something equivalent to that. Don't wait for a `WM_SETTINGCHANGE` message. When using just `CreateEnvironmentBlock()`, a second expansion of the environment block using `RtlExpandEnvironmentStrings()` may help if a `REG_EXPAND_SZ` variable wasn't expanded completely when the environment was created. This is what the "appinfo" service does when Terminal is run from the start menu. For example, a system `REG_EXPAND_SZ` variable can't depend on system variables that `CreateEnvironmentBlock()` hasn't set yet, such as "COMPUTERNAME", "ProgramFiles" and "CommonProgramFiles", but these variables should be defined the second time around. Similarly a user `REG_EXPAND_SZ` variable can't rely on volatile user variables that `CreateEnvironmentBlock()` hasn't set yet, such as "USERNAME", "HOMEDRIVE", and "HOMEPATH", but they should be defined when the environment is expanded the second time. A second expansion does not guarantee, however, that `REG_EXPAND_SZ` variables can arbitrarily reference other `REG_EXPAND_SZ` variables that are defined in the same "Environment" key. For example, assume that `REG_EXPAND_SZ` variable "X" is defined as `"%Y%"`; `REG_EXPAND_SZ` variable "Y" is defined as `"%Z"`; and `REG_SZ` variable "Z" is defined as `"spam"`. When the environment is created, if "X" is set before "Y" is set (based on the arbitrary enumeration order of the registry key), the value of "X" won't be expanded, but for sure the value of variable "Y" will be expanded to `"spam"` since `REG_SZ` "Z" is loaded first. In this case, a second expansion fixes the value of "X". On the other hand, say these are system variables, and the raw value of "Y" is changed to `"%ProgramFiles%"`. In this case, "Y" will never be expanded when the environment is created since `CreateEnvironmentBlock()` doesn't set "ProgramFiles" in the environment block until after the system "Environment" key is loaded. Thus a second expansion sets the value of "X" to `"%ProgramFiles%"`, which will require a third expansion.
Author
Owner

@miniksa commented on GitHub (Jan 27, 2022):

@eryksun I'm working on this here if you want to peek: https://github.com/microsoft/terminal/blob/dev/miniksa/env/src/inc/til/env.h

I can't say I've addressed all your concerns yet, but I have tried to replicate the spirit of how the OS and Explorer work behind the RegenerateUserEnvironment() function. It should be pretty close.

@miniksa commented on GitHub (Jan 27, 2022): @eryksun I'm working on this here if you want to peek: https://github.com/microsoft/terminal/blob/dev/miniksa/env/src/inc/til/env.h I can't say I've addressed all your concerns yet, but I have tried to replicate the spirit of how the OS and Explorer work behind the `RegenerateUserEnvironment()` function. It should be pretty close.
Author
Owner

@eryksun commented on GitHub (Jan 29, 2022):

@miniksa, Windows uses reserved environment variables with names that begin with "=", which effectively makes them hidden variables in most cases. This isn't a problem in practice, given an empty name is disallowed in "name=value" entries. CMD uses a hidden "=ExitCode" variable. The most common use is to store the working directory on a drive. For example, to resolve "Z:spam", the Windows API checks for an "=Z:" environment variable. The CMD shell's CHDIR command sets these variables, as does the C runtime's _[w]chdir().

You can set and check hidden variables directly via WinAPI SetEnvironmentVariableW() and GetEnvironmentVariableW(), or by examining the PEB with a debugger (e.g. the !peb command). CMD has a well-known bug when executing set " or set "" that displays them.

Defining hidden environment variables in an "Environment" key is not supported. They're documented as reserved names: "[equals] must not be used in the name". They can't be defined or modified conventionally as persistent variables -- not via the GUI editor, "setx.exe", or .NET SetEnvironmentVariable(variable, value, target). One would have to modify the registry directly.

That said, RegenerateUserEnvironment() loads any 'hidden' environment variables defined in the "Environment" keys. If you're looking to match the behavior of RegenerateUserEnvironment() as close as possible, then your code should retain them. At the least, I think an intentional choice should be made. If you want to support hidden environment variables that are defined in the registry, then the find_first_of() call in parse() should be modified to start at index 1. If you instead want to filter out all hidden variables, the loop should be modified to skip the entry when pos is 0. The current code keeps the first such entry that's found.

@eryksun commented on GitHub (Jan 29, 2022): @miniksa, Windows uses reserved environment variables with names that begin with "=", which effectively makes them hidden variables in most cases. This isn't a problem in practice, given an empty name is disallowed in "name=value" entries. CMD uses a hidden "=ExitCode" variable. The most common use is to store the working directory on a drive. For example, to resolve "Z:spam", the Windows API checks for an "=Z:" environment variable. The CMD shell's `CHDIR` command sets these variables, as does the C runtime's `_[w]chdir()`. You can set and check hidden variables directly via WinAPI `SetEnvironmentVariableW()` and `GetEnvironmentVariableW()`, or by examining the PEB with a debugger (e.g. the `!peb` command). CMD has a well-known bug when executing `set "` or `set ""` that displays them. Defining hidden environment variables in an "Environment" key is not supported. They're documented as reserved names: "[equals] must not be used in the name". They can't be defined or modified conventionally as persistent variables -- not via the GUI editor, "setx.exe", or .NET `SetEnvironmentVariable(variable, value, target)`. One would have to modify the registry directly. That said, `RegenerateUserEnvironment()` loads any 'hidden' environment variables defined in the "Environment" keys. If you're looking to match the behavior of `RegenerateUserEnvironment()` as close as possible, then your code should retain them. At the least, I think an intentional choice should be made. If you want to support hidden environment variables that are defined in the registry, then the `find_first_of()` call in `parse()` should be modified to start at index 1. If you instead want to filter out all hidden variables, the loop should be modified to skip the entry when `pos` is 0. The current code keeps the first such entry that's found.
Author
Owner

@htcfreek commented on GitHub (Jan 31, 2022):

@miniksa
In PT Run I have recreated the behavior of the os nearly exactly including removing and renaming variables.

You find the code here and many helpful informations here.

I you have questions don't hesitate to aske me. 😉

(Regarding hidden env vars I am not sure where the use case should be.)

@htcfreek commented on GitHub (Jan 31, 2022): @miniksa In PT Run I have recreated the behavior of the os nearly exactly including removing and renaming variables. You find the code [here](https://github.com/microsoft/PowerToys/blob/main/src/modules/launcher/PowerLauncher/Helper/EnvironmentHelper.cs) and many helpful informations [here](https://github.com/microsoft/PowerToys/pull/13363). I you have questions don't hesitate to aske me. 😉 (Regarding hidden env vars I am not sure where the use case should be.)
Author
Owner

@eryksun commented on GitHub (Jan 31, 2022):

(Regarding hidden env vars I am not sure where the use case should be.)

It's a matter of which behaviors of RegenerateUserEnvironment() should be intentionally preserved or rejected, instead of letting something slip through as undefined behavior. The current code stores the first 'hidden' variable to the mapping, and ignores the rest. I think ignoring them all is perfectly acceptable behavior. It's a small change to the code.

I just wanted to explain the details, in case people are unfamiliar with such variables. They are illegal environment variable names in the .NET and C runtime (though the C runtime itself sets them), and often they aren't displayed (e.g. Process Explorer doesn't show them), so I'd venture that some developers aren't even aware of their existence.

If someone happened across the existence of 'hidden' environment variables (e.g. set "" in CMD) and decided to define one in the registry, it's better for it to not be defined at all under Windows Terminal, instead of letting just that one variable slip through.

In PT Run I have recreated the behavior of the os nearly exactly including removing and renaming variables.

RegenerateUserEnvironment() and CreateEnvironmentBlock() have to load almost every environment variable from the registry or API calls. RegenerateUserEnvironment() starts with an already loaded environment for the current user, so it can selectively re-use more, but it's not much more. It still loads most variables from the registry and API. Refer to Michael's code.

@eryksun commented on GitHub (Jan 31, 2022): > (Regarding hidden env vars I am not sure where the use case should be.) It's a matter of which behaviors of `RegenerateUserEnvironment()` should be intentionally preserved or rejected, instead of letting something slip through as undefined behavior. The current code stores the first 'hidden' variable to the mapping, and ignores the rest. I think ignoring them all is perfectly acceptable behavior. It's a small change to the code. I just wanted to explain the details, in case people are unfamiliar with such variables. They are illegal environment variable names in the .NET and C runtime (though the C runtime itself sets them), and often they aren't displayed (e.g. Process Explorer doesn't show them), so I'd venture that some developers aren't even aware of their existence. If someone happened across the existence of 'hidden' environment variables (e.g. `set ""` in CMD) and decided to define one in the registry, it's better for it to not be defined at all under Windows Terminal, instead of letting just that one variable slip through. > In PT Run I have recreated the behavior of the os nearly exactly including removing and renaming variables. `RegenerateUserEnvironment()` and `CreateEnvironmentBlock()` have to load almost every environment variable from the registry or API calls. `RegenerateUserEnvironment()` starts with an already loaded environment for the current user, so it can selectively re-use more, but it's not much more. It still loads most variables from the registry and API. Refer to Michael's code.
Author
Owner

@miniksa commented on GitHub (Feb 1, 2022):

@miniksa, Windows uses reserved environment variables with names that begin with "=", which effectively makes them hidden variables in most cases. This isn't a problem in practice, given an empty name is disallowed in "name=value" entries. CMD uses a hidden "=ExitCode" variable. The most common use is to store the working directory on a drive. For example, to resolve "Z:spam", the Windows API checks for an "=Z:" environment variable. The CMD shell's CHDIR command sets these variables, as does the C runtime's _[w]chdir().

You can set and check hidden variables directly via WinAPI SetEnvironmentVariableW() and GetEnvironmentVariableW(), or by examining the PEB with a debugger (e.g. the !peb command). CMD has a well-known bug when executing set " or set "" that displays them.

Defining hidden environment variables in an "Environment" key is not supported. They're documented as reserved names: "[equals] must not be used in the name". They can't be defined or modified conventionally as persistent variables -- not via the GUI editor, "setx.exe", or .NET SetEnvironmentVariable(variable, value, target). One would have to modify the registry directly.

That said, RegenerateUserEnvironment() loads any 'hidden' environment variables defined in the "Environment" keys. If you're looking to match the behavior of RegenerateUserEnvironment() as close as possible, then your code should retain them. At the least, I think an intentional choice should be made. If you want to support hidden environment variables that are defined in the registry, then the find_first_of() call in parse() should be modified to start at index 1. If you instead want to filter out all hidden variables, the loop should be modified to skip the entry when pos is 0. The current code keeps the first such entry that's found.

I did notice the allowance for the leading = in the underlying code, but I wasn't really aware of the impacts or usages of them. I don't think I excluded them on purpose. It could be that I misread some of what is behind RegenerateUserEnvironment() and still need to work it out. My goal is this operates the same as that.

I do also know I still need to handle the recursive processing aspect of later variables being able to have things substituted in from earlier ones.

@miniksa commented on GitHub (Feb 1, 2022): > @miniksa, Windows uses reserved environment variables with names that begin with "=", which effectively makes them hidden variables in most cases. This isn't a problem in practice, given an empty name is disallowed in "name=value" entries. CMD uses a hidden "=ExitCode" variable. The most common use is to store the working directory on a drive. For example, to resolve "Z:spam", the Windows API checks for an "=Z:" environment variable. The CMD shell's `CHDIR` command sets these variables, as does the C runtime's `_[w]chdir()`. > > You can set and check hidden variables directly via WinAPI `SetEnvironmentVariableW()` and `GetEnvironmentVariableW()`, or by examining the PEB with a debugger (e.g. the `!peb` command). CMD has a well-known bug when executing `set "` or `set ""` that displays them. > > Defining hidden environment variables in an "Environment" key is not supported. They're documented as reserved names: "[equals] must not be used in the name". They can't be defined or modified conventionally as persistent variables -- not via the GUI editor, "setx.exe", or .NET `SetEnvironmentVariable(variable, value, target)`. One would have to modify the registry directly. > > That said, `RegenerateUserEnvironment()` loads any 'hidden' environment variables defined in the "Environment" keys. If you're looking to match the behavior of `RegenerateUserEnvironment()` as close as possible, then your code should retain them. At the least, I think an intentional choice should be made. If you want to support hidden environment variables that are defined in the registry, then the `find_first_of()` call in `parse()` should be modified to start at index 1. If you instead want to filter out all hidden variables, the loop should be modified to skip the entry when `pos` is 0. The current code keeps the first such entry that's found. I did notice the allowance for the leading `=` in the underlying code, but I wasn't really aware of the impacts or usages of them. I don't think I excluded them on purpose. It could be that I misread some of what is behind `RegenerateUserEnvironment()` and still need to work it out. My goal is this operates the same as that. I do also know I still need to handle the recursive processing aspect of later variables being able to have things substituted in from earlier ones.
Author
Owner

@sba923 commented on GitHub (Feb 16, 2022):

When I faced the issue yesterday and wondered whether it was already tracked here, I had no idea this was being discussed for more than 2 years...

I understand there are many corner cases, but couldn't the base use cases (user or app installer updates the user or system env vars) be addressed as a temporary solution?

@sba923 commented on GitHub (Feb 16, 2022): When I faced the issue yesterday and wondered whether it was already tracked here, I had no idea this was being discussed for more than 2 years... I understand there are many corner cases, but couldn't the base use cases (user or app installer updates the user or system env vars) be addressed as a temporary solution?
Author
Owner

@DHowett commented on GitHub (Feb 16, 2022):

Funny enough, we had a temporary solution! We only recently backed it out because it caused more problems than it fixed. :)

@DHowett commented on GitHub (Feb 16, 2022): Funny enough, we had a temporary solution! We only recently backed it out because it caused more problems than it fixed. :)
Author
Owner

@hros commented on GitHub (May 3, 2022):

Being a "late joiner", I wonder if there is a solution for windows-terminal to reload updated PATH environment variable.
I just noticed that after I updated the PATH environment variable, it was not updated in an existing command prompt in windows-terminal, nor was it updated it new command prompt tabs
However, when I opened a new window of windows-terminal, the PATH was up to date

Is there a "reload environment" command in windows-terminal?

@hros commented on GitHub (May 3, 2022): Being a "late joiner", I wonder if there is a solution for windows-terminal to reload updated PATH environment variable. I just noticed that after I updated the PATH environment variable, it was not updated in an existing command prompt in windows-terminal, nor was it updated it new command prompt tabs However, when I opened a new window of windows-terminal, the PATH was up to date Is there a "reload environment" command in windows-terminal?
Author
Owner

@dexeonify commented on GitHub (May 4, 2022):

No, which is why this issue is still open. The closest workaround I have is to run

$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")

after you update the PATH.

Source

@dexeonify commented on GitHub (May 4, 2022): No, which is why this issue is still open. The closest workaround I have is to run ```powershell $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") ``` after you update the PATH. [Source](https://stackoverflow.com/a/31845512/16689935)
Author
Owner

@Suncatcher commented on GitHub (May 19, 2022):

I observe the same behavior. I installed the package via winget that updated PATH variable, but it is not reflected in terminal

image

the opening new tab in terminal does NOT help, I need to reload the terminal. Very uncomfortable and inconvenient.

@Suncatcher commented on GitHub (May 19, 2022): I observe the same behavior. I installed the package via winget that updated PATH variable, but it is not reflected in terminal ![image](https://user-images.githubusercontent.com/6388034/169268031-c51e9dce-3618-4cdd-8ed5-5c966051262e.png) the opening new tab in terminal does NOT help, I need to reload the terminal. Very uncomfortable and inconvenient.
Author
Owner

@sba923 commented on GitHub (May 19, 2022):

I need to reload the terminal. Very uncomfortable and inconvenient.

That's especially inconvenient when one has long-running workloads in existing tabs. You just can't stop and reload the terminal. And I don't know how to start another terminal instance.

@sba923 commented on GitHub (May 19, 2022): > I need to reload the terminal. Very uncomfortable and inconvenient. That's especially inconvenient when one has long-running workloads in existing tabs. You just **can't** stop and reload the terminal. And I don't know how to start another terminal _instance_.
Author
Owner

@garretwilson commented on GitHub (Oct 1, 2022):

$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")

I had almost filed a bug with Node.js, then almost filed a bug with PowerShell, until I finally found this ticket. I had found the same workaround that @dexeonify mentioned.

Yes, I can see putting that workaround in my PowerShell $PROFILE so that every time I start PowerShell I know I'm getting the "one true path" (although I don't like the way that sounds, now that I think about it); but … really? Am I misreading things, or has this ticket been unresolved for over three years?!

Is this on any team's schedule to address? I think most people here would agree that just reloading Path would be a major step forward. The current situation is only furthering Windows' reputation that "I have to reboot after the slightest change for things to work properly".

@garretwilson commented on GitHub (Oct 1, 2022): > `$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")` I had almost filed a bug with Node.js, then almost filed a bug with PowerShell, until I finally found this ticket. I had found the same workaround that @dexeonify mentioned. Yes, I can see putting that workaround in my PowerShell `$PROFILE` so that every time I start PowerShell I know I'm getting the "one true path" (although I don't like the way that sounds, now that I think about it); but … really? Am I misreading things, or has this ticket been unresolved for over three years?! Is this on any team's schedule to address? I think most people here would agree that just reloading `Path` would be a major step forward. The current situation is only furthering Windows' reputation that "I have to reboot after the slightest change for things to work properly".
Author
Owner

@garretwilson commented on GitHub (Oct 1, 2022):

I've read most of the comments here, and I still don't get why closing the entire Microsoft Terminal instance and restarting it doesn't result in the latest Path environment variable being loaded. Surely Microsoft Terminal doesn't keep running in the background, does it?

@garretwilson commented on GitHub (Oct 1, 2022): I've read most of the comments here, and I still don't get why closing the entire Microsoft Terminal instance and restarting it doesn't result in the latest `Path` environment variable being loaded. Surely Microsoft Terminal doesn't keep running in the background, does it?
Author
Owner

@bruno-brant commented on GitHub (Oct 2, 2022):

It does

@bruno-brant commented on GitHub (Oct 2, 2022): It does
Author
Owner

@zadjii-msft commented on GitHub (Oct 2, 2022):

It does

It sure shouldn't be. There might be a lasting RuntimeBroker.exe, but I don't think that should have anything to do with it. Closing all the Terminal windows (s.t. there are no windowsterminal.exe's running) should cause the next Terminal launch to have a fresh env block.

@zadjii-msft commented on GitHub (Oct 2, 2022): > It does It sure _shouldn't be_. There might be a lasting RuntimeBroker.exe, but I don't think that should have anything to do with it. Closing all the Terminal windows (s.t. there are no windowsterminal.exe's running) _should_ cause the next Terminal launch to have a fresh env block.
Author
Owner

@garretwilson commented on GitHub (Oct 2, 2022):

Closing all the Terminal windows (s.t. there are no windowsterminal.exe's running) should cause the next Terminal launch to have a fresh env block.

But alas … it doth not. 😒

I see various "Console Windows Host" entries in Task Manager. Would that have anything to do with it? If so, does that mean there's yet another bug that Microsoft Terminal isn't shutting down properly?

@garretwilson commented on GitHub (Oct 2, 2022): > Closing all the Terminal windows (s.t. there are no windowsterminal.exe's running) should cause the next Terminal launch to have a fresh env block. But alas … it doth not. 😒 I see various "Console Windows Host" entries in Task Manager. Would that have anything to do with it? If so, does that mean there's yet another bug that Microsoft Terminal isn't shutting down properly?
Author
Owner

@char-46 commented on GitHub (Nov 6, 2022):

Now this problem more seriously.
Because of this program add a feature that can merge new window to existing window, so that I can't use Run command to create a new task to reload environment variable.

@char-46 commented on GitHub (Nov 6, 2022): Now this problem more seriously. Because of this program add a feature that can merge new window to existing window, so that I can't use Run command to create a new task to reload environment variable.
Author
Owner

@cforce commented on GitHub (Jan 17, 2023):

I can start a new cmd from windows start menu and my user PATH is loaded
If i do the same ( same cmd and params) via terminal it doesn't reflect the PATH changes.
I can open a dozens new terminal windows cmd windows from the same running terminal parent instance - issue still remains.
It seems to recover when i completly terminate the terminal parent and create a new and a new cmd window . so its terminal app caching the context. Do you use some cached state of context?
How to enforce REAL (re)loadind onf env?
My workaround is now to use choco's "refreshenv" .. but5 hey this is definitely a bug in the terminal application so please reopen this issue.

@cforce commented on GitHub (Jan 17, 2023): I can start a new cmd from windows start menu and my user PATH is loaded If i do the same ( same cmd and params) via terminal it doesn't reflect the PATH changes. I can open a dozens new terminal windows cmd windows from the same running terminal parent instance - issue still remains. It seems to recover when i completly terminate the terminal parent and create a new and a new cmd window . so its terminal app caching the context. Do you use some cached state of context? How to enforce REAL (re)loadind onf env? My workaround is now to use choco's "refreshenv" .. but5 hey this is definitely a bug in the terminal application so please reopen this issue.
Author
Owner

@zadjii-msft commented on GitHub (Jan 17, 2023):

so please reopen this issue.

Thanks for the feedback! As you'll notice, this issue is currently open. We've got an idea how to solve this with the work being done in #12516, now we just need to book some time to polish that out.

If anyone is interested in helping out, I'd be more than happy to help give an outline of what I was planning on doing with that thread. It'll unfortunately be a few months before I can loop back on that, so if folks want this sooner than that, I'd appreciate the help ☺️

@zadjii-msft commented on GitHub (Jan 17, 2023): > so please reopen this issue. Thanks for the feedback! As you'll notice, this issue _is currently open_. We've got an idea how to solve this with the work being done in #12516, now we just need to book some time to polish that out. If anyone is interested in helping out, I'd be more than happy to help give an outline of what I was planning on doing with that thread. It'll unfortunately be a few months before I can loop back on that, so if folks want this sooner than that, I'd appreciate the help ☺️
Author
Owner

@zadjii-msft commented on GitHub (Jan 24, 2023):

Note

Walkthrough

Okay, if I was gonna do this, here's how I'd approach this. I'd make it two PRs. One to finish building the helper class, and one to actually integrate it into the Teminal

  • Finish the til::env PR, #12516.
    • This was intended to be a helper that would generate an env block similar to the way that the Windows shell does when launching new processes from explorer.exe.
    • I honestly don't know what's missing from the PR. Fortunately, @miniksa is still a close friend of the team, so I think he'd be more than willing to give some pointers.
    • it was pretty much done except for me updating the spell check and perhaps adding more tests

    • "please stop inheriting from std::map"

      • In the current incarnation of that PR, it directly derives from std::map, which is generally frowned upon. Find a way to implement it without that. Probably fine to just move the map into a private member variable.
  • Generate fresh environment blocks for every connection (tab, pane, etc)
    • Use a til::env to generate the new env block.
    • TerminalPage::_CreateConnectionFromSettings (in TerminalPage.cpp) is the method responsible for creating new ConptyConnections (which is what's used to hook the Terminal up to a cmd.exe or other commandline process)
      • In that method, we're already injecting a couple env vars
        StringMap envMap{};
        envMap.Insert(L"WT_PROFILE_ID", guidWString);
        envMap.Insert(L"WSLENV", L"WT_PROFILE_ID");
        
        So that seems like about the right place to make changes to me.
    • We should also add a global setting to allow folks to opt-out of this behavior (in case we break someone's workflow)
      • This should be stored in the Global settings, so you could add a bool to the MTSM_GLOBAL_SETTINGS x-macro in MTSMSettings.h.
      • Name it something like experimental.reloadEnvironmentVariables, and default it to true.

I honestly think that's it. That should just work. We shouldn't need to listen for env var updates, because we should just get a fresh block everytime.

And then we can handle ourselves the follow-up of merging #9287 with the code written to solve this issue.

@zadjii-msft commented on GitHub (Jan 24, 2023): > **Note** > ## Walkthrough Okay, if I was gonna do this, here's how I'd approach this. I'd make it two PRs. One to finish building the helper class, and one to actually integrate it into the Teminal * [ ] Finish the `til::env` PR, #12516. * This was intended to be a helper that would generate an env block similar to the way that the Windows shell does when launching new processes from explorer.exe. * ~I honestly don't know what's missing from the PR. Fortunately, @miniksa is still a close friend of the team, so I think he'd be more than willing to give some pointers.~ * > it was pretty much done except for me updating the spell check and perhaps adding more tests * > "please stop inheriting from `std::map`" * In the current incarnation of that PR, it directly derives from `std::map`, which is generally frowned upon. Find a way to implement it without that. Probably fine to just move the `map` into a private member variable. * [ ] Generate fresh environment blocks for _every_ connection (tab, pane, etc) * [ ] Use a `til::env` to generate the new env block. * [ ] `TerminalPage::_CreateConnectionFromSettings` (in `TerminalPage.cpp`) is the method responsible for creating new `ConptyConnection`s (which is what's used to hook the Terminal up to a `cmd.exe` or other commandline process) * In that method, we're already injecting a couple env vars ```c++ StringMap envMap{}; envMap.Insert(L"WT_PROFILE_ID", guidWString); envMap.Insert(L"WSLENV", L"WT_PROFILE_ID"); ``` So that seems like about the right place to make changes to me. * [ ] We should also add a _global_ setting to allow folks to opt-out of this behavior (in case we break someone's workflow) * [ ] This should be stored in the Global settings, so you could add a `bool` to the `MTSM_GLOBAL_SETTINGS` x-macro in `MTSMSettings.h`. * [ ] Name it something like `experimental.reloadEnvironmentVariables`, and default it to `true`. I honestly think that's it. That should just work. We shouldn't need to listen for env var updates, because we should just get a fresh block everytime. And then we can handle ourselves the follow-up of merging #9287 with the code written to solve this issue.
Author
Owner

@miniksa commented on GitHub (Jan 24, 2023):

AFAIK, it was pretty much done except for me updating the spell check and perhaps adding more tests. It probably just needs a spit shine then check it in and call anything else a bug, @zadjii-msft.

@miniksa commented on GitHub (Jan 24, 2023): AFAIK, it was pretty much done except for me updating the spell check and perhaps adding more tests. It probably just needs a spit shine then check it in and call anything else a bug, @zadjii-msft.
Author
Owner

@DHowett commented on GitHub (Jan 24, 2023):

Ah, plus me imploring you to "please stop inheriting from std::map". Thanks for the update 😄

@DHowett commented on GitHub (Jan 24, 2023): Ah, plus me imploring you to "please stop inheriting from `std::map`". Thanks for the update :smile:
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/terminal#1495