Repository: microsoft/terminal (language: cpp)

Issue:
Cannot Connect to Cloud Shell anymore
### Windows Terminal version

1.17.11461.0

### Windows build number

10.0.22621.2283

### Other Software

_No response_

### Steps to reproduce

1. Create Azure Cloud Shell Tab
2. Step through browser login
3. Select tenant

### Expected Behavior

Expecting a cloud shell terminal.

### Actual Behavior

Requesting a cloud shell instance...
Succeeded.
Requesting a terminal (this might take a while)...

[process exited with code 1 (0x00000001)]


Discussion / hints:
Are you sure that's all the output that was displayed in the Terminal? We're pretty confident that there's supposed to be an exception handler in that path that should have written out the error message from Azure.....


The exception handler only handles `std::exceptions`. Maybe another type of exception was thrown. 
https://github.com/microsoft/terminal/blob/4145f18768772270da03f9ea56b5ec6f7157375c/src/cascadia/TerminalConnection/AzureConnection.cpp#L107-L107

or there's no exception text
A WPR trace of this would be helpful: [Capturing a Debug ETL Trace](https://github.com/microsoft/terminal/wiki/Toubleshooting-Tips#capturing-a-debug-etl-trace)
Oh also a note: a winrt exception does NOT inherit from `std::exception`: https://github.com/microsoft/cppwinrt/issues/1367
> A WPR trace of this would be helpful: [Capturing a Debug ETL Trace](https://github.com/microsoft/terminal/wiki/Toubleshooting-Tips#capturing-a-debug-etl-trace)

I have uploaded the ETL to Feedback Hub: https://aka.ms/AAn1y80

Also, no, the only output I get is "[process exited with code 1 (0x00000001)]." I even tried installing the Azure CLI latest version and manually editing the winterm json to point to that .exe and it returns the same.

Additional info: this behavior started about the same time Cloud Shell stopped working in vscode; though in vscode it manifests as not displaying any available subs/directories when I try to start the shell. Connect-AzAccount from pwsh 7.3.7 in winterm allows me to run commands just fine, but this requires some adaption to the runbook I'm building so testing isn't 1:1.

![image](https://github.com/microsoft/terminal/assets/46008360/b79800dd-232f-4f5b-8b20-582f81745764)
![image](https://github.com/microsoft/terminal/assets/46008360/25371c8e-62ad-4e9e-a5c0-5ad6b7c7df42)


We've gotten a few of these reports recently so there's likely something here. I have collected traces of the login process from someone who was unable to connect and the current suspicion is that there was an API change on the Azure side that we aren't conforming to. We have set up a meeting with the Cloud Shell team, will update here once that's done.
@PankajBhojwani Sorry to be pushy, but do you have an update on this? I manage ~100 tenants through cloud shell so this is slowing me down quite a bit.

Thanks for the transparency!
A few things - the Azure Cloud Shell team is undergoing some amount of migration regarding where they store the sessions, which could explain why only some users are facing this issue and why it seems to sometimes be intermittent. Other than that, there might be some post-handling of the URLs that Terminal needs to do to make sure our requests are going to the right place and are well formed. We are continuing to investigate this! 

Fix patch:
diff --git a/.github/actions/spelling/allow/allow.txt b/.github/actions/spelling/allow/allow.txt
index 7bff8f0cfa0..bccfe086aeb 100644
--- a/.github/actions/spelling/allow/allow.txt
+++ b/.github/actions/spelling/allow/allow.txt
@@ -1,3 +1,4 @@
+aci
 admins
 allcolors
 Apc
@@ -8,6 +9,7 @@ breadcrumbs
 bsd
 calt
 ccmp
+ccon
 changelog
 clickable
 clig
@@ -91,6 +93,7 @@ reserialize
 reserializes
 rlig
 runtimes
+servicebus
 shcha
 slnt
 Sos
@@ -117,6 +120,7 @@ vsdevcmd
 walkthrough
 walkthroughs
 We'd
+westus
 wildcards
 XBox
 YBox
diff --git a/src/cascadia/TerminalConnection/AzureConnection.cpp b/src/cascadia/TerminalConnection/AzureConnection.cpp
index 3844979b488..708518509b5 100644
--- a/src/cascadia/TerminalConnection/AzureConnection.cpp
+++ b/src/cascadia/TerminalConnection/AzureConnection.cpp
@@ -398,6 +398,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
 
                         switch (bufferType)
                         {
+                        case WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE:
                         case WINHTTP_WEB_SOCKET_UTF8_FRAGMENT_BUFFER_TYPE:
                         case WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE:
                         {
@@ -797,7 +798,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
     // - an optional HTTP method (defaults to POST if content is present, GET otherwise)
     // Return value:
     // - the response from the server as a json value
-    WDJ::JsonObject AzureConnection::_SendRequestReturningJson(std::wstring_view uri, const WWH::IHttpContent& content, WWH::HttpMethod method)
+    WDJ::JsonObject AzureConnection::_SendRequestReturningJson(std::wstring_view uri, const WWH::IHttpContent& content, WWH::HttpMethod method, const Windows::Foundation::Uri referer)
     {
         if (!method)
         {
@@ -810,6 +811,11 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
         auto headers{ request.Headers() };
         headers.Accept().TryParseAdd(L"application/json");
 
+        if (referer)
+        {
+            headers.Referer(referer);
+        }
+
         const auto response{ _httpClient.SendRequestAsync(request).get() };
         const auto string{ response.Content().ReadAsStringAsync().get() };
         const auto jsonResult{ WDJ::JsonObject::Parse(string) };
@@ -974,17 +980,56 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
         auto uri{ fmt::format(L"{}terminals?cols={}&rows={}&version=2019-01-01&shell={}", _cloudShellUri, _initialCols, _initialRows, shellType) };
 
         WWH::HttpStringContent content{
-            L"",
+            L"{}",
             WSS::UnicodeEncoding::Utf8,
             // LOAD-BEARING. the API returns "'content-type' should be 'application/json' or 'multipart/form-data'"
             L"application/json"
         };
 
-        const auto terminalResponse = _SendRequestReturningJson(uri, content);
+        const auto terminalResponse = _SendRequestReturningJson(uri, content, WWH::HttpMethod::Post(), Windows::Foundation::Uri(_cloudShellUri));
         _terminalID = terminalResponse.GetNamedString(L"id");
 
+        // we have to do some post-handling to get the proper socket endpoint
+        // the logic here is based on the way the cloud shell team itself does it
+        winrt::hstring finalSocketUri;
+        const std::wstring_view wCloudShellUri{ _cloudShellUri };
+
+        if (wCloudShellUri.find(L"servicebus") == std::wstring::npos)
+        {
+            // wCloudShellUri does not contain the word "servicebus", we can just use it to make the final URI
+
+            // remove the "https" from the cloud shell URI
+            const auto uriWithoutProtocol = wCloudShellUri.substr(5);
+
+            finalSocketUri = fmt::format(FMT_COMPILE(L"wss{}terminals/{}"), uriWithoutProtocol, _terminalID);
+        }
+        else
+        {
+            // if wCloudShellUri contains the word "servicebus", that means the returned socketUri is of the form
+            // wss://ccon-prod-westus-aci-03.servicebus.windows.net/cc-AAAA-AAAAAAAA//aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+            // we need to change it to:
+            // wss://ccon-prod-westus-aci-03.servicebus.windows.net/$hc/cc-AAAA-AAAAAAAA/terminals/aaaaaaaaaaaaaaaaaaaaaa
+
+            const auto socketUri = terminalResponse.GetNamedString(L"socketUri");
+            const std::wstring_view wSocketUri{ socketUri };
+
+            // get the substring up until the ".net"
+            const auto dotNetStart = wSocketUri.find(L".net");
+            THROW_HR_IF(E_UNEXPECTED, dotNetStart == std::wstring::npos);
+            const auto dotNetEnd = dotNetStart + 4;
+            const auto wSocketUriBody = wSocketUri.substr(0, dotNetEnd);
+
+            // get the portion between the ".net" and the "//" (this is the cc-AAAA-AAAAAAAA part)
+            const auto lastDoubleSlashPos = wSocketUri.find_last_of(L"//");
+            THROW_HR_IF(E_UNEXPECTED, lastDoubleSlashPos == std::wstring::npos);
+            const auto wSocketUriMiddle = wSocketUri.substr(dotNetEnd, lastDoubleSlashPos - (dotNetEnd));
+
+            // piece together the final uri, adding in the "$hc" and "terminals" where needed
+            finalSocketUri = fmt::format(FMT_COMPILE(L"{}/$hc{}terminals/{}"), wSocketUriBody, wSocketUriMiddle, _terminalID);
+        }
+
         // Return the uri
-        return terminalResponse.GetNamedString(L"socketUri");
+        return winrt::hstring{ finalSocketUri };
     }
 
     // Method description:
diff --git a/src/cascadia/TerminalConnection/AzureConnection.h b/src/cascadia/TerminalConnection/AzureConnection.h
index cedd76757af..9d179073019 100644
--- a/src/cascadia/TerminalConnection/AzureConnection.h
+++ b/src/cascadia/TerminalConnection/AzureConnection.h
@@ -68,7 +68,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
 
         void _WriteStringWithNewline(const std::wstring_view str);
         void _WriteCaughtExceptionRecord();
-        winrt::Windows::Data::Json::JsonObject _SendRequestReturningJson(std::wstring_view uri, const winrt::Windows::Web::Http::IHttpContent& content = nullptr, winrt::Windows::Web::Http::HttpMethod method = nullptr);
+        winrt::Windows::Data::Json::JsonObject _SendRequestReturningJson(std::wstring_view uri, const winrt::Windows::Web::Http::IHttpContent& content = nullptr, winrt::Windows::Web::Http::HttpMethod method = nullptr, const winrt::Windows::Foundation::Uri referer = nullptr);
         void _setAccessToken(std::wstring_view accessToken);
         winrt::Windows::Data::Json::JsonObject _GetDeviceCode();
         winrt::Windows::Data::Json::JsonObject _WaitForUser(const winrt::hstring& deviceCode, int pollInterval, int expiresIn);


Test patch: