Repository: microsoft/terminal (language: cpp)

Issue:
Provide an easy way to disable / remove Azure Cloud Shell from Terminal
<!-- 
🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨

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!
-->

# Description of the new feature/enhancement

There are enterprise customers need to disable Azure Cloud Shell for their whole environment. At the moment the only known way is to use a json file. This is user specific and has to be created for each user in his profile. That is not a possible way for an enterprise environment. This feature request will be for an easy way to disable Azure Cloud Shell using a Regkey or a GPO. 


# Proposed technical implementation details (optional)

When a specific GPO or regkey is set, when you open terminal and choose the arrow down menu, there is no option to start an Azure Cloud Shell. Also the keyboard short cut should be disabled (ctrl+shift+3)





Discussion / hints:
Thanks for the request! We definitely need better enterprise manageability for Terminal profiles (as well as sources, colors, themes, VT capabilities, ...).

In the meantime:

> This is user specific

You can also install a machine-wide [fragment extension](https://learn.microsoft.com/en-us/windows/terminal/json-fragment-extensions) that overrides the `hidden` property for the Azure Cloud Shell profile. That will affect removing it on all of your managed systems, and is not per-user.

Fix patch:
diff --git a/.github/actions/spelling/expect/04cdb9b77d6827c0202f51acd4205b017015bfff.txt b/.github/actions/spelling/expect/04cdb9b77d6827c0202f51acd4205b017015bfff.txt
deleted file mode 100644
index f117f5081da..00000000000
--- a/.github/actions/spelling/expect/04cdb9b77d6827c0202f51acd4205b017015bfff.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-EOB
-swrapped
-wordi
-wordiswrapped
-wrappe
diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt
index 3a6ca5cf14e..5023e7274fa 100644
--- a/.github/actions/spelling/expect/expect.txt
+++ b/.github/actions/spelling/expect/expect.txt
@@ -16,6 +16,8 @@ ADDALIAS
 ADDREF
 ADDSTRING
 ADDTOOL
+adml
+admx
 AFill
 AFX
 AHelper
@@ -564,6 +566,7 @@ entrypoints
 ENU
 ENUMLOGFONT
 ENUMLOGFONTEX
+EOB
 EOK
 EPres
 EQU
@@ -782,6 +785,7 @@ HIWORD
 HKCU
 hkey
 hkl
+HKLM
 hlocal
 hlsl
 HMB
@@ -1735,6 +1739,7 @@ swapchain
 swapchainpanel
 SWMR
 SWP
+swrapped
 SYMED
 SYNCPAINT
 syscalls
@@ -2077,6 +2082,8 @@ WNDCLASSW
 Wndproc
 WNegative
 WNull
+wordi
+wordiswrapped
 workarea
 WOutside
 WOWARM
@@ -2090,6 +2097,7 @@ WPrep
 WPresent
 wprp
 wprpi
+wrappe
 wregex
 writeback
 WRITECONSOLE
diff --git a/policies/WindowsTerminal.admx b/policies/WindowsTerminal.admx
new file mode 100644
index 00000000000..48c36aec7eb
--- /dev/null
+++ b/policies/WindowsTerminal.admx
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--  (c) 2024 Microsoft Corporation  -->
+<policyDefinitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.0" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
+  <policyNamespaces>
+    <target prefix="terminal" namespace="Microsoft.Policies.WindowsTerminal" />
+    <using prefix="windows" namespace="Microsoft.Policies.Windows" />
+  </policyNamespaces>
+  <resources minRequiredRevision="1.0" />
+  <supportedOn>
+    <definitions>
+      <definition name="SUPPORTED_WindowsTerminal_1_21" displayName="$(string.SUPPORTED_WindowsTerminal_1_21)" />
+    </definitions>
+  </supportedOn>
+  <categories>
+    <category name="WindowsTerminal" displayName="$(string.WindowsTerminal)">
+      <parentCategory ref="windows:WindowsComponents" />
+    </category>
+  </categories>
+  <policies>
+    <policy name="DisabledProfileSources" class="Both" displayName="$(string.DisabledProfileSources)" explainText="$(string.DisabledProfileSourcesText)" presentation="$(presentation.DisabledProfileSources)" key="Software\Policies\Microsoft\Windows Terminal">
+      <parentCategory ref="WindowsTerminal" />
+      <supportedOn ref="SUPPORTED_WindowsTerminal_1_21" />
+      <elements>
+        <multiText id="DisabledProfileSources" valueName="DisabledProfileSources" required="true" />
+      </elements>
+    </policy>
+  </policies>
+</policyDefinitions>
diff --git a/policies/en-US/WindowsTerminal.adml b/policies/en-US/WindowsTerminal.adml
new file mode 100644
index 00000000000..5516292e123
--- /dev/null
+++ b/policies/en-US/WindowsTerminal.adml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--  (c) 2024 Microsoft Corporation  -->
+<policyDefinitionResources xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.0" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
+  <displayName>Windows Terminal</displayName>
+  <description>Windows Terminal</description>
+  <resources>
+    <stringTable>
+      <string id="WindowsTerminal">Windows Terminal</string>
+      <string id="SUPPORTED_WindowsTerminal_1_21">At least Windows Terminal 1.21</string>
+      <string id="DisabledProfileSources">Disabled Profile Sources</string>
+      <string id="DisabledProfileSourcesText">Profiles will not be generated from any sources listed here. Source names can be arbitrary strings. Potential candidates can be found as the "source" property on profile definitions in Windows Terminal's settings.json file.
+
+Common sources are:
+- Windows.Terminal.Azure
+- Windows.Terminal.PowershellCore
+- Windows.Terminal.Wsl
+
+For instance, setting this policy to Windows.Terminal.Wsl will disable the builtin WSL integration of Windows Terminal.
+
+Note: Existing profiles will disappear from Windows Terminal after adding their source to this policy.</string>
+    </stringTable>
+    <presentationTable>
+      <presentation id="DisabledProfileSources">
+        <multiTextBox refId="DisabledProfileSources">List of disabled sources (one per line)</multiTextBox>
+      </presentation>
+    </presentationTable>
+  </resources>
+</policyDefinitionResources>
diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h
index 75eb9ee96ee..e91c43885e9 100644
--- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h
+++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h
@@ -96,7 +96,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
         void _addOrMergeUserColorScheme(const winrt::com_ptr<implementation::ColorScheme>& colorScheme);
         void _executeGenerator(const IDynamicProfileGenerator& generator);
 
-        std::unordered_set<std::wstring_view> _ignoredNamespaces;
+        std::unordered_set<winrt::hstring, til::transparent_hstring_hash, til::transparent_hstring_equal_to> _ignoredNamespaces;
         std::set<std::string> themesChangeLog;
         // See _getNonUserOriginProfiles().
         size_t _userProfileCount = 0;
diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp
index 5ec7275b05c..1039bf4343d 100644
--- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp
+++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp
@@ -146,9 +146,27 @@ SettingsLoader::SettingsLoader(const std::string_view& userJSON, const std::stri
     if (const auto sources = userSettings.globals->DisabledProfileSources())
     {
         _ignoredNamespaces.reserve(sources.Size());
-        for (const auto& id : sources)
+        for (auto&& id : sources)
         {
-            _ignoredNamespaces.emplace(id);
+            _ignoredNamespaces.emplace(std::move(id));
+        }
+    }
+
+    // Apply DisabledProfileSources policy setting. Pick whatever policy is set first.
+    // In most cases HKCU settings take precedence over HKLM settings, but the inverse is true for policies.
+    for (const auto key : { HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER })
+    {
+        wchar_t buffer[512]; // "640K ought to be enough for anyone"
+        DWORD bufferSize = sizeof(buffer);
+        if (RegGetValueW(key, LR"(Software\Policies\Microsoft\Windows Terminal)", L"DisabledProfileSources", RRF_RT_REG_MULTI_SZ, nullptr, buffer, &bufferSize) == 0)
+        {
+            for (auto p = buffer; *p;)
+            {
+                const auto len = wcslen(p);
+                _ignoredNamespaces.emplace(p, gsl::narrow_cast<uint32_t>(len));
+                p += len + 1;
+            }
+            break;
         }
     }
 
@@ -260,7 +278,7 @@ void SettingsLoader::FindFragmentsAndMergeIntoUserSettings()
                 const auto filename = fragmentExtFolder.path().filename();
                 const auto& source = filename.native();
 
-                if (!_ignoredNamespaces.count(std::wstring_view{ source }) && fragmentExtFolder.is_directory())
+                if (!_ignoredNamespaces.contains(std::wstring_view{ source }) && fragmentExtFolder.is_directory())
                 {
                     parseAndLayerFragmentFiles(fragmentExtFolder.path(), winrt::hstring{ source });
                 }
@@ -295,7 +313,7 @@ void SettingsLoader::FindFragmentsAndMergeIntoUserSettings()
     for (const auto& ext : extensions)
     {
         const auto packageName = ext.Package().Id().FamilyName();
-        if (_ignoredNamespaces.count(std::wstring_view{ packageName }))
+        if (_ignoredNamespaces.contains(std::wstring_view{ packageName }))
         {
             continue;
         }
@@ -914,7 +932,7 @@ void SettingsLoader::_addOrMergeUserColorScheme(const winrt::com_ptr<implementat
 void SettingsLoader::_executeGenerator(const IDynamicProfileGenerator& generator)
 {
     const auto generatorNamespace = generator.GetNamespace();
-    if (_ignoredNamespaces.count(generatorNamespace))
+    if (_ignoredNamespaces.contains(generatorNamespace))
     {
         return;
     }
diff --git a/src/inc/til/winrt.h b/src/inc/til/winrt.h
index f585109747d..c5e68bac776 100644
--- a/src/inc/til/winrt.h
+++ b/src/inc/til/winrt.h
@@ -109,4 +109,24 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
     //     Which is just silly
 
 #endif
+
+    struct transparent_hstring_hash
+    {
+        using is_transparent = void;
+
+        size_t operator()(const auto& hstr) const noexcept
+        {
+            return std::hash<std::wstring_view>{}(hstr);
+        }
+    };
+
+    struct transparent_hstring_equal_to
+    {
+        using is_transparent = void;
+
+        bool operator()(const auto& lhs, const auto& rhs) const noexcept
+        {
+            return lhs == rhs;
+        }
+    };
 }


Test patch: