Skip to content

Commit

Permalink
Launch elevated instances via shell:AppFolder (#14637)
Browse files Browse the repository at this point in the history
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 a bug in elevate-shim where the first argument of
WinMain was lost (e.g. `new-tab`). 

Curiously, `AppLogic::RunAsUwp()` is never called and
`AppLogic::IsUwp()` is always false when running debug builds locally
(e.g. WindowsTerminalDev). It's not clear if this is an artifact of
development packages or something else.

## Validation Steps Performed

Various manual debug/execution scenarios.

Verified the fallback path by running the unbundled app by extracting
the `CascadiaPackage_0.0.1.0_x64.msix` from the 'drop' build artifact.

Fixes #14501
  • Loading branch information
jboelter authored Jan 19, 2023
1 parent 96a9dd5 commit eab1c23
Showing 1 changed file with 64 additions and 9 deletions.
73 changes: 64 additions & 9 deletions src/cascadia/ElevateShim/elevate-shim.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
#include <wil/stl.h>
#include <wil/resource.h>
#include <wil/win32_helpers.h>
#include <gsl/gsl_util>
#include <gsl/pointers>
#include <shellapi.h>
#include <appmodel.h>

// BODGY
//
Expand All @@ -25,18 +28,68 @@
// 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 will invoke an elevated terminal in two possible ways. See GH#14501
// In both scenarios, it passes the entire cmdline as-is to the new process.
//
// #1 discover and invoke the app using the GetCurrentApplicationUserModelId
// api using shell:AppsFolder\package!appid
// cmd: shell:AppsFolder\WindowsTerminalDev_8wekyb3d8bbwe!App
// params: new-tab -p {guid}
//
// #2 find and execute WindowsTerminal.exe
// cmd: {same path as this binary}\WindowsTerminal.exe
// params: 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");
// see if we're a store app we can invoke with shell:AppsFolder
std::wstring appUserModelId;
const auto result = wil::AdaptFixedSizeToAllocatedResult<std::wstring, APPLICATION_USER_MODEL_ID_MAX_LENGTH>(
appUserModelId, [&](PWSTR value, size_t valueLength, gsl::not_null<size_t*> valueLengthNeededWithNull) noexcept -> HRESULT {
UINT32 length = gsl::narrow_cast<UINT32>(valueLength);
const LONG rc = GetCurrentApplicationUserModelId(&length, value);
switch (rc)
{
case ERROR_SUCCESS:
*valueLengthNeededWithNull = length;
return S_OK;

case ERROR_INSUFFICIENT_BUFFER:
*valueLengthNeededWithNull = length;
return S_FALSE; // trigger allocation loop

case APPMODEL_ERROR_NO_APPLICATION:
return E_FAIL; // we are not running as a store app

default:
return E_UNEXPECTED;
}
});
LOG_IF_FAILED(result);

std::wstring cmd = {};
if (result == S_OK && appUserModelId.length() > 0)
{
// scenario #1
cmd = L"shell:AppsFolder\\" + appUserModelId;
}
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;
}

// Go!

// The cmdline argument passed to WinMain is stripping the first argument.
// Using GetCommandLine() instead for lParameters

// disable warnings from SHELLEXECUTEINFOW struct. We can't fix that.
#pragma warning(push)
#pragma warning(disable : 26476) // Macro uses naked union over variant.
Expand All @@ -46,8 +99,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 = GetCommandLine(); // This is `new-tab -p {guid}`
seInfo.nShow = SW_SHOWNORMAL;
LOG_IF_WIN32_BOOL_FALSE(ShellExecuteExW(&seInfo));

return 0;
}

0 comments on commit eab1c23

Please sign in to comment.