diff --git a/src/cascadia/ElevateShim/elevate-shim.cpp b/src/cascadia/ElevateShim/elevate-shim.cpp index e9b4d859cfb..41029a43861 100644 --- a/src/cascadia/ElevateShim/elevate-shim.cpp +++ b/src/cascadia/ElevateShim/elevate-shim.cpp @@ -25,15 +25,64 @@ // process can successfully elevate. #pragma warning(suppress : 26461) // we can't change the signature of wWinMain -int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR pCmdLine, int) +int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) { - // All of the args passed to us (something like `new-tab -p {guid}`) are in - // pCmdLine + // This supports being invoked in two possible ways. See GH#14501 + // + // #1 Invoke the AppX package using shell:AppsFolder\app!id. The callers passes + // the cmd as the first argument in the form of shell:AppsFolder\app!id. We parse + // this out of the command line as the cmd and pass the remainder as the cmdline + // `shell:AppsFolder\WindowsTerminalDev_8wekyb3d8bbwe!App new-tab -p {guid}` + // + // #2 in this scenario we find and execute WindowsTerminal.exe and pass the + // cmdline args as-is. + // `new-tab -p {guid}` + // - // Get the path to WindowsTerminal.exe, which should live next to us. - std::filesystem::path module{ wil::GetModuleFileNameW(nullptr) }; - // Swap elevate-shim.exe for WindowsTerminal.exe - module.replace_filename(L"WindowsTerminal.exe"); + DebugBreak(); + + // The cmdline argument in WinMain is stripping the first argument. + // Using GetCommandLine() and __wargv instead. + int argc = __argc; + LPWSTR* argv = __wargv; + if (argv == nullptr) + { + OutputDebugString(L"elevate-shim: no arguments found, argv == nullptr"); + LOG_LAST_ERROR_IF_NULL(argv); + return -1; + } + if (argc == 0) + { + OutputDebugString(L"elevate-shim: no arguments found, argc == 0"); + return -1; + } + + std::wstring cmdLine = GetCommandLine(); + std::wstring cmd = {}; + std::wstring args = {}; + + std::wstring arg0 = argv[0]; + if (arg0.starts_with(L"shell:AppsFolder")) + { + // scenario #1 + cmd = arg0; + + // We don't want to reconstruct the args with proper quoting and escaping. + // Let's remove the first arg instead. This assumes it was not unescaped + // when processed to __wargv. We might leave a pair of empty quotes if it + // was quoted. + args = cmdLine.replace(cmdLine.find(arg0), arg0.length(), L""); + } + else + { + // scenario #2 + // Get the path to WindowsTerminal.exe, which should live next to us. + std::filesystem::path module{ wil::GetModuleFileNameW(nullptr) }; + // Swap elevate-shim.exe for WindowsTerminal.exe + module.replace_filename(L"WindowsTerminal.exe"); + cmd = module; + args = GetCommandLine(); // args as-is + } // Go! @@ -46,8 +95,10 @@ int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR pCmdLine, int) seInfo.cbSize = sizeof(seInfo); seInfo.fMask = SEE_MASK_DEFAULT; seInfo.lpVerb = L"runas"; // This asks the shell to elevate the process - seInfo.lpFile = module.c_str(); // This is `...\WindowsTerminal.exe` - seInfo.lpParameters = pCmdLine; // This is `new-tab -p {guid}` + seInfo.lpFile = cmd.c_str(); // This is `shell:AppsFolder\...` or `...\WindowsTerminal.exe` + seInfo.lpParameters = args.c_str(); // This is `new-tab -p {guid}` seInfo.nShow = SW_SHOWNORMAL; LOG_IF_WIN32_BOOL_FALSE(ShellExecuteExW(&seInfo)); + + return 0; } diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 4381e24e0b1..140dffa594e 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include "../../types/inc/utils.hpp" @@ -219,6 +220,43 @@ namespace winrt::TerminalApp::implementation return _isUwp; } + // Method Description: + // - Called by _OpenElevatedWT to determine the app name to invoke with shell:AppFolder + // Arguments: + // - + // Return Value: + // - Empty if inaccessible; an invocable shell appfolder path otherwise e.g. WindowsTerminalDev_8wekyb3d8bbwe!App + winrt::hstring AppLogic::GetApplicationUserModelId() + { + winrt::hstring _applicationUserModelId = {}; + UINT32 length = 0; + LONG rc = GetCurrentApplicationUserModelId(&length, NULL); + switch (rc) + { + case ERROR_INSUFFICIENT_BUFFER: + break; // expected + case APPMODEL_ERROR_NO_PACKAGE: + return _applicationUserModelId; + default: // unexpected + return _applicationUserModelId; + } + + wchar_t* packageId = new wchar_t[length]; + if (packageId == nullptr) + { + return _applicationUserModelId; // doh + } + + rc = GetCurrentApplicationUserModelId(&length, packageId); + if (rc != ERROR_SUCCESS) + { + return _applicationUserModelId; + } + _applicationUserModelId = packageId; + delete[] packageId; + return _applicationUserModelId; + } + // Method Description: // - Called around the codebase to discover if Terminal is running elevated // Arguments: diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index 5534979b973..42532c399f9 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -64,6 +64,8 @@ namespace winrt::TerminalApp::implementation bool IsElevated() const noexcept; void ReloadSettings(); + winrt::hstring GetApplicationUserModelId(); + [[nodiscard]] Microsoft::Terminal::Settings::Model::CascadiaSettings GetSettings() const noexcept; void Quit(); diff --git a/src/cascadia/TerminalApp/AppLogic.idl b/src/cascadia/TerminalApp/AppLogic.idl index b7a92ea1103..8e670495d07 100644 --- a/src/cascadia/TerminalApp/AppLogic.idl +++ b/src/cascadia/TerminalApp/AppLogic.idl @@ -50,6 +50,8 @@ namespace TerminalApp void RunAsUwp(); Boolean IsElevated(); + String GetApplicationUserModelId(); + Boolean HasCommandlineArguments(); Boolean HasSettingsStartupActions(); Int32 SetStartupCommandline(String[] commands); @@ -68,7 +70,7 @@ namespace TerminalApp Boolean FocusMode { get; }; Boolean Fullscreen { get; }; - void Maximized(Boolean newMaximized); + void Maximized(Boolean newMaximized); Boolean AlwaysOnTop { get; }; Boolean AutoHideWindow { get; }; diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index f03c6101a14..7fe5404948f 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -4114,9 +4114,22 @@ namespace winrt::TerminalApp::implementation std::filesystem::path exePath = wil::GetModuleFileNameW(nullptr); exePath.replace_filename(L"elevate-shim.exe"); + std::wstring shellCommand = {}; + + auto appUserModelId = ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Logic().GetApplicationUserModelId(); + if (appUserModelId.length() > 0) + { + // See GH#14501 for background. If you quote this string, you'll have + // to update elevate-shim to strip the quotes when constructing the arguments. + shellCommand = fmt::format(L"shell:AppsFolder\\{}", appUserModelId); + } + // else leave shellCommand empty; fallback to invoking WindowsTerminal.exe + // Build the commandline to pass to wt for this set of NewTerminalArgs + // The shellCommand argument is used if we're using shell:AppsFolder to launch the app; + // else, leave it empty. auto cmdline{ - fmt::format(L"new-tab {}", newTerminalArgs.ToCommandline().c_str()) + fmt::format(L"{} new-tab {}", shellCommand, newTerminalArgs.ToCommandline().c_str()) }; wil::unique_process_information pi;