Repository: microsoft/terminal (language: cpp)

Issue:
Order of environment variables different in WT
<!-- 
🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨

I ACKNOWLEDGE THE FOLLOWING BEFORE PROCEEDING:
1. If I delete this entire template and go my own path, the core team may close my issue without further explanation or engagement.
2. If I list multiple bugs/concerns in this one issue, the core team may close my issue without further explanation or engagement.
3. If I write an issue that has many duplicates, the core team may close my issue without further explanation or engagement (and without necessarily spending time to find the exact duplicate ID number).
4. If I leave the title incomplete when filing the issue, the core team may close my issue without further explanation or engagement.
5. If I file something completely blank in the body, the core team may close my issue without further explanation or engagement.

All good? Then proceed!
-->

<!--
This bug tracker is monitored by Windows Terminal development team and other technical folks.

**Important: When reporting BSODs or security issues, DO NOT attach memory dumps, logs, or traces to Github issues**.
Instead, send dumps/traces to secure@microsoft.com, referencing this GitHub issue.

If this is an application crash, please also provide a Feedback Hub submission link so we can find your diagnostic data on the backend. Use the category "Apps > Windows Terminal (Preview)" and choose "Share My Feedback" after submission to get the link.

Please use this form and describe your issue, concisely but precisely, with as much detail as possible.

-->

# Environment
Microsoft Windows 10 Pro for Workstations
10.0.19042.804 (2009, 20H2)
Windows Terminal Preview
Version: 1.6.10412.0

```none
Windows build number: [run `[Environment]::OSVersion` for powershell, or `ver` for cmd]
Windows Terminal version (if applicable):

Any other software?
```

# Steps to reproduce

<!-- A description of how to trigger this bug. -->

See below.

# Expected behavior

Same behavior whether in WT or not

<!-- A description of what you're expecting, possibly containing screenshots or reference material. -->

# Actual behavior

Behavior different and undesirable when in WT.
<!-- What's actually happening? -->

In my three Windows shells (CMD, Powershell, TCC), when running in WT
1. the environment block is ordered differently, and
2. new environment variables seem to go in the wrong place

Here's a new CMD.EXE running in a Windows console; this is expected behavior.

![image](https://user-images.githubusercontent.com/61856645/108604119-d2372b00-7379-11eb-8afc-8be4a6879d12.png)

And here's a new CMD.EXE running in Windows Terminal.  Note that the variables __VIP (from the user environment) and WT (newly created) are out of place.

![image](https://user-images.githubusercontent.com/61856645/108604182-335efe80-737a-11eb-9055-ad68560f7ee4.png)





Discussion / hints:

Fix patch:
diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json
index 2c62802115a..55a866a05b0 100644
--- a/doc/cascadia/profiles.schema.json
+++ b/doc/cascadia/profiles.schema.json
@@ -2574,6 +2574,13 @@
           "default": false,
           "description": "When true, this profile should always open in an elevated context. If the window isn't running as an Administrator, then a new elevated window will be created."
         },
+        "environment": {
+          "description": "Key-value pairs representing environment variables to set. Environment variable names are not case sensitive. You can reference existing environment variable names by enclosing them in literal percent characters (e.g. %PATH%).",
+          "type": "object",
+          "additionalProperties": {
+            "type": "string"
+          }
+        },
         "experimental.autoMarkPrompts": {
           "default": false,
           "description": "When set to true, prompts will automatically be marked.",
diff --git a/src/cascadia/LocalTests_SettingsModel/ProfileTests.cpp b/src/cascadia/LocalTests_SettingsModel/ProfileTests.cpp
index fdb160fe275..a73b2bec3e4 100644
--- a/src/cascadia/LocalTests_SettingsModel/ProfileTests.cpp
+++ b/src/cascadia/LocalTests_SettingsModel/ProfileTests.cpp
@@ -38,6 +38,9 @@ namespace SettingsModelLocalTests
         TEST_METHOD(LayerProfileProperties);
         TEST_METHOD(LayerProfileIcon);
         TEST_METHOD(LayerProfilesOnArray);
+        TEST_METHOD(ProfileWithEnvVars);
+        TEST_METHOD(ProfileWithEnvVarsSameNameDifferentCases);
+
         TEST_METHOD(DuplicateProfileTest);
         TEST_METHOD(TestGenGuidsForProfiles);
         TEST_METHOD(TestCorrectOldDefaultShellPaths);
@@ -349,6 +352,50 @@ namespace SettingsModelLocalTests
         VERIFY_ARE_NOT_EQUAL(settings->AllProfiles().GetAt(0).Guid(), settings->AllProfiles().GetAt(1).Guid());
     }
 
+    void ProfileTests::ProfileWithEnvVars()
+    {
+        const std::string profileString{ R"({
+            "name": "profile0",
+            "guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
+            "environment": {
+                "VAR_1": "value1",
+                "VAR_2": "value2",
+                "VAR_3": "%VAR_3%;value3"
+            }
+        })" };
+        const auto profile = implementation::Profile::FromJson(VerifyParseSucceeded(profileString));
+        std::vector<IEnvironmentVariableMap> envVarMaps{};
+        envVarMaps.emplace_back(profile->EnvironmentVariables());
+        for (auto& envMap : envVarMaps)
+        {
+            VERIFY_ARE_EQUAL(static_cast<uint32_t>(3), envMap.Size());
+            VERIFY_ARE_EQUAL(L"value1", envMap.Lookup(L"VAR_1"));
+            VERIFY_ARE_EQUAL(L"value2", envMap.Lookup(L"VAR_2"));
+            VERIFY_ARE_EQUAL(L"%VAR_3%;value3", envMap.Lookup(L"VAR_3"));
+        }
+    }
+
+    void ProfileTests::ProfileWithEnvVarsSameNameDifferentCases()
+    {
+        const std::string userSettings{ R"({
+            "profiles": [
+                {
+                    "name": "profile0",
+                    "guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
+                    "environment": {
+                        "FOO": "VALUE",
+                        "Foo": "Value"
+                    }
+                }
+            ]
+        })" };
+        const auto settings = winrt::make_self<implementation::CascadiaSettings>(userSettings);
+        const auto warnings = settings->Warnings();
+        VERIFY_ARE_EQUAL(static_cast<uint32_t>(2), warnings.Size());
+        uint32_t index;
+        VERIFY_IS_TRUE(warnings.IndexOf(SettingsLoadWarnings::InvalidProfileEnvironmentVariables, index));
+    }
+
     void ProfileTests::TestCorrectOldDefaultShellPaths()
     {
         static constexpr std::string_view inboxProfiles{ R"({
diff --git a/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp b/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp
index d4cbd49aafe..ffc7dea07bd 100644
--- a/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp
+++ b/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp
@@ -165,7 +165,13 @@ namespace SettingsModelLocalTests
                 "historySize": 9001,
 
                 "closeOnExit": "graceful",
-                "experimental.retroTerminalEffect": false
+                "experimental.retroTerminalEffect": false,
+                "environment":
+                {
+                    "KEY_1": "VALUE_1",
+                    "KEY_2": "%KEY_1%",
+                    "KEY_3": "%PATH%"
+                }
             })" };
 
         static constexpr std::string_view smallProfileString{ R"(
diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw
index 2d34da2f610..da77d1e87e0 100644
--- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw
+++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw
@@ -282,6 +282,9 @@
     <value>Failed to parse "startupActions".</value>
     <comment>{Locked="\"startupActions\""}</comment>
   </data>
+  <data name="InvalidProfileEnvironmentVariables" xml:space="preserve">
+    <value>Found multiple environment variables with the same name in different cases (lower/upper) - only one value will be used.</value>
+  </data>
   <data name="CmdCommandArgDesc" xml:space="preserve">
     <value>An optional command, with arguments, to be spawned in the new tab or pane</value>
   </data>
diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp
index 9b0abe8904a..23e67a69904 100644
--- a/src/cascadia/TerminalApp/TerminalPage.cpp
+++ b/src/cascadia/TerminalApp/TerminalPage.cpp
@@ -1202,7 +1202,8 @@ namespace winrt::TerminalApp::implementation
                                                                                  nullptr,
                                                                                  settings.InitialRows(),
                                                                                  settings.InitialCols(),
-                                                                                 winrt::guid());
+                                                                                 winrt::guid(),
+                                                                                 profile.Guid());
 
             if constexpr (Feature_VtPassthroughMode::IsEnabled())
             {
@@ -1214,12 +1215,9 @@ namespace winrt::TerminalApp::implementation
 
         else
         {
-            // profile is guaranteed to exist here
-            auto guidWString = Utils::GuidToString(profile.Guid());
-
-            StringMap envMap{};
-            envMap.Insert(L"WT_PROFILE_ID", guidWString);
-            envMap.Insert(L"WSLENV", L"WT_PROFILE_ID");
+            const auto environment = settings.EnvironmentVariables() != nullptr ?
+                                         settings.EnvironmentVariables().GetView() :
+                                         nullptr;
 
             // Update the path to be relative to whatever our CWD is.
             //
@@ -1250,10 +1248,11 @@ namespace winrt::TerminalApp::implementation
             auto valueSet = TerminalConnection::ConptyConnection::CreateSettings(settings.Commandline(),
                                                                                  newWorkingDirectory,
                                                                                  settings.StartingTitle(),
-                                                                                 envMap.GetView(),
+                                                                                 environment,
                                                                                  settings.InitialRows(),
                                                                                  settings.InitialCols(),
-                                                                                 winrt::guid());
+                                                                                 winrt::guid(),
+                                                                                 profile.Guid());
 
             valueSet.Insert(L"passthroughMode", Windows::Foundation::PropertyValue::CreateBoolean(settings.VtPassthrough()));
             valueSet.Insert(L"reloadEnvironmentVariables",
diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp
index dd984c6c126..73968dde720 100644
--- a/src/cascadia/TerminalApp/TerminalWindow.cpp
+++ b/src/cascadia/TerminalApp/TerminalWindow.cpp
@@ -50,6 +50,7 @@ static const std::array settingsLoadWarningsLabels{
     USES_RESOURCE(L"InvalidColorSchemeInCmd"),
     USES_RESOURCE(L"InvalidSplitSize"),
     USES_RESOURCE(L"FailedToParseStartupActions"),
+    USES_RESOURCE(L"InvalidProfileEnvironmentVariables"),
     USES_RESOURCE(L"FailedToParseSubCommands"),
     USES_RESOURCE(L"UnknownTheme"),
     USES_RESOURCE(L"DuplicateRemainingProfilesEntry"),
diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp
index fef88c33628..9e59098985d 100644
--- a/src/cascadia/TerminalConnection/ConptyConnection.cpp
+++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp
@@ -5,12 +5,12 @@
 #include "ConptyConnection.h"
 
 #include <conpty-static.h>
+#include <til/string.h>
 #include <til/env.h>
 #include <winternl.h>
 
 #include "CTerminalHandoff.h"
 #include "LibraryResources.h"
-#include "../../types/inc/Environment.hpp"
 #include "../../types/inc/utils.hpp"
 
 #include "ConptyConnection.g.cpp"
@@ -84,31 +84,19 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
 
         auto cmdline{ wil::ExpandEnvironmentStringsW<std::wstring>(_commandline.c_str()) }; // mutable copy -- required for CreateProcessW
 
-        Utils::EnvironmentVariableMapW environment;
+        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)
-            {
-                ::SecureZeroMemory(value.data(), value.size() * sizeof(decltype(value.begin())::value_type));
-            }
-
             environment.clear();
         });
 
         // Populate the environment map with the current environment.
         if (_reloadEnvironmentVariables)
         {
-            til::env refreshedEnvironment;
-            refreshedEnvironment.regenerate();
-
-            for (auto& [key, value] : refreshedEnvironment.as_map())
-            {
-                environment.try_emplace(key, std::move(value));
-            }
+            environment.regenerate();
         }
         else
         {
-            RETURN_IF_FAILED(Utils::UpdateEnvironmentMapW(environment));
+            environment = til::env::from_current_environment();
         }
 
         {
@@ -119,39 +107,45 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
             const auto guidSubStr = std::wstring_view{ wsGuid }.substr(1);
 
             // Ensure every connection has the unique identifier in the environment.
-            environment.insert_or_assign(L"WT_SESSION", guidSubStr.data());
+            environment.as_map().insert_or_assign(L"WT_SESSION", guidSubStr.data());
 
+            // The profile Guid does include the enclosing '{}'
+            const auto profileGuid{ Utils::GuidToString(_profileGuid) };
+            environment.as_map().insert_or_assign(L"WT_PROFILE_ID", profileGuid.data());
+
+            // WSLENV is a colon-delimited list of environment variables (+flags) that should appear inside WSL
+            // https://devblogs.microsoft.com/commandline/share-environment-vars-between-wsl-and-windows/
+            std::wstring wslEnv{ L"WT_SESSION:WT_PROFILE_ID:" };
             if (_environment)
             {
-                // add additional WT env vars like WT_SETTINGS, WT_DEFAULTS and WT_PROFILE_ID
-                for (auto item : _environment)
+                // Order the environment variable names so that resolution order is consistent
+                std::set<std::wstring, til::wstring_case_insensitive_compare> keys{};
+                for (const auto item : _environment)
+                {
+                    keys.insert(item.Key().c_str());
+                }
+                // add additional env vars
+                for (const auto& key : keys)
                 {
                     try
                     {
-                        auto key = item.Key();
                         // This will throw if the value isn't a string. If that
                         // happens, then just skip this entry.
-                        auto value = winrt::unbox_value<hstring>(item.Value());
-
-                        // avoid clobbering WSLENV
-                        if (std::wstring_view{ key } == L"WSLENV")
-                        {
-                            auto current = environment[L"WSLENV"];
-                            value = current + L":" + value;
-                        }
+                        const auto value = winrt::unbox_value<hstring>(_environment.Lookup(key));
 
-                        environment.insert_or_assign(key.c_str(), value.c_str());
+                        environment.set_user_environment_var(key.c_str(), value.c_str());
+                        // For each environment variable added to the environment, also add it to WSLENV
+                        wslEnv += key + L":";
                     }
                     CATCH_LOG();
                 }
             }
 
-            // WSLENV is a colon-delimited list of environment variables (+flags) that should appear inside WSL
-            // https://devblogs.microsoft.com/commandline/share-environment-vars-between-wsl-and-windows/
-
-            auto wslEnv = environment[L"WSLENV"];
-            wslEnv = L"WT_SESSION:" + wslEnv; // prepend WT_SESSION to make sure it's visible inside WSL.
-            environment.insert_or_assign(L"WSLENV", wslEnv);
+            // We want to prepend new environment variables to WSLENV - that way if a variable already
+            // exists in WSLENV but with a flag, the flag will be respected.
+            // (This behaviour was empirically observed)
+            wslEnv += environment.as_map()[L"WSLENV"];
+            environment.as_map().insert_or_assign(L"WSLENV", wslEnv);
         }
 
         std::vector<wchar_t> newEnvVars;
@@ -160,7 +154,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
                                newEnvVars.size() * sizeof(decltype(newEnvVars.begin())::value_type));
         });
 
-        RETURN_IF_FAILED(Utils::EnvironmentMapToEnvironmentStringsW(environment, newEnvVars));
+        RETURN_IF_FAILED(environment.to_environment_strings_w(newEnvVars));
 
         auto lpEnvironment = newEnvVars.empty() ? nullptr : newEnvVars.data();
 
@@ -244,7 +238,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
                                                                                 const Windows::Foundation::Collections::IMapView<hstring, hstring>& environment,
                                                                                 uint32_t rows,
                                                                                 uint32_t columns,
-                                                                                const winrt::guid& guid)
+                                                                                const winrt::guid& guid,
+                                                                                const winrt::guid& profileGuid)
     {
         Windows::Foundation::Collections::ValueSet vs{};
 
@@ -254,6 +249,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
         vs.Insert(L"initialRows", Windows::Foundation::PropertyValue::CreateUInt32(rows));
         vs.Insert(L"initialCols", Windows::Foundation::PropertyValue::CreateUInt32(columns));
         vs.Insert(L"guid", Windows::Foundation::PropertyValue::CreateGuid(guid));
+        vs.Insert(L"profileGuid", Windows::Foundation::PropertyValue::CreateGuid(profileGuid));
 
         if (environment)
         {
@@ -288,6 +284,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);
         }
 
         if (_guid == guid{})
diff --git a/src/cascadia/TerminalConnection/ConptyConnection.h b/src/cascadia/TerminalConnection/ConptyConnection.h
index 60528dc0463..8ac589d6462 100644
--- a/src/cascadia/TerminalConnection/ConptyConnection.h
+++ b/src/cascadia/TerminalConnection/ConptyConnection.h
@@ -52,7 +52,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
                                                                          const Windows::Foundation::Collections::IMapView<hstring, hstring>& environment,
                                                                          uint32_t rows,
                                                                          uint32_t columns,
-                                                                         const winrt::guid& guid);
+                                                                         const winrt::guid& guid,
+                                                                         const winrt::guid& profileGuid);
 
         WINRT_CALLBACK(TerminalOutput, TerminalOutputHandler);
 
@@ -90,6 +91,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
         std::array<char, 4096> _buffer{};
         bool _passthroughMode{};
         bool _reloadEnvironmentVariables{};
+        guid _profileGuid{};
 
         struct StartupInfoFromDefTerm
         {
diff --git a/src/cascadia/TerminalConnection/ConptyConnection.idl b/src/cascadia/TerminalConnection/ConptyConnection.idl
index 135a2fd2fbe..c9928d5c425 100644
--- a/src/cascadia/TerminalConnection/ConptyConnection.idl
+++ b/src/cascadia/TerminalConnection/ConptyConnection.idl
@@ -31,6 +31,7 @@ namespace Microsoft.Terminal.TerminalConnection
                                                                       IMapView<String, String> environment,
                                                                       UInt32 rows,
                                                                       UInt32 columns,
-                                                                      Guid guid);
+                                                                      Guid guid,
+                                                                      Guid profileGuid);
     };
 }
diff --git a/src/cascadia/TerminalControl/IControlSettings.idl b/src/cascadia/TerminalControl/IControlSettings.idl
index 1e2f7d71f24..131b9432bd7 100644
--- a/src/cascadia/TerminalControl/IControlSettings.idl
+++ b/src/cascadia/TerminalControl/IControlSettings.idl
@@ -52,7 +52,6 @@ namespace Microsoft.Terminal.Control
 
         String Commandline { get; };
         String StartingDirectory { get; };
-        String EnvironmentVariables { get; };
 
         TextAntialiasingMode AntialiasingMode { get; };
 
diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp
index 8a73dea58a2..851e667ac9a 100644
--- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp
+++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp
@@ -15,6 +15,7 @@
 #include <shellapi.h>
 #include <shlwapi.h>
 #include <til/latch.h>
+#include <til/string.h>
 
 using namespace winrt::Microsoft::Terminal;
 using namespace winrt::Microsoft::Terminal::Settings;
@@ -417,6 +418,7 @@ void CascadiaSettings::_validateSettings()
     _validateKeybindings();
     _validateColorSchemesInCommands();
     _validateThemeExists();
+    _validateProfileEnvironmentVariables();
 }
 
 // Method Description:
@@ -541,6 +543,30 @@ void CascadiaSettings::_validateMediaResources()
     }
 }
 
+// Method Description:
+// - Checks if the profiles contain multiple environment variables with the same name, but different
+//   cases
+void CascadiaSettings::_validateProfileEnvironmentVariables()
+{
+    for (const auto& profile : _allProfiles)
+    {
+        std::set<std::wstring, til::wstring_case_insensitive_compare> envVarNames{};
+        if (profile.EnvironmentVariables() == nullptr)
+        {
+            continue;
+        }
+        for (const auto [key, value] : profile.EnvironmentVariables())
+        {
+            const auto iterator = envVarNames.insert(key.c_str());
+            if (!iterator.second)
+            {
+                _warnings.Append(SettingsLoadWarnings::InvalidProfileEnvironmentVariables);
+                return;
+            }
+        }
+    }
+}
+
 // Method Description:
 // - Helper to get the GUID of a profile, given an optional index and a possible
 //   "profile" value to override that.
diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h
index b41a17c57ee..d4ad396e264 100644
--- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h
+++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h
@@ -162,6 +162,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
         void _validateSettings();
         void _validateAllSchemesExist();
         void _validateMediaResources();
+        void _validateProfileEnvironmentVariables();
         void _validateKeybindings() const;
         void _validateColorSchemesInCommands() const;
         bool _hasInvalidColorScheme(const Model::Command& command) const;
diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h
index 798505de23d..191203caf3f 100644
--- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h
+++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h
@@ -81,6 +81,7 @@ Author(s):
     X(CloseOnExitMode, CloseOnExit, "closeOnExit", CloseOnExitMode::Automatic)                                                                                 \
     X(hstring, TabTitle, "tabTitle")                                                                                                                           \
     X(Model::BellStyle, BellStyle, "bellStyle", BellStyle::Audible)                                                                                            \
+    X(IEnvironmentVariableMap, EnvironmentVariables, "environment", nullptr)                                                                                   \
     X(bool, UseAtlasEngine, "useAtlasEngine", Feature_AtlasEngine::IsEnabled())                                                                                \
     X(bool, RightClickContextMenu, "experimental.rightClickContextMenu", false)                                                                                \
     X(Windows::Foundation::Collections::IVector<winrt::hstring>, BellSound, "bellSound", nullptr)                                                              \
diff --git a/src/cascadia/TerminalSettingsModel/Profile.h b/src/cascadia/TerminalSettingsModel/Profile.h
index dbc33fc9ef5..05bd77cc720 100644
--- a/src/cascadia/TerminalSettingsModel/Profile.h
+++ b/src/cascadia/TerminalSettingsModel/Profile.h
@@ -67,6 +67,8 @@ namespace TerminalAppUnitTests
     class JsonTests;
 };
 
+using IEnvironmentVariableMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::hstring>;
+
 // GUID used for generating GUIDs at runtime, for profiles that did not have a
 // GUID specified manually.
 constexpr GUID RUNTIME_GENERATED_PROFILE_NAMESPACE_GUID = { 0xf65ddb7e, 0x706b, 0x4499, { 0x8a, 0x50, 0x40, 0x31, 0x3c, 0xaf, 0x51, 0x0a } };
diff --git a/src/cascadia/TerminalSettingsModel/Profile.idl b/src/cascadia/TerminalSettingsModel/Profile.idl
index 0f6bf14921a..f6ab00a26c9 100644
--- a/src/cascadia/TerminalSettingsModel/Profile.idl
+++ b/src/cascadia/TerminalSettingsModel/Profile.idl
@@ -9,6 +9,8 @@ import "FontConfig.idl";
     _BASE_INHERITABLE_SETTING(Type, Name);      \
     Microsoft.Terminal.Settings.Model.Profile Name##OverrideSource { get; }
 
+#define COMMA ,
+
 namespace Microsoft.Terminal.Settings.Model
 {
     // This tag is used to identify the context in which the Profile was created
@@ -81,6 +83,9 @@ namespace Microsoft.Terminal.Settings.Model
         INHERITABLE_PROFILE_SETTING(Boolean, SnapOnInput);
         INHERITABLE_PROFILE_SETTING(Boolean, AltGrAliasing);
         INHERITABLE_PROFILE_SETTING(BellStyle, BellStyle);
+
+        INHERITABLE_PROFILE_SETTING(Windows.Foundation.Collections.IMap<String COMMA String>, EnvironmentVariables);
+
         INHERITABLE_PROFILE_SETTING(Boolean, UseAtlasEngine);
         INHERITABLE_PROFILE_SETTING(Windows.Foundation.Collections.IVector<String>, BellSound);
 
diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp
index 091b33e444b..a69ba7086b4 100644
--- a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp
+++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp
@@ -306,6 +306,20 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
             _TabColor = static_cast<winrt::Microsoft::Terminal::Core::Color>(colorRef);
         }
 
+        const auto profileEnvVars = profile.EnvironmentVariables();
+        if (profileEnvVars == nullptr)
+        {
+            _EnvironmentVariables = std::nullopt;
+        }
+        else
+        {
+            _EnvironmentVariables = winrt::single_threaded_map<winrt::hstring, winrt::hstring>();
+            for (const auto& [key, value] : profileEnvVars)
+            {
+                _EnvironmentVariables.value().Insert(key, value);
+            }
+        }
+
         _Elevate = profile.Elevate();
         _AutoMarkPrompts = Feature_ScrollbarMarks::IsEnabled() && profile.AutoMarkPrompts();
         _ShowMarks = Feature_ScrollbarMarks::IsEnabled() && profile.ShowMarks();
diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.h b/src/cascadia/TerminalSettingsModel/TerminalSettings.h
index cb43e24eb00..7df75e1ab5c 100644
--- a/src/cascadia/TerminalSettingsModel/TerminalSettings.h
+++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.h
@@ -22,6 +22,7 @@ Author(s):
 
 using IFontAxesMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, float>;
 using IFontFeatureMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, uint32_t>;
+using IEnvironmentVariableMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::hstring>;
 
 // fwdecl unittest classes
 namespace SettingsModelLocalTests
@@ -143,7 +144,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
         INHERITABLE_SETTING(Model::TerminalSettings, hstring, StartingDirectory);
         INHERITABLE_SETTING(Model::TerminalSettings, hstring, StartingTitle);
         INHERITABLE_SETTING(Model::TerminalSettings, bool, SuppressApplicationTitle);
-        INHERITABLE_SETTING(Model::TerminalSettings, hstring, EnvironmentVariables);
+        INHERITABLE_SETTING(Model::TerminalSettings, IEnvironmentVariableMap, EnvironmentVariables);
 
         INHERITABLE_SETTING(Model::TerminalSettings, Microsoft::Terminal::Control::ScrollbarState, ScrollState, Microsoft::Terminal::Control::ScrollbarState::Visible);
         INHERITABLE_SETTING(Model::TerminalSettings, bool, UseAtlasEngine, false);
diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.idl b/src/cascadia/TerminalSettingsModel/TerminalSettings.idl
index 514fcf7e363..3bc2c72ed0e 100644
--- a/src/cascadia/TerminalSettingsModel/TerminalSettings.idl
+++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.idl
@@ -3,6 +3,8 @@
 
 import "CascadiaSettings.idl";
 
+#define COMMA ,
+
 namespace Microsoft.Terminal.Settings.Model
 {
     runtimeclass TerminalSettingsCreateResult
@@ -26,6 +28,8 @@ namespace Microsoft.Terminal.Settings.Model
     {
         TerminalSettings();
 
+        Windows.Foundation.Collections.IMap<String COMMA String> EnvironmentVariables;
+
         static TerminalSettings CreateForPreview(CascadiaSettings appSettings, Profile profile);
         static TerminalSettingsCreateResult CreateWithProfile(CascadiaSettings appSettings, Profile profile, Microsoft.Terminal.Control.IKeyBindings keybindings);
         static TerminalSettingsCreateResult CreateWithNewTerminalArgs(CascadiaSettings appSettings, NewTerminalArgs newTerminalArgs, Microsoft.Terminal.Control.IKeyBindings keybindings);
@@ -39,7 +43,6 @@ namespace Microsoft.Terminal.Settings.Model
         // able to change these at runtime (e.g. when duplicating a pane).
         String Commandline { set; };
         String StartingDirectory { set; };
-        String EnvironmentVariables { set; };
 
         Boolean Elevate;
     };
diff --git a/src/cascadia/TerminalSettingsModel/TerminalWarnings.idl b/src/cascadia/TerminalSettingsModel/TerminalWarnings.idl
index a82acf0d5db..aa02d18ff46 100644
--- a/src/cascadia/TerminalSettingsModel/TerminalWarnings.idl
+++ b/src/cascadia/TerminalSettingsModel/TerminalWarnings.idl
@@ -20,6 +20,7 @@ namespace Microsoft.Terminal.Settings.Model
         InvalidColorSchemeInCmd,
         InvalidSplitSize,
         FailedToParseStartupActions,
+        InvalidProfileEnvironmentVariables,
         FailedToParseSubCommands,
         UnknownTheme,
         DuplicateRemainingProfilesEntry,
diff --git a/src/cascadia/inc/ControlProperties.h b/src/cascadia/inc/ControlProperties.h
index 4d2bec9e209..cccde919a91 100644
--- a/src/cascadia/inc/ControlProperties.h
+++ b/src/cascadia/inc/ControlProperties.h
@@ -66,7 +66,6 @@
     X(winrt::Microsoft::Terminal::Control::IKeyBindings, KeyBindings, nullptr)                                                                           \
     X(winrt::hstring, Commandline)                                                                                                                       \
     X(winrt::hstring, StartingDirectory)                                                                                                                 \
-    X(winrt::hstring, EnvironmentVariables)                                                                                                              \
     X(winrt::Microsoft::Terminal::Control::ScrollbarState, ScrollState, winrt::Microsoft::Terminal::Control::ScrollbarState::Visible)                    \
     X(winrt::Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, winrt::Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale) \
     X(bool, ForceFullRepaintRendering, false)                                                                                                            \
diff --git a/src/inc/til/env.h b/src/inc/til/env.h
index 72e626a8da8..1ed0ada927f 100644
--- a/src/inc/til/env.h
+++ b/src/inc/til/env.h
@@ -5,6 +5,7 @@
 
 #include <wil/token_helpers.h>
 #include <winternl.h>
+#include <til/string.h>
 
 #pragma warning(push)
 #pragma warning(disable : 26429) // Symbol '...' is never tested for nullness, it can be marked as not_null (f.23).
@@ -21,37 +22,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
     namespace details
     {
 
-        //
-        // A case-insensitive wide-character map is used to store environment variables
-        // due to documented requirements:
-        //
-        //      "All strings in the environment block must be sorted alphabetically by name.
-        //      The sort is case-insensitive, Unicode order, without regard to locale.
-        //      Because the equal sign is a separator, it must not be used in the name of
-        //      an environment variable."
-        //      https://docs.microsoft.com/en-us/windows/desktop/ProcThread/changing-environment-variables
-        //
-        // - Returns CSTR_LESS_THAN, CSTR_EQUAL or CSTR_GREATER_THAN
-        [[nodiscard]] inline int compare_string_ordinal(const std::wstring_view& lhs, const std::wstring_view& rhs) noexcept
-        {
-            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;
-        }
-
-        struct wstring_case_insensitive_compare
-        {
-            [[nodiscard]] bool operator()(const std::wstring& lhs, const std::wstring& rhs) const noexcept
-            {
-                return compare_string_ordinal(lhs, rhs) == CSTR_LESS_THAN;
-            }
-        };
-
         namespace vars
         {
             inline constexpr wil::zwstring_view system_root{ L"SystemRoot" };
@@ -250,7 +220,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
         friend class ::EnvTests;
 #endif
 
-        std::map<std::wstring, std::wstring, til::details::wstring_case_insensitive_compare> _envMap{};
+        std::map<std::wstring, std::wstring, til::wstring_case_insensitive_compare> _envMap{};
 
         // We make copies of the environment variable names to ensure they are null terminated.
         void get(wil::zwstring_view variable)
@@ -438,13 +408,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
             return expanded;
         }
 
-        void set_user_environment_var(std::wstring_view var, std::wstring_view value)
-        {
-            auto valueString = expand_environment_strings(value);
-            valueString = check_for_temp(var, valueString);
-            save_to_map(std::wstring{ var }, std::move(valueString));
-        }
-
         void concat_var(std::wstring var, std::wstring value)
         {
             if (const auto existing = _envMap.find(var); existing != _envMap.end())
@@ -475,8 +438,8 @@ 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" };
-            if (til::details::compare_string_ordinal(var, temp) == CSTR_EQUAL ||
-                til::details::compare_string_ordinal(var, tmp) == CSTR_EQUAL)
+            if (til::compare_string_ordinal(var, temp) == CSTR_EQUAL ||
+                til::compare_string_ordinal(var, tmp) == CSTR_EQUAL)
             {
                 return til::details::wil_env::GetShortPathNameW<std::wstring, 256>(value.data());
             }
@@ -491,9 +454,9 @@ 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" };
-            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;
+            return til::compare_string_ordinal(input, path) == CSTR_EQUAL ||
+                   til::compare_string_ordinal(input, libPath) == CSTR_EQUAL ||
+                   til::compare_string_ordinal(input, os2LibPath) == CSTR_EQUAL;
         }
 
         static void strip_trailing_null(std::wstring& str) noexcept
@@ -533,6 +496,35 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
             parse(block);
         }
 
+        // Function Description:
+        // - Creates a new environment with the current process's unicode environment
+        //   variables.
+        // Return Value:
+        // - A new environment
+        static til::env from_current_environment()
+        {
+            LPWCH currentEnvVars{};
+            auto freeCurrentEnv = wil::scope_exit([&] {
+                if (currentEnvVars)
+                {
+                    FreeEnvironmentStringsW(currentEnvVars);
+                    currentEnvVars = nullptr;
+                }
+            });
+
+            currentEnvVars = ::GetEnvironmentStringsW();
+            THROW_HR_IF_NULL(E_OUTOFMEMORY, currentEnvVars);
+
+            return til::env{ currentEnvVars };
+        }
+
+        void set_user_environment_var(std::wstring_view var, std::wstring_view value)
+        {
+            auto valueString = expand_environment_strings(value);
+            valueString = check_for_temp(var, valueString);
+            save_to_map(std::wstring{ var }, std::move(valueString));
+        }
+
         void regenerate()
         {
             // Generally replicates the behavior of shell32!RegenerateUserEnvironment
@@ -572,6 +564,93 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
             return result;
         }
 
+        void clear() noexcept
+        {
+            // Can't zero the keys, but at least we can zero the values.
+            for (auto& [name, value] : _envMap)
+            {
+                ::SecureZeroMemory(value.data(), value.size() * sizeof(decltype(value.begin())::value_type));
+            }
+
+            _envMap.clear();
+        }
+
+        // Function Description:
+        // - Creates a new environment block using the provided vector as appropriate
+        //   (resizing if needed) based on the current environment variable map
+        //   matching the format of GetEnvironmentStringsW.
+        // Arguments:
+        // - newEnvVars: The vector that will be used to create the new environment block.
+        // Return Value:
+        // - S_OK if we succeeded, or an appropriate HRESULT for failing
+        HRESULT to_environment_strings_w(std::vector<wchar_t>& newEnvVars)
+        try
+        {
+            // Clear environment block before use.
+            constexpr auto cbChar{ sizeof(decltype(newEnvVars.begin())::value_type) };
+
+            if (!newEnvVars.empty())
+            {
+                ::SecureZeroMemory(newEnvVars.data(), newEnvVars.size() * cbChar);
+            }
+
+            // Resize environment block to fit map.
+            size_t cchEnv{ 2 }; // For the block's double NULL-terminator.
+            for (const auto& [name, value] : _envMap)
+            {
+                // Final form of "name=value\0".
+                cchEnv += name.size() + 1 + value.size() + 1;
+            }
+            newEnvVars.resize(cchEnv);
+
+            // Ensure new block is wiped if we exit due to failure.
+            auto zeroNewEnv = wil::scope_exit([&]() noexcept {
+                ::SecureZeroMemory(newEnvVars.data(), newEnvVars.size() * cbChar);
+            });
+
+            // Transform each map entry and copy it into the new environment block.
+            auto pEnvVars{ newEnvVars.data() };
+            auto cbRemaining{ cchEnv * cbChar };
+            for (const auto& [name, value] : _envMap)
+            {
+                // Final form of "name=value\0" for every entry.
+                {
+                    const auto cchSrc{ name.size() };
+                    const auto cbSrc{ cchSrc * cbChar };
+                    RETURN_HR_IF(E_OUTOFMEMORY, memcpy_s(pEnvVars, cbRemaining, name.c_str(), cbSrc) != 0);
+                    pEnvVars += cchSrc;
+                    cbRemaining -= cbSrc;
+                }
+
+                RETURN_HR_IF(E_OUTOFMEMORY, memcpy_s(pEnvVars, cbRemaining, L"=", cbChar) != 0);
+                ++pEnvVars;
+                cbRemaining -= cbChar;
+
+                {
+                    const auto cchSrc{ value.size() };
+                    const auto cbSrc{ cchSrc * cbChar };
+                    RETURN_HR_IF(E_OUTOFMEMORY, memcpy_s(pEnvVars, cbRemaining, value.c_str(), cbSrc) != 0);
+                    pEnvVars += cchSrc;
+                    cbRemaining -= cbSrc;
+                }
+
+                RETURN_HR_IF(E_OUTOFMEMORY, memcpy_s(pEnvVars, cbRemaining, L"\0", cbChar) != 0);
+                ++pEnvVars;
+                cbRemaining -= cbChar;
+            }
+
+            // Environment block only has to be NULL-terminated, but double NULL-terminate anyway.
+            RETURN_HR_IF(E_OUTOFMEMORY, memcpy_s(pEnvVars, cbRemaining, L"\0\0", cbChar * 2) != 0);
+            cbRemaining -= cbChar * 2;
+
+            RETURN_HR_IF(E_UNEXPECTED, cbRemaining != 0);
+
+            zeroNewEnv.release(); // success; don't wipe new environment block on exit
+
+            return S_OK;
+        }
+        CATCH_RETURN();
+
         auto& as_map() noexcept
         {
             return _envMap;
diff --git a/src/inc/til/string.h b/src/inc/til/string.h
index c6cfb28ce72..618c692a234 100644
--- a/src/inc/til/string.h
+++ b/src/inc/til/string.h
@@ -342,4 +342,35 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
     {
         return prefix_split<>(str, needle);
     }
+
+    //
+    // A case-insensitive wide-character map is used to store environment variables
+    // due to documented requirements:
+    //
+    //      "All strings in the environment block must be sorted alphabetically by name.
+    //      The sort is case-insensitive, Unicode order, without regard to locale.
+    //      Because the equal sign is a separator, it must not be used in the name of
+    //      an environment variable."
+    //      https://docs.microsoft.com/en-us/windows/desktop/ProcThread/changing-environment-variables
+    //
+    // - Returns CSTR_LESS_THAN, CSTR_EQUAL or CSTR_GREATER_THAN
+    [[nodiscard]] inline int compare_string_ordinal(const std::wstring_view& lhs, const std::wstring_view& rhs) noexcept
+    {
+        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;
+    }
+
+    struct wstring_case_insensitive_compare
+    {
+        [[nodiscard]] bool operator()(const std::wstring& lhs, const std::wstring& rhs) const noexcept
+        {
+            return compare_string_ordinal(lhs, rhs) == CSTR_LESS_THAN;
+        }
+    };
 }
diff --git a/src/types/Environment.cpp b/src/types/Environment.cpp
deleted file mode 100644
index 9e6a1118580..00000000000
--- a/src/types/Environment.cpp
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT license.
-
-#include "precomp.h"
-#include "inc/Environment.hpp"
-
-using namespace ::Microsoft::Console::Utils;
-
-// We cannot use spand or not_null because we're dealing with \0\0-terminated buffers of unknown length
-#pragma warning(disable : 26481 26429)
-
-// Function Description:
-// - Updates an EnvironmentVariableMapW with the current process's unicode
-//   environment variables ignoring ones already set in the provided map.
-// Arguments:
-// - map: The map to populate with the current processes's environment variables.
-// Return Value:
-// - S_OK if we succeeded, or an appropriate HRESULT for failing
-HRESULT Microsoft::Console::Utils::UpdateEnvironmentMapW(EnvironmentVariableMapW& map) noexcept
-try
-{
-    LPWCH currentEnvVars{};
-    auto freeCurrentEnv = wil::scope_exit([&] {
-        if (currentEnvVars)
-        {
-            FreeEnvironmentStringsW(currentEnvVars);
-            currentEnvVars = nullptr;
-        }
-    });
-
-    currentEnvVars = ::GetEnvironmentStringsW();
-    RETURN_HR_IF_NULL(E_OUTOFMEMORY, currentEnvVars);
-
-    // Each entry is NULL-terminated; block is guaranteed to be double-NULL terminated at a minimum.
-    for (const wchar_t* lastCh{ currentEnvVars }; *lastCh != '\0'; ++lastCh)
-    {
-        // Copy current entry into temporary map.
-        const auto cchEntry{ ::wcslen(lastCh) };
-        const std::wstring_view entry{ lastCh, cchEntry };
-
-        // Every entry is of the form "name=value\0".
-        const auto pos = entry.find_first_of(L"=", 0, 1);
-        RETURN_HR_IF(E_UNEXPECTED, pos == std::wstring::npos);
-
-        std::wstring name{ entry.substr(0, pos) }; // portion before '='
-        std::wstring value{ entry.substr(pos + 1) }; // portion after '='
-
-        // Don't replace entries that already exist.
-        map.try_emplace(std::move(name), std::move(value));
-        lastCh += cchEntry;
-    }
-
-    return S_OK;
-}
-CATCH_RETURN();
-
-// Function Description:
-// - Creates a new environment block using the provided vector as appropriate
-//   (resizing if needed) based on the provided environment variable map
-//   matching the format of GetEnvironmentStringsW.
-// Arguments:
-// - map: The map to populate the new environment block vector with.
-// - newEnvVars: The vector that will be used to create the new environment block.
-// Return Value:
-// - S_OK if we succeeded, or an appropriate HRESULT for failing
-HRESULT Microsoft::Console::Utils::EnvironmentMapToEnvironmentStringsW(EnvironmentVariableMapW& map, std::vector<wchar_t>& newEnvVars) noexcept
-try
-{
-    // Clear environment block before use.
-    constexpr auto cbChar{ sizeof(decltype(newEnvVars.begin())::value_type) };
-
-    if (!newEnvVars.empty())
-    {
-        ::SecureZeroMemory(newEnvVars.data(), newEnvVars.size() * cbChar);
-    }
-
-    // Resize environment block to fit map.
-    size_t cchEnv{ 2 }; // For the block's double NULL-terminator.
-    for (const auto& [name, value] : map)
-    {
-        // Final form of "name=value\0".
-        cchEnv += name.size() + 1 + value.size() + 1;
-    }
-    newEnvVars.resize(cchEnv);
-
-    // Ensure new block is wiped if we exit due to failure.
-    auto zeroNewEnv = wil::scope_exit([&]() noexcept {
-        ::SecureZeroMemory(newEnvVars.data(), newEnvVars.size() * cbChar);
-    });
-
-    // Transform each map entry and copy it into the new environment block.
-    auto pEnvVars{ newEnvVars.data() };
-    auto cbRemaining{ cchEnv * cbChar };
-    for (const auto& [name, value] : map)
-    {
-        // Final form of "name=value\0" for every entry.
-        {
-            const auto cchSrc{ name.size() };
-            const auto cbSrc{ cchSrc * cbChar };
-            RETURN_HR_IF(E_OUTOFMEMORY, memcpy_s(pEnvVars, cbRemaining, name.c_str(), cbSrc) != 0);
-            pEnvVars += cchSrc;
-            cbRemaining -= cbSrc;
-        }
-
-        RETURN_HR_IF(E_OUTOFMEMORY, memcpy_s(pEnvVars, cbRemaining, L"=", cbChar) != 0);
-        ++pEnvVars;
-        cbRemaining -= cbChar;
-
-        {
-            const auto cchSrc{ value.size() };
-            const auto cbSrc{ cchSrc * cbChar };
-            RETURN_HR_IF(E_OUTOFMEMORY, memcpy_s(pEnvVars, cbRemaining, value.c_str(), cbSrc) != 0);
-            pEnvVars += cchSrc;
-            cbRemaining -= cbSrc;
-        }
-
-        RETURN_HR_IF(E_OUTOFMEMORY, memcpy_s(pEnvVars, cbRemaining, L"\0", cbChar) != 0);
-        ++pEnvVars;
-        cbRemaining -= cbChar;
-    }
-
-    // Environment block only has to be NULL-terminated, but double NULL-terminate anyway.
-    RETURN_HR_IF(E_OUTOFMEMORY, memcpy_s(pEnvVars, cbRemaining, L"\0\0", cbChar * 2) != 0);
-    cbRemaining -= cbChar * 2;
-
-    RETURN_HR_IF(E_UNEXPECTED, cbRemaining != 0);
-
-    zeroNewEnv.release(); // success; don't wipe new environment block on exit
-
-    return S_OK;
-}
-CATCH_RETURN();
diff --git a/src/types/inc/Environment.hpp b/src/types/inc/Environment.hpp
deleted file mode 100644
index 0994a177762..00000000000
--- a/src/types/inc/Environment.hpp
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT license.
-
-namespace Microsoft::Console::Utils
-{
-    //
-    // A case-insensitive wide-character map is used to store environment variables
-    // due to documented requirements:
-    //
-    //      "All strings in the environment block must be sorted alphabetically by name.
-    //      The sort is case-insensitive, Unicode order, without regard to locale.
-    //      Because the equal sign is a separator, it must not be used in the name of
-    //      an environment variable."
-    //      https://docs.microsoft.com/en-us/windows/desktop/ProcThread/changing-environment-variables
-    //
-    struct WStringCaseInsensitiveCompare
-    {
-        [[nodiscard]] bool operator()(const std::wstring& lhs, const std::wstring& rhs) const noexcept
-        {
-            return (::_wcsicmp(lhs.c_str(), rhs.c_str()) < 0);
-        }
-    };
-
-    using EnvironmentVariableMapW = std::map<std::wstring, std::wstring, WStringCaseInsensitiveCompare>;
-
-    [[nodiscard]] HRESULT UpdateEnvironmentMapW(EnvironmentVariableMapW& map) noexcept;
-
-    [[nodiscard]] HRESULT EnvironmentMapToEnvironmentStringsW(EnvironmentVariableMapW& map,
-                                                              std::vector<wchar_t>& newEnvVars) noexcept;
-
-};
diff --git a/src/types/lib/types.vcxproj b/src/types/lib/types.vcxproj
index 9d015dff9bc..bceb8348e8c 100644
--- a/src/types/lib/types.vcxproj
+++ b/src/types/lib/types.vcxproj
@@ -15,7 +15,6 @@
     <ClCompile Include="..\ColorFix.cpp" />
     <ClCompile Include="..\convert.cpp" />
     <ClCompile Include="..\colorTable.cpp" />
-    <ClCompile Include="..\Environment.cpp" />
     <ClCompile Include="..\GlyphWidth.cpp" />
     <ClCompile Include="..\MouseEvent.cpp" />
     <ClCompile Include="..\FocusEvent.cpp" />
@@ -43,7 +42,6 @@
     <ClInclude Include="..\inc\ColorFix.hpp" />
     <ClInclude Include="..\inc\convert.hpp" />
     <ClInclude Include="..\inc\colorTable.hpp" />
-    <ClInclude Include="..\inc\Environment.hpp" />
     <ClInclude Include="..\inc\GlyphWidth.hpp" />
     <ClInclude Include="..\inc\IInputEvent.hpp" />
     <ClInclude Include="..\inc\sgrStack.hpp" />


Test patch: