Skip to content

Commit

Permalink
Launch elevated instances via shell:AppFolder
Browse files Browse the repository at this point in the history
Fixes microsoft#14501

This uses `shell:AppsFolder` to launch elevated instances of the app via
`ShellExecuteEx` and `runas` in elevate-shim.exe. The app to launch is
discovered via the `GetCurrentApplicationUserModelId` API.

e.g. `shell:AppsFolder\WindowsTerminalDev_8wekyb3d8bbwe!App`

This will fallback to launching `WindowsTerminal.exe` if it fails to
discover the app user model id to launch.

This also fixes an innocuous bug in elevate-shim where the first
argument of WinMain was lost (e.g. `new-tab`).
  • Loading branch information
jboelter committed Jan 5, 2023
1 parent 21a62c5 commit 62017c0
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 11 deletions.
69 changes: 60 additions & 9 deletions src/cascadia/ElevateShim/elevate-shim.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::wstring>(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<std::wstring>(nullptr) };
// Swap elevate-shim.exe for WindowsTerminal.exe
module.replace_filename(L"WindowsTerminal.exe");
cmd = module;
args = GetCommandLine(); // args as-is
}

// Go!

Expand All @@ -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;
}
38 changes: 38 additions & 0 deletions src/cascadia/TerminalApp/AppLogic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <LibraryResources.h>
#include <WtExeUtils.h>
#include <wil/token_helpers.h>
#include <appmodel.h>

#include "../../types/inc/utils.hpp"

Expand Down Expand Up @@ -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:
// - <none>
// 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:
Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalApp/AppLogic.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
4 changes: 3 additions & 1 deletion src/cascadia/TerminalApp/AppLogic.idl
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ namespace TerminalApp
void RunAsUwp();
Boolean IsElevated();

String GetApplicationUserModelId();

Boolean HasCommandlineArguments();
Boolean HasSettingsStartupActions();
Int32 SetStartupCommandline(String[] commands);
Expand All @@ -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; };

Expand Down
15 changes: 14 additions & 1 deletion src/cascadia/TerminalApp/TerminalPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4114,9 +4114,22 @@ namespace winrt::TerminalApp::implementation
std::filesystem::path exePath = wil::GetModuleFileNameW<std::wstring>(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;
Expand Down

0 comments on commit 62017c0

Please sign in to comment.