Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support environment variables in the settings #15082

Merged

Conversation

ianjoneill
Copy link
Contributor

@ianjoneill ianjoneill commented Apr 1, 2023

Existing environment variables can be referenced by enclosing the name in percent characters (e.g. %PATH%).

Resurrects #9287 by @christapley.

Tests added and manually tested.

Closes #2785
Closes #9233

Co-authored-by: Chris Tapley chris.tapley.81@gmail.com

@microsoft-github-policy-service microsoft-github-policy-service bot added Issue-Feature Complex enough to require an in depth planning process and actual budgeted, scheduled work. Area-Settings Issues related to settings and customizability, for console or terminal Area-TerminalConnection Issues pertaining to the terminal<->backend connection interface Product-Terminal The new Windows Terminal. labels Apr 1, 2023
@zadjii-msft
Copy link
Member

holy shit the madman actually did it

@ianjoneill
Copy link
Contributor Author

Test failures are CI flakes

image

@zadjii-msft
Copy link
Member

idle thought before reading the code: the original PR might predate layering. Lets see how this works with layering. Can the defaults say (for example) TERM=windows-terminal, and then a profile say TERM=, to unset it? Or does setting it in a profile entirely replace the lower layer? If it's a per-variable layering, we could use that to vaguely do #11057.

@ianjoneill
Copy link
Contributor Author

Currently the concrete profile's environment completely replaces the one specified in defaults.

So if you've got:

"defaults": {
  "environment": {
    "Foo": "Bar"
  }
},
"list": [
  {
    "name": "PowerShell",
    "environment": {
      "Baz": "Qux"
    }
  }
]

Your PowerShell profile will get Baz=Qux and your cmd profile will get Foo=Bar.

@zadjii-msft zadjii-msft added the Needs-Discussion Something that requires a team discussion before we can proceed label Apr 3, 2023
@microsoft-github-policy-service microsoft-github-policy-service bot added the Priority-3 A description (P3) label Apr 3, 2023
@zadjii-msft
Copy link
Member

Some discussion notes: We're all happy with it being non-layering for now. Maybe in the future we can re-evaluate that, but this is much better than what we had before.

Comment on lines 86 to 95
til::env environment;
auto zeroEnvMap = wil::scope_exit([&]() noexcept {
// Can't zero the keys, but at least we can zero the values.
for (auto& [name, value] : environment)
for (auto& [name, value] : environment.as_map())
{
::SecureZeroMemory(value.data(), value.size() * sizeof(decltype(value.begin())::value_type));
}

environment.clear();
environment.as_map().clear();
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should til::env just do this clearing as a part of regenerate? Is this even needed anymore now that it's just a til::env?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I'm missing something, if we did this as part of regenerate() the map would be cleared on exit of the method which would mean the map would be populated and immediately cleared.

It would probably make sense to extract this for loop into a clear() method on til::env, so that the caller doesn't need to worry about the internal representation.

With regards to whether it's necessary, I can't answer that - I don't know why the code originally did this. It's possible the environment could contain sensitive information (e.g. temporary credentials), which you may want to clear out.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've refactored this into a clear() method in til::env.

Copy link
Member

@DHowett DHowett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bunch of notes. thanks for bringing this back from the dead @ianjoneill :)

@@ -288,6 +289,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
}
_reloadEnvironmentVariables = winrt::unbox_value_or<bool>(settings.TryLookup(L"reloadEnvironmentVariables").try_as<Windows::Foundation::IPropertyValue>(),
_reloadEnvironmentVariables);
_profileGuid = winrt::unbox_value_or<winrt::guid>(settings.TryLookup(L"profileGuid").try_as<Windows::Foundation::IPropertyValue>(), _profileGuid);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in an ideal world (which is not the world in which we live), the Connection classes would not have any concept of a "profile". Even so, I'm cool with this since we've committed so many other layering violations in the past five years.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't need to do anything about this, i just felt like I had a duty to say it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah - it seemed silly both this and TerminalPage doing the same dance fiddling with WSLENV though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmmm... now that we have the WSLENV dance happening over all provided environment variables in ConptyConnection, do you think that we could get away with moving profile GUID up to TerminalPage?

It could become annoying because now we need to copy the user's provided environment map (if there is one! but we do have to copy it because we don't want to edit it inplace since the one living on the profile is durable)... but after all, if TPage provides WT_PROFILE_ID via the environment map it will automatically show up in WSLENV

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that depends on if we think people will try and override that environment variable.

The keys in the IMap are case sensitive - so if someone has added say wt_profile_id as an environment variable, that'll be stored separately - so back in ConptyConnection one of the two values will be put into the connection's environment.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair!
We can fix the interface later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One potential solution would be to pass two environment variable maps to ConptyConnection - one containing user variables and one containing "terminal" variables. We'd insert the "terminal" ones into the environment first, and then the user ones. I can implement that as a follow-up if you're happy with that approach (and please say if you have a better name for "terminal" variables too!).

src/cascadia/TerminalSettingsModel/JsonUtils.h Outdated Show resolved Hide resolved
src/cascadia/TerminalSettingsModel/JsonUtils.h Outdated Show resolved Hide resolved
src/cascadia/TerminalSettingsModel/JsonUtils.h Outdated Show resolved Hide resolved
src/cascadia/TerminalSettingsModel/JsonUtils.h Outdated Show resolved Hide resolved
src/cascadia/TerminalSettingsModel/Profile.idl Outdated Show resolved Hide resolved
src/cascadia/TerminalSettingsModel/TerminalSettings.idl Outdated Show resolved Hide resolved
src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp Outdated Show resolved Hide resolved
src/cascadia/TerminalConnection/ConptyConnection.cpp Outdated Show resolved Hide resolved
src/cascadia/TerminalConnection/ConptyConnection.cpp Outdated Show resolved Hide resolved
@microsoft-github-policy-service microsoft-github-policy-service bot added the Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something label Apr 4, 2023
Copy link
Contributor Author

@ianjoneill ianjoneill left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some thoughts before I open Visual Studio

Comment on lines 86 to 95
til::env environment;
auto zeroEnvMap = wil::scope_exit([&]() noexcept {
// Can't zero the keys, but at least we can zero the values.
for (auto& [name, value] : environment)
for (auto& [name, value] : environment.as_map())
{
::SecureZeroMemory(value.data(), value.size() * sizeof(decltype(value.begin())::value_type));
}

environment.clear();
environment.as_map().clear();
});
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I'm missing something, if we did this as part of regenerate() the map would be cleared on exit of the method which would mean the map would be populated and immediately cleared.

It would probably make sense to extract this for loop into a clear() method on til::env, so that the caller doesn't need to worry about the internal representation.

With regards to whether it's necessary, I can't answer that - I don't know why the code originally did this. It's possible the environment could contain sensitive information (e.g. temporary credentials), which you may want to clear out.

src/cascadia/TerminalConnection/ConptyConnection.cpp Outdated Show resolved Hide resolved
@@ -288,6 +289,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
}
_reloadEnvironmentVariables = winrt::unbox_value_or<bool>(settings.TryLookup(L"reloadEnvironmentVariables").try_as<Windows::Foundation::IPropertyValue>(),
_reloadEnvironmentVariables);
_profileGuid = winrt::unbox_value_or<winrt::guid>(settings.TryLookup(L"profileGuid").try_as<Windows::Foundation::IPropertyValue>(), _profileGuid);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah - it seemed silly both this and TerminalPage doing the same dance fiddling with WSLENV though.

@microsoft-github-policy-service microsoft-github-policy-service bot removed the Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something label Apr 4, 2023
@zadjii-msft zadjii-msft added this to the Terminal v1.18 milestone Apr 4, 2023
@zadjii-msft zadjii-msft self-assigned this Apr 6, 2023
@ianjoneill
Copy link
Contributor Author

Just FYI I should be able to take a look at this tomorrow (Friday).

@DHowett
Copy link
Member

DHowett commented Apr 7, 2023

This is very subtle (and I am only testing as of >5 hours ago, so please ignore if changed), but you can resurrect inherited environment variables depending on sort order.

I don't know if I think this is an issue worth fixing, but it is fun!

"environment": {
    "Z_NEW_PATH": "%PATH%", // gets new path!
    "PATH": "A",
    "0_OLD_PATH": "%PATH%", // gets old path!
}

@ianjoneill
Copy link
Contributor Author

This is seemingly how Windows resolves environment variables - though the order as far as I can tell is the order they're inserted into the registry.

Starting off with a system environment variable FOO=Foo and setting up the registry with REG_EXPAND_SZ user variables 0_FOO=%FOO%, FOO=NewFoo and Z_FOO=%FOO%, as below:
image

Leads to them being resolved as 0_FOO=Foo, FOO=NewFoo and Z_FOO=NewFoo in a conhost window, as below:
image

Worth pointing out though that if you create the FOO user variable in the system properties window, it's set up as a REG_SZ variable - so it does get resolved first, leading to 0_FOO and Z_FOO both resolving to NewFoo.

I guess we could put in a pre-processing step that inserts variables with values not containing percent characters into the environment map first. It would require iterating over the values in the map twice though and would complicate the logic somewhat - your call.

@DHowett
Copy link
Member

DHowett commented Apr 7, 2023

Oh, no, I'm totally happy with how it works today! I legitimately found it interesting 😄

@zadjii-msft zadjii-msft removed the Needs-Discussion Something that requires a team discussion before we can proceed label Apr 11, 2023
@zadjii-msft zadjii-msft removed their assignment Apr 11, 2023
Copy link
Member

@DHowett DHowett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love it. Thanks @ianjoneill and @christapley :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Settings Issues related to settings and customizability, for console or terminal Area-TerminalConnection Issues pertaining to the terminal<->backend connection interface Issue-Bug It either shouldn't be doing this or needs an investigation. Issue-Feature Complex enough to require an in depth planning process and actual budgeted, scheduled work. Priority-3 A description (P3) Product-Terminal The new Windows Terminal.
Projects
None yet
4 participants