Repository: microsoft/terminal (language: cpp)

Issue:
Feature Request: Terminal should listen for the WM_SETTINGCHANGE for environment variable updates
# 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.



Discussion / hints:
Related discussion in ConEmu. https://github.com/Maximus5/ConEmu/issues/468
Opening a new tab should definitely inherit from the current system environment.
&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.
> 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.
Make sure when we reload this, we also reload settings. That'll probably fix the keyboard issue in 4735.
> 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.
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. ^_^
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.
The environment variables bit of this was addressed in #7243; I'm leaving this issue open for the other environment settings.
> 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.
> 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.
That's the approach used in #7243, but it caused problems and had to be reverted; see #7418 and e.g., 616a71dd23e8fff717919c20c382edeaf59f433a
> 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 :-( 
> 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()`.

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:
Please, just check for new environment variables when a new tab is created.
> 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 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.
@miniksa is looking at some of our options here and will report back.
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
@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 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, 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. 

@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.)
> (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.
> @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. 

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?
Funny enough, we had a temporary solution! We only recently backed it out because it caused more problems than it fixed. :)
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?
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)
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.
> 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_.
> `$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".
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?
It does


> 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. 
> 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?
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.
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.
> 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 ☺️ 
> **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. 
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.
Ah, plus me imploring you to "please stop inheriting from `std::map`". Thanks for the update :smile:

Fix patch:
diff --git a/.github/actions/spelling/allow/apis.txt b/.github/actions/spelling/allow/apis.txt
index ce36d8d2a2b..0843ec910e6 100644
--- a/.github/actions/spelling/allow/apis.txt
+++ b/.github/actions/spelling/allow/apis.txt
@@ -34,7 +34,6 @@ DNE
 DONTADDTORECENT
 DWMSBT
 DWMWA
-DWMWA
 DWORDLONG
 endfor
 ENDSESSION
@@ -251,3 +250,4 @@ xtree
 xutility
 YIcon
 YMax
+zwstring
diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json
index bac66b8066e..64b5903dc39 100644
--- a/doc/cascadia/profiles.schema.json
+++ b/doc/cascadia/profiles.schema.json
@@ -2164,6 +2164,11 @@
           "description": "When set to true, the background image for the currently focused profile is expanded to encompass the entire window, beneath other panes.",
           "type": "boolean"
         },
+        "compatibility.reloadEnvironmentVariables": {
+          "default": true,
+          "description": "When set to true, when opening a new tab or pane it will get reloaded environment variables.",
+          "type": "boolean"
+        },
         "initialCols": {
           "default": 120,
           "description": "The number of columns displayed in the window upon first load. If \"launchMode\" is set to \"maximized\" (or \"maximizedFocus\"), this property is ignored.",
diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp
index 94e9865e63a..8cc6162ff5b 100644
--- a/src/cascadia/TerminalApp/TerminalPage.cpp
+++ b/src/cascadia/TerminalApp/TerminalPage.cpp
@@ -1318,6 +1318,8 @@ namespace winrt::TerminalApp::implementation
                                                                                  winrt::guid());
 
             valueSet.Insert(L"passthroughMode", Windows::Foundation::PropertyValue::CreateBoolean(settings.VtPassthrough()));
+            valueSet.Insert(L"reloadEnvironmentVariables",
+                            Windows::Foundation::PropertyValue::CreateBoolean(_settings.GlobalSettings().ReloadEnvironmentVariables()));
 
             conhostConn.Initialize(valueSet);
 
diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp
index 9ddc90cdf2d..fef88c33628 100644
--- a/src/cascadia/TerminalConnection/ConptyConnection.cpp
+++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp
@@ -5,6 +5,7 @@
 #include "ConptyConnection.h"
 
 #include <conpty-static.h>
+#include <til/env.h>
 #include <winternl.h>
 
 #include "CTerminalHandoff.h"
@@ -95,7 +96,20 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
         });
 
         // Populate the environment map with the current environment.
-        RETURN_IF_FAILED(Utils::UpdateEnvironmentMapW(environment));
+        if (_reloadEnvironmentVariables)
+        {
+            til::env refreshedEnvironment;
+            refreshedEnvironment.regenerate();
+
+            for (auto& [key, value] : refreshedEnvironment.as_map())
+            {
+                environment.try_emplace(key, std::move(value));
+            }
+        }
+        else
+        {
+            RETURN_IF_FAILED(Utils::UpdateEnvironmentMapW(environment));
+        }
 
         {
             // Convert connection Guid to string and ignore the enclosing '{}'.
@@ -272,6 +286,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
             {
                 _passthroughMode = winrt::unbox_value_or<bool>(settings.TryLookup(L"passthroughMode").try_as<Windows::Foundation::IPropertyValue>(), _passthroughMode);
             }
+            _reloadEnvironmentVariables = winrt::unbox_value_or<bool>(settings.TryLookup(L"reloadEnvironmentVariables").try_as<Windows::Foundation::IPropertyValue>(),
+                                                                      _reloadEnvironmentVariables);
         }
 
         if (_guid == guid{})
diff --git a/src/cascadia/TerminalConnection/ConptyConnection.h b/src/cascadia/TerminalConnection/ConptyConnection.h
index 3709ccef33a..60528dc0463 100644
--- a/src/cascadia/TerminalConnection/ConptyConnection.h
+++ b/src/cascadia/TerminalConnection/ConptyConnection.h
@@ -89,6 +89,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
         std::wstring _u16Str{};
         std::array<char, 4096> _buffer{};
         bool _passthroughMode{};
+        bool _reloadEnvironmentVariables{};
 
         struct StartupInfoFromDefTerm
         {
diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl
index 4ee2b0bf9e6..f5bf5676294 100644
--- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl
+++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl
@@ -79,6 +79,7 @@ namespace Microsoft.Terminal.Settings.Model
         INHERITABLE_SETTING(Boolean, ForceFullRepaintRendering);
         INHERITABLE_SETTING(Boolean, SoftwareRendering);
         INHERITABLE_SETTING(Boolean, UseBackgroundImageForWindow);
+        INHERITABLE_SETTING(Boolean, ReloadEnvironmentVariables);
         INHERITABLE_SETTING(Boolean, ForceVTInput);
         INHERITABLE_SETTING(Boolean, DebugFeaturesEnabled);
         INHERITABLE_SETTING(Boolean, StartOnUserLogin);
diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h
index 73d7cd80009..af4798a75b7 100644
--- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h
+++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h
@@ -27,6 +27,7 @@ Author(s):
     X(bool, ForceFullRepaintRendering, "experimental.rendering.forceFullRepaint", false)                                                                   \
     X(bool, SoftwareRendering, "experimental.rendering.software", false)                                                                                   \
     X(bool, UseBackgroundImageForWindow, "experimental.useBackgroundImageForWindow", false)                                                                \
+    X(bool, ReloadEnvironmentVariables, "compatibility.reloadEnvironmentVariables", true)                                                                  \
     X(bool, ForceVTInput, "experimental.input.forceVT", false)                                                                                             \
     X(bool, TrimBlockSelection, "trimBlockSelection", true)                                                                                                \
     X(bool, DetectURLs, "experimental.detectURLs", true)                                                                                                   \
diff --git a/src/inc/til/env.h b/src/inc/til/env.h
index 031ea4388ed..72e626a8da8 100644
--- a/src/inc/til/env.h
+++ b/src/inc/til/env.h
@@ -6,6 +6,12 @@
 #include <wil/token_helpers.h>
 #include <winternl.h>
 
+#pragma warning(push)
+#pragma warning(disable : 26429) // Symbol '...' is never tested for nullness, it can be marked as not_null (f.23).
+#pragma warning(disable : 26472) // Don't use a static_cast for arithmetic conversions. Use brace initialization, gsl::narrow_cast or gsl::narrow (type.1).
+#pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
+#pragma warning(disable : 26490) // Don't use reinterpret_cast (type.1).
+
 #ifdef UNIT_TESTING
 class EnvTests;
 #endif
@@ -26,13 +32,14 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
         //      https://docs.microsoft.com/en-us/windows/desktop/ProcThread/changing-environment-variables
         //
         // - Returns CSTR_LESS_THAN, CSTR_EQUAL or CSTR_GREATER_THAN
-        [[nodiscard]] int compare_string_ordinal(const std::wstring_view& lhs, const std::wstring_view& rhs) noexcept
+        [[nodiscard]] inline int compare_string_ordinal(const std::wstring_view& lhs, const std::wstring_view& rhs) noexcept
         {
-            int result = CompareStringOrdinal(lhs.data(),
-                                              ::base::saturated_cast<int>(lhs.size()),
-                                              rhs.data(),
-                                              ::base::saturated_cast<int>(rhs.size()),
-                                              TRUE);
+            const auto result = CompareStringOrdinal(
+                lhs.data(),
+                ::base::saturated_cast<int>(lhs.size()),
+                rhs.data(),
+                ::base::saturated_cast<int>(rhs.size()),
+                TRUE);
             FAIL_FAST_LAST_ERROR_IF(!result);
             return result;
         }
@@ -47,30 +54,30 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
 
         namespace vars
         {
-            inline constexpr std::wstring_view system_root{ L"SystemRoot" };
-            inline constexpr std::wstring_view system_drive{ L"SystemDrive" };
-            inline constexpr std::wstring_view all_users_profile{ L"ALLUSERSPROFILE" };
-            inline constexpr std::wstring_view public_var{ L"PUBLIC" };
-            inline constexpr std::wstring_view program_data{ L"ProgramData" };
-            inline constexpr std::wstring_view computer_name{ L"COMPUTERNAME" };
-            inline constexpr std::wstring_view user_name{ L"USERNAME" };
-            inline constexpr std::wstring_view user_domain{ L"USERDOMAIN" };
-            inline constexpr std::wstring_view user_dns_domain{ L"USERDNSDOMAIN" };
-            inline constexpr std::wstring_view home_drive{ L"HOMEDRIVE" };
-            inline constexpr std::wstring_view home_share{ L"HOMESHARE" };
-            inline constexpr std::wstring_view home_path{ L"HOMEPATH" };
-            inline constexpr std::wstring_view user_profile{ L"USERPROFILE" };
-            inline constexpr std::wstring_view app_data{ L"APPDATA" };
-            inline constexpr std::wstring_view local_app_data{ L"LOCALAPPDATA" };
-
-            inline constexpr std::wstring_view program_files{ L"ProgramFiles" };
-            inline constexpr std::wstring_view program_files_x86{ L"ProgramFiles(x86)" };
-            inline constexpr std::wstring_view program_files_arm64{ L"ProgramFiles(Arm)" };
-            inline constexpr std::wstring_view program_w6432{ L"ProgramW6432" };
-            inline constexpr std::wstring_view common_program_files{ L"CommonProgramFiles" };
-            inline constexpr std::wstring_view common_program_files_x86{ L"CommonProgramFiles(x86)" };
-            inline constexpr std::wstring_view common_program_files_arm64{ L"CommonProgramFiles(Arm)" };
-            inline constexpr std::wstring_view common_program_w6432{ L"CommonProgramW6432" };
+            inline constexpr wil::zwstring_view system_root{ L"SystemRoot" };
+            inline constexpr wil::zwstring_view system_drive{ L"SystemDrive" };
+            inline constexpr wil::zwstring_view all_users_profile{ L"ALLUSERSPROFILE" };
+            inline constexpr wil::zwstring_view public_var{ L"PUBLIC" };
+            inline constexpr wil::zwstring_view program_data{ L"ProgramData" };
+            inline constexpr wil::zwstring_view computer_name{ L"COMPUTERNAME" };
+            inline constexpr wil::zwstring_view user_name{ L"USERNAME" };
+            inline constexpr wil::zwstring_view user_domain{ L"USERDOMAIN" };
+            inline constexpr wil::zwstring_view user_dns_domain{ L"USERDNSDOMAIN" };
+            inline constexpr wil::zwstring_view home_drive{ L"HOMEDRIVE" };
+            inline constexpr wil::zwstring_view home_share{ L"HOMESHARE" };
+            inline constexpr wil::zwstring_view home_path{ L"HOMEPATH" };
+            inline constexpr wil::zwstring_view user_profile{ L"USERPROFILE" };
+            inline constexpr wil::zwstring_view app_data{ L"APPDATA" };
+            inline constexpr wil::zwstring_view local_app_data{ L"LOCALAPPDATA" };
+
+            inline constexpr wil::zwstring_view program_files{ L"ProgramFiles" };
+            inline constexpr wil::zwstring_view program_files_x86{ L"ProgramFiles(x86)" };
+            inline constexpr wil::zwstring_view program_files_arm64{ L"ProgramFiles(Arm)" };
+            inline constexpr wil::zwstring_view program_w6432{ L"ProgramW6432" };
+            inline constexpr wil::zwstring_view common_program_files{ L"CommonProgramFiles" };
+            inline constexpr wil::zwstring_view common_program_files_x86{ L"CommonProgramFiles(x86)" };
+            inline constexpr wil::zwstring_view common_program_files_arm64{ L"CommonProgramFiles(Arm)" };
+            inline constexpr wil::zwstring_view common_program_w6432{ L"CommonProgramW6432" };
 
             const std::array program_files_map{
                 std::pair{ L"ProgramFilesDir", program_files },
@@ -89,11 +96,11 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
 
             namespace reg
             {
-                inline constexpr std::wstring_view program_files_root{ LR"(Software\Microsoft\Windows\CurrentVersion)" };
-                inline constexpr std::wstring_view system_env_var_root{ LR"(SYSTEM\CurrentControlSet\Control\Session Manager\Environment)" };
-                inline constexpr std::wstring_view user_env_var_root{ LR"(Environment)" };
-                inline constexpr std::wstring_view user_volatile_env_var_root{ LR"(Volatile Environment)" };
-                inline constexpr std::wstring_view user_volatile_session_env_var_root_pattern{ LR"(Volatile Environment\{0:d})" };
+                inline constexpr wil::zwstring_view program_files_root{ LR"(Software\Microsoft\Windows\CurrentVersion)" };
+                inline constexpr wil::zwstring_view system_env_var_root{ LR"(SYSTEM\CurrentControlSet\Control\Session Manager\Environment)" };
+                inline constexpr wil::zwstring_view user_env_var_root{ LR"(Environment)" };
+                inline constexpr wil::zwstring_view user_volatile_env_var_root{ LR"(Volatile Environment)" };
+                inline constexpr wil::zwstring_view user_volatile_session_env_var_root_pattern{ LR"(Volatile Environment\{0:d})" };
             };
         };
 
@@ -101,33 +108,32 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
         {
             /** Looks up the computer name and fails if it is not found. */
             template<typename string_type, size_t initialBufferLength = MAX_COMPUTERNAME_LENGTH + 1>
-            inline HRESULT GetComputerNameW(string_type& result) WI_NOEXCEPT
+            HRESULT GetComputerNameW(string_type& result) WI_NOEXCEPT
             {
-                return wil::AdaptFixedSizeToAllocatedResult<string_type, initialBufferLength>(result,
-                                                                                              [&](_Out_writes_(valueLength) PWSTR value, size_t valueLength, _Out_ size_t* valueLengthNeededWithNul) -> HRESULT {
-                                                                                                  // If the function succeeds, the return value is the number of characters stored in the buffer
-                                                                                                  // pointed to by lpBuffer, not including the terminating null character.
-                                                                                                  //
-                                                                                                  // If lpBuffer is not large enough to hold the data, the return value is the buffer size, in
-                                                                                                  // characters, required to hold the string and its terminating null character and the contents of
-                                                                                                  // lpBuffer are undefined.
-                                                                                                  //
-                                                                                                  // If the function fails, the return value is zero. If the specified environment variable was not
-                                                                                                  // found in the environment block, GetLastError returns ERROR_ENVVAR_NOT_FOUND.
-
-                                                                                                  ::SetLastError(ERROR_SUCCESS);
-
-                                                                                                  DWORD length = static_cast<DWORD>(valueLength);
-
-                                                                                                  auto result = ::GetComputerNameW(value, &length);
-                                                                                                  *valueLengthNeededWithNul = length;
-                                                                                                  RETURN_IF_WIN32_BOOL_FALSE_EXPECTED(result);
-                                                                                                  if (*valueLengthNeededWithNul < valueLength)
-                                                                                                  {
-                                                                                                      (*valueLengthNeededWithNul)++; // It fit, account for the null.
-                                                                                                  }
-                                                                                                  return S_OK;
-                                                                                              });
+                return wil::AdaptFixedSizeToAllocatedResult<string_type, initialBufferLength>(result, [&](_Out_writes_(valueLength) PWSTR value, size_t valueLength, _Out_ size_t* valueLengthNeededWithNul) -> HRESULT {
+                    // If the function succeeds, the return value is the number of characters stored in the buffer
+                    // pointed to by lpBuffer, not including the terminating null character.
+                    //
+                    // If lpBuffer is not large enough to hold the data, the return value is the buffer size, in
+                    // characters, required to hold the string and its terminating null character and the contents of
+                    // lpBuffer are undefined.
+                    //
+                    // If the function fails, the return value is zero. If the specified environment variable was not
+                    // found in the environment block, GetLastError returns ERROR_ENVVAR_NOT_FOUND.
+
+                    ::SetLastError(ERROR_SUCCESS);
+
+                    DWORD length = static_cast<DWORD>(valueLength);
+
+                    const auto result = ::GetComputerNameW(value, &length);
+                    *valueLengthNeededWithNul = length;
+                    RETURN_IF_WIN32_BOOL_FALSE_EXPECTED(result);
+                    if (*valueLengthNeededWithNul < valueLength)
+                    {
+                        (*valueLengthNeededWithNul)++; // It fit, account for the null.
+                    }
+                    return S_OK;
+                });
             }
 
             /** Looks up the computer name and returns null if it is not found. */
@@ -161,18 +167,17 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
 
             /** Looks up a registry value from 'key' and fails if it is not found. */
             template<typename string_type, size_t initialBufferLength = 256>
-            inline HRESULT RegQueryValueExW(HKEY key, PCWSTR valueName, string_type& result) WI_NOEXCEPT
+            HRESULT RegQueryValueExW(HKEY key, PCWSTR valueName, string_type& result) WI_NOEXCEPT
             {
-                return wil::AdaptFixedSizeToAllocatedResult<string_type, initialBufferLength>(result,
-                                                                                              [&](_Out_writes_(valueLength) PWSTR value, size_t valueLength, _Out_ size_t* valueLengthNeededWithNul) -> HRESULT {
-                                                                                                  auto length = gsl::narrow<DWORD>(valueLength * sizeof(wchar_t));
-                                                                                                  const auto status = ::RegQueryValueExW(key, valueName, 0, nullptr, reinterpret_cast<BYTE*>(value), &length);
-                                                                                                  // length will receive the number of bytes including trailing null byte. Convert to a number of wchar_t's.
-                                                                                                  // AdaptFixedSizeToAllocatedResult will then resize buffer to valueLengthNeededWithNull.
-                                                                                                  // We're rounding up to prevent infinite loops if the data isn't a REG_SZ and length isn't divisible by 2.
-                                                                                                  *valueLengthNeededWithNul = (length + sizeof(wchar_t) - 1) / sizeof(wchar_t);
-                                                                                                  return status == ERROR_MORE_DATA ? S_OK : HRESULT_FROM_WIN32(status);
-                                                                                              });
+                return wil::AdaptFixedSizeToAllocatedResult<string_type, initialBufferLength>(result, [&](_Out_writes_(valueLength) PWSTR value, size_t valueLength, _Out_ size_t* valueLengthNeededWithNul) -> HRESULT {
+                    auto length = gsl::narrow<DWORD>(valueLength * sizeof(wchar_t));
+                    const auto status = ::RegQueryValueExW(key, valueName, nullptr, nullptr, reinterpret_cast<BYTE*>(value), &length);
+                    // length will receive the number of bytes including trailing null byte. Convert to a number of wchar_t's.
+                    // AdaptFixedSizeToAllocatedResult will then resize buffer to valueLengthNeededWithNull.
+                    // We're rounding up to prevent infinite loops if the data isn't a REG_SZ and length isn't divisible by 2.
+                    *valueLengthNeededWithNul = (length + sizeof(wchar_t) - 1) / sizeof(wchar_t);
+                    return status == ERROR_MORE_DATA ? S_OK : HRESULT_FROM_WIN32(status);
+                });
             }
 
             /** Looks up a registry value from 'key' and returns null if it is not found. */
@@ -209,19 +214,18 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
             template<typename string_type, size_t stackBufferLength = 256>
             HRESULT GetShortPathNameW(PCWSTR file, string_type& path)
             {
-                const auto hr = wil::AdaptFixedSizeToAllocatedResult<string_type, stackBufferLength>(path,
-                                                                                                     [&](_Out_writes_(valueLength) PWSTR value, size_t valueLength, _Out_ size_t* valueLengthNeededWithNull) -> HRESULT {
-                                                                                                         // Note that GetShortPathNameW() is not limited to MAX_PATH
-                                                                                                         // but it does take a fixed size buffer.
-                                                                                                         *valueLengthNeededWithNull = ::GetShortPathNameW(file, value, static_cast<DWORD>(valueLength));
-                                                                                                         RETURN_LAST_ERROR_IF(*valueLengthNeededWithNull == 0);
-                                                                                                         WI_ASSERT((*value != L'\0') == (*valueLengthNeededWithNull < valueLength));
-                                                                                                         if (*valueLengthNeededWithNull < valueLength)
-                                                                                                         {
-                                                                                                             (*valueLengthNeededWithNull)++; // it fit, account for the null
-                                                                                                         }
-                                                                                                         return S_OK;
-                                                                                                     });
+                const auto hr = wil::AdaptFixedSizeToAllocatedResult<string_type, stackBufferLength>(path, [&](_Out_writes_(valueLength) PWSTR value, size_t valueLength, _Out_ size_t* valueLengthNeededWithNull) -> HRESULT {
+                    // Note that GetShortPathNameW() is not limited to MAX_PATH
+                    // but it does take a fixed size buffer.
+                    *valueLengthNeededWithNull = ::GetShortPathNameW(file, value, static_cast<DWORD>(valueLength));
+                    RETURN_LAST_ERROR_IF(*valueLengthNeededWithNull == 0);
+                    WI_ASSERT((*value != L'\0') == (*valueLengthNeededWithNull < valueLength));
+                    if (*valueLengthNeededWithNull < valueLength)
+                    {
+                        (*valueLengthNeededWithNull)++; // it fit, account for the null
+                    }
+                    return S_OK;
+                });
                 return hr;
             }
 
@@ -249,27 +253,27 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
         std::map<std::wstring, std::wstring, til::details::wstring_case_insensitive_compare> _envMap{};
 
         // We make copies of the environment variable names to ensure they are null terminated.
-        void get(std::wstring variable)
+        void get(wil::zwstring_view variable)
         {
-            if (auto value = wil::TryGetEnvironmentVariableW(variable.c_str()))
+            if (auto value = wil::TryGetEnvironmentVariableW<std::wstring>(variable.c_str()); !value.empty())
             {
-                save_to_map(std::move(variable), std::move(value.get()));
+                save_to_map(std::wstring{ variable }, std::move(value));
             }
         }
 
         void get_computer_name()
         {
-            if (auto value = til::details::wil_env::TryGetComputerNameW())
+            if (auto value = til::details::wil_env::TryGetComputerNameW<std::wstring>(); !value.empty())
             {
-                save_to_map(std::wstring{ til::details::vars::computer_name }, std::move(value.get()));
+                save_to_map(std::wstring{ til::details::vars::computer_name }, std::move(value));
             }
         }
 
         void get_user_name_and_domain()
         try
         {
-            auto token = wil::open_current_access_token();
-            auto user = wil::get_token_information<TOKEN_USER>(token.get());
+            const auto token = wil::open_current_access_token();
+            const auto user = wil::get_token_information<TOKEN_USER>(token.get());
 
             DWORD accountNameSize = 0, userDomainSize = 0;
             SID_NAME_USE sidNameUse;
@@ -305,10 +309,10 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
             }
         }
 
-        void get_vars_from_registry(HKEY rootKey, std::wstring_view subkey)
+        void get_vars_from_registry(HKEY rootKey, wil::zwstring_view subkey)
         {
             wil::unique_hkey key;
-            if (RegOpenKeyExW(rootKey, subkey.data(), 0, KEY_READ, &key) == ERROR_SUCCESS)
+            if (RegOpenKeyExW(rootKey, subkey.c_str(), 0, KEY_READ, &key) == ERROR_SUCCESS)
             {
                 DWORD maxValueNameSize = 0, maxValueDataSize = 0;
                 if (RegQueryInfoKeyW(key.get(), nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, &maxValueNameSize, &maxValueDataSize, nullptr, nullptr) == ERROR_SUCCESS)
@@ -329,7 +333,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
 
                         while (true)
                         {
-                            DWORD result = RegEnumValueW(key.get(), index, valueName.data(), &valueNameSize, nullptr, &type, valueData.data(), &valueDataSize);
+                            const DWORD result = RegEnumValueW(key.get(), index, valueName.data(), &valueNameSize, nullptr, &type, valueData.data(), &valueDataSize);
                             if (result != ERROR_SUCCESS)
                             {
                                 break;
@@ -363,11 +367,11 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
                                 {
                                     if (is_path_var(valueName))
                                     {
-                                        concat_var(valueName, std::wstring{ data });
+                                        concat_var(valueName, std::move(data));
                                     }
                                     else
                                     {
-                                        set_user_environment_var(valueName, std::wstring{ data });
+                                        set_user_environment_var(valueName, std::move(data));
                                     }
                                 }
                             }
@@ -449,10 +453,9 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
                 // Otherwise, just take advantage of the one it has.
                 if (existing->second.back() != L';')
                 {
-                    existing->second.append(L";");
+                    existing->second.push_back(L';');
                 }
                 existing->second.append(value);
-                save_to_map(std::move(var), std::move(existing->second));
             }
             else
             {
@@ -468,12 +471,12 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
             }
         }
 
-        static constexpr std::wstring_view temp{ L"temp" };
-        static constexpr std::wstring_view tmp{ L"tmp" };
         std::wstring check_for_temp(std::wstring_view var, std::wstring_view value)
         {
-            if (til::details::compare_string_ordinal(var.data(), temp.data()) == CSTR_EQUAL ||
-                til::details::compare_string_ordinal(var.data(), tmp.data()) == CSTR_EQUAL)
+            static constexpr std::wstring_view temp{ L"temp" };
+            static constexpr std::wstring_view tmp{ L"tmp" };
+            if (til::details::compare_string_ordinal(var, temp) == CSTR_EQUAL ||
+                til::details::compare_string_ordinal(var, tmp) == CSTR_EQUAL)
             {
                 return til::details::wil_env::GetShortPathNameW<std::wstring, 256>(value.data());
             }
@@ -483,17 +486,17 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
             }
         }
 
-        static constexpr std::wstring_view path{ L"Path" };
-        static constexpr std::wstring_view libPath{ L"LibPath" };
-        static constexpr std::wstring_view os2LibPath{ L"Os2LibPath" };
-        bool is_path_var(std::wstring_view input)
+        static bool is_path_var(std::wstring_view input) noexcept
         {
-            return til::details::compare_string_ordinal(input.data(), path.data()) == CSTR_EQUAL ||
-                   til::details::compare_string_ordinal(input.data(), libPath.data()) == CSTR_EQUAL ||
-                   til::details::compare_string_ordinal(input.data(), os2LibPath.data()) == CSTR_EQUAL;
+            static constexpr std::wstring_view path{ L"Path" };
+            static constexpr std::wstring_view libPath{ L"LibPath" };
+            static constexpr std::wstring_view os2LibPath{ L"Os2LibPath" };
+            return til::details::compare_string_ordinal(input, path) == CSTR_EQUAL ||
+                   til::details::compare_string_ordinal(input, libPath) == CSTR_EQUAL ||
+                   til::details::compare_string_ordinal(input, os2LibPath) == CSTR_EQUAL;
         }
 
-        void strip_trailing_null(std::wstring& str)
+        static void strip_trailing_null(std::wstring& str) noexcept
         {
             if (!str.empty() && str.back() == L'\0')
             {
@@ -501,9 +504,9 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
             }
         }
 
-        void parse(wchar_t* block)
+        void parse(const wchar_t* lastCh)
         {
-            for (wchar_t const* lastCh{ block }; *lastCh != '\0'; ++lastCh)
+            for (; *lastCh != '\0'; ++lastCh)
             {
                 // Copy current entry into temporary map.
                 const size_t cchEntry{ ::wcslen(lastCh) };
@@ -523,11 +526,9 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
         }
 
     public:
-        env()
-        {
-        }
+        env() = default;
 
-        env(wchar_t* block)
+        env(const wchar_t* block)
         {
             parse(block);
         }
@@ -535,20 +536,20 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
         void regenerate()
         {
             // Generally replicates the behavior of shell32!RegenerateUserEnvironment
-            get(std::wstring{ til::details::vars::system_root });
-            get(std::wstring{ til::details::vars::system_drive });
-            get(std::wstring{ til::details::vars::all_users_profile });
-            get(std::wstring{ til::details::vars::public_var });
-            get(std::wstring{ til::details::vars::program_data });
+            get(til::details::vars::system_root);
+            get(til::details::vars::system_drive);
+            get(til::details::vars::all_users_profile);
+            get(til::details::vars::public_var);
+            get(til::details::vars::program_data);
             get_computer_name();
             get_user_name_and_domain();
-            get(std::wstring{ til::details::vars::user_dns_domain });
-            get(std::wstring{ til::details::vars::home_drive });
-            get(std::wstring{ til::details::vars::home_share });
-            get(std::wstring{ til::details::vars::home_path });
-            get(std::wstring{ til::details::vars::user_profile });
-            get(std::wstring{ til::details::vars::app_data });
-            get(std::wstring{ til::details::vars::local_app_data });
+            get(til::details::vars::user_dns_domain);
+            get(til::details::vars::home_drive);
+            get(til::details::vars::home_share);
+            get(til::details::vars::home_path);
+            get(til::details::vars::user_profile);
+            get(til::details::vars::app_data);
+            get(til::details::vars::local_app_data);
             get_program_files();
             get_vars_from_registry(HKEY_LOCAL_MACHINE, til::details::vars::reg::system_env_var_root);
             // not processing autoexec.bat
@@ -571,9 +572,11 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
             return result;
         }
 
-        std::map<std::wstring, std::wstring, til::details::wstring_case_insensitive_compare>& as_map()
+        auto& as_map() noexcept
         {
             return _envMap;
         }
     };
 };
+
+#pragma warning(pop)


Test patch: