Repository: microsoft/terminal (language: cpp)

Issue:
WindowsTerminal fails to launch elevate=true profiles on new install; elevate-shim does not set dllpath
### Windows Terminal version

1.15.2875.0

### Windows build number

10.0.22621.819

### Other Software

N/A - brand new system setup

### Steps to reproduce

This should repro on a brand new Windows 11 installation as long as you do not have vcruntime140.dll in your default system %PATH%.

- Install new Windows 11 System
- Install Windows Terminal from the App Store
- Launch Windows Terminal
- Create a new profile by duplicating the `Command Prompt` profile and setting the `elevate = true` flag
- Launch the new `Command Prompt (elevated)` profile (or whatever you named it)

Will provide additional details in the comments. I've got procmon traces, dump snapshots and a method to observe this w/o the failure.  Issue seems to be related to ShellExecuteExW in elevate-shim not setting the DllPath in the elevated process.

### Expected Behavior

New Windows Terminal window launches w/ elevated cmd.exe

### Actual Behavior

WindowsTerminal.exe fails to launch w/ "not found" errors for msvcp140.dll, vcruntime140.dll, vcruntime140_1.dll

<img width="309" alt="image" src="https://user-images.githubusercontent.com/3619564/206064820-04d66ed7-118e-407e-a7b0-c55438ecb452.png">

<img width="302" alt="image" src="https://user-images.githubusercontent.com/3619564/206064842-3552f372-22b4-4a4f-8eac-8c0d6e872fc2.png">

<img width="309" alt="image" src="https://user-images.githubusercontent.com/3619564/206065201-5d78d77d-986a-4b9c-a5f9-5487aab023c8.png">



Discussion / hints:
Process Monitor Trace shows the shim launching an elevated WindowsTerminal.exe, however -- note the paths used to resolve DLLs is different than the unelevated process (and fails).

<img width="1920" alt="image" src="https://user-images.githubusercontent.com/3619564/206065661-44119216-9844-4e7d-a4ad-f9bf9d50bc6d.png">

[WindowsTerminal-ProcMon.zip](https://github.com/microsoft/terminal/files/10171970/WindowsTerminal-ProcMon.zip)


I took dump snapshots (via Task Manager) of WindowsTerminal.exe of unelevated (after initial launch) and the elevated process (while the initial error dialog is on screen).  Notable, the DllPath is different between the two.

Unelevated:
```
0:000> !peb
    CurrentDirectory:  'C:\Windows\system32\'
    WindowTitle:  'C:\Program Files\WindowsApps\Microsoft.WindowsTerminal_1.15.2875.0_x64__8wekyb3d8bbwe\WindowsTerminal.exe'
    ImageFile:    'C:\Program Files\WindowsApps\Microsoft.WindowsTerminal_1.15.2875.0_x64__8wekyb3d8bbwe\WindowsTerminal.exe'
    CommandLine:  '"C:\Program Files\WindowsApps\Microsoft.WindowsTerminal_1.15.2875.0_x64__8wekyb3d8bbwe\WindowsTerminal.exe" '
    DllPath:      'C:\Program Files\WindowsApps\Microsoft.WindowsTerminal_1.15.2875.0_x64__8wekyb3d8bbwe;C:\Program Files\WindowsApps\Microsoft.VCLibs.140.00.UWPDesktop_14.0.30704.0_x64__8wekyb3d8bbwe;C:\Program Files\WindowsApps\Microsoft.UI.Xaml.2.7_7.2208.15002.0_x64__8wekyb3d8bbwe;'
```

Elevated:
```
0:000> !peb

    CurrentDirectory:  'C:\Windows\system32\'
    WindowTitle:  'C:\Program Files\WindowsApps\Microsoft.WindowsTerminal_1.15.2875.0_x64__8wekyb3d8bbwe\WindowsTerminal.exe'
    ImageFile:    'C:\Program Files\WindowsApps\Microsoft.WindowsTerminal_1.15.2875.0_x64__8wekyb3d8bbwe\WindowsTerminal.exe'
    CommandLine:  '"C:\Program Files\WindowsApps\Microsoft.WindowsTerminal_1.15.2875.0_x64__8wekyb3d8bbwe\WindowsTerminal.exe" --profile "{37fcd1f2-c0fc-49bb-8ea7-f5bf41414c0d}"'
    DllPath:      '< Name not readable >'

0:000> dt _peb @$peb
WindowsTerminal!_PEB
   +0x000 Reserved1        : [2]  ""
   +0x002 BeingDebugged    : 0 ''
   +0x003 Reserved2        : [1]  "???"
   +0x008 Reserved3        : [2] 0xffffffff`ffffffff Void
   +0x018 Ldr              : 0x00007ff8`9c254380 _PEB_LDR_DATA
   +0x020 ProcessParameters : 0x00000184`59203760 _RTL_USER_PROCESS_PARAMETERS
   +0x028 Reserved4        : [3] (null) 
   +0x040 AtlThunkSListPtr : (null) 
   +0x048 Reserved5        : (null) 
   +0x050 Reserved6        : 3
   +0x058 Reserved7        : (null) 
   +0x060 Reserved8        : 0
   +0x064 AtlThunkSListPtr32 : 0
   +0x068 Reserved9        : [45] 0x00000184`58f50000 Void
   +0x1d0 Reserved10       : [96]  ""
   +0x230 PostProcessInitRoutine : (null) 
   +0x238 Reserved11       : [128]  "???"
   +0x2b8 Reserved12       : [1] (null) 
   +0x2c0 SessionId        : 1
0:000> dx -r1 ((WindowsTerminal!_RTL_USER_PROCESS_PARAMETERS *)0x18459203760)
((WindowsTerminal!_RTL_USER_PROCESS_PARAMETERS *)0x18459203760)                 : 0x18459203760 [Type: _RTL_USER_PROCESS_PARAMETERS *]
    [+0x000] Reserved1        [Type: unsigned char [16]]
    [+0x010] Reserved2        [Type: void * [10]]
    [+0x060] ImagePathName    [Type: _UNICODE_STRING]
    [+0x070] CommandLine      [Type: _UNICODE_STRING]
0:000> dx -r1 (*((WindowsTerminal!void * (*)[10])0x18459203770))
(*((WindowsTerminal!void * (*)[10])0x18459203770))                 [Type: void * [10]]
    [0]              : 0x0 [Type: void *]
    [1]              : 0x0 [Type: void *]
    [2]              : 0x0 [Type: void *]
    [3]              : 0x0 [Type: void *]
    [4]              : 0x0 [Type: void *]
    [5]              : 0x2080028 [Type: void *]
    [6]              : 0x18459204530 [Type: void *]
    [7]              : 0x4c [Type: void *]
    [8]              : 0x0 [Type: void *]
    [9]              : 0x0 [Type: void *] <---- null 
```

Finally; you can verify this on a working system by attaching a debugger to WindowsTerminal.exe and inspecting the loaded modules for an unelevated vs elevated process. Note the image path is different.

Edit: the dll versions are different too, that's interesting from a bug/support perspective.

unelevated:

```
0:000> lmDvmVCRUNTIME140
Browse full module list
start             end                 module name
00007ffc`36f90000 00007ffc`36fab000   VCRUNTIME140   (deferred)             
    Image path: C:\Program Files\WindowsApps\Microsoft.VCLibs.140.00.UWPDesktop_14.0.30704.0_x64__8wekyb3d8bbwe\VCRUNTIME140.dll
    Image name: VCRUNTIME140.dll
    Browse all global symbols  functions  data
    Timestamp:        Sun Oct  3 22:33:09 2021 (615A9215)
    CheckSum:         000241C0
    ImageSize:        0001B000
    File version:     14.30.30704.0
    Product version:  14.30.30704.0
    File flags:       0 (Mask 3F)
    File OS:          4 Unknown Win32
    File type:        2.0 Dll
    File date:        00000000.00000000
    Translations:     0409.04b0
    Information from resource tables:
        CompanyName:      Microsoft Corporation
        ProductName:      Microsoft® Visual Studio®
        InternalName:     vcruntime140.dll
        OriginalFilename: vcruntime140.dll
        ProductVersion:   14.30.30704.0
        FileVersion:      14.30.30704.0 built by: vcwrkspc
        FileDescription:  Microsoft® C Runtime Library
        LegalCopyright:   © Microsoft Corporation. All rights reserved.
``` 

elevated:

```
0:000> lmDvmVCRUNTIME140
Browse full module list
start             end                 module name
00007ffc`4e130000 00007ffc`4e14b000   VCRUNTIME140   (deferred)             
    Image path: C:\Windows\System32\VCRUNTIME140.dll
    Image name: VCRUNTIME140.dll
    Browse all global symbols  functions  data
    Image was built with /Brepro flag.
    Timestamp:        8E79CD85 (This is a reproducible build file hash, not a timestamp)
    CheckSum:         000235A7
    ImageSize:        0001B000
    File version:     14.34.31931.0
    Product version:  14.34.31931.0
    File flags:       0 (Mask 3F)
    File OS:          4 Unknown Win32
    File type:        2.0 Dll
    File date:        00000000.00000000
    Translations:     0409.04b0
    Information from resource tables:
        CompanyName:      Microsoft Corporation
        ProductName:      Microsoft® Visual Studio®
        InternalName:     vcruntime140.dll
        OriginalFilename: vcruntime140.dll
        ProductVersion:   14.34.31931.0
        FileVersion:      14.34.31931.0
        FileDescription:  Microsoft® C Runtime Library
        LegalCopyright:   © Microsoft Corporation. All rights reserved.
```
👍🏼 great collection of details
This is wild, and a great collection of details. I'm not entirely sure how we go about actually fixing this. Kinda seems to me like something that the package infrastructure should have taken care of _for us_, but I can't be sure. We'll need to follow up with some folks and investigate. 

I reckon this is gonna come to something that we could theoretically work around, but we should make sure the rest of the platform team is aware of this. 

(@DHowett putting this in your inbox for after vacation - who owns the MSIX bits these days that we should follow up with?)
I went down quite the rabbit hole exploring how a new process is created with ShellExecuteEx runas. It was also a good excuse to learn more about Time Travel Debug and store apps.

From what I can gather, the [ShellExecuteEx](https://github.com/microsoft/terminal/blob/main/src/cascadia/ElevateShim/elevate-shim.cpp#L52) call from [elevate-shim](https://github.com/microsoft/terminal/tree/main/src/cascadia/ElevateShim) eventually fires off some RPC COM calls via Explorer.exe to launch the new process `WindowsTerminal.exe`. However, launching the new process (apparently) does this without any awareness of the store app; and in turn does not set the DllPath in the PEB.

However, launching Terminal via the start menu (elevated or not) seems to follow a different process that is aware of the store app manifest.  I can see the AppInfo service (svchost.exe) reading the app manifest file here: `C:\ProgramData\Microsoft\Windows\AppRepository\Packages\Microsoft.WindowsTerminal_1.15.3466.0_x64__8wekyb3d8bbwe\S-1-5-21-725345543-602162358-527237240-86197.pckgdep`


It looks like there is a viable path with minimal changes (still uses the shim and ShellExecute); it would require some state to determine if the original process was launched as a store app vs some other means (e.g. developer mode). This state would be passed along to the elevate shim to make a decision to use `shell:AppsFolder` vs the `WindowsTerminal.exe` path while it's constructing the call to ShellExecute.

Relevant: PowerToys knows how to [launch processes elevated](https://github.com/microsoft/PowerToys/blob/a510d5e3ce084dd86ae383892573a1efde2f9922/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/UWPApplication.cs#L149-L157) using `shell:AppsFolder` + `runas` instead of an executable path.

```csharp
string command = "shell:AppsFolder\\" + UniqueIdentifier;
command = Environment.ExpandEnvironmentVariables(command.Trim());

var info = ShellCommand.SetProcessStartInfo(command, verb: "runas");
info.UseShellExecute = true;
info.Arguments = queryArguments;
Process.Start(info);
return true;
```



Fix patch:
diff --git a/src/cascadia/ElevateShim/elevate-shim.cpp b/src/cascadia/ElevateShim/elevate-shim.cpp
index e9b4d859cfb..50449ddcacb 100644
--- a/src/cascadia/ElevateShim/elevate-shim.cpp
+++ b/src/cascadia/ElevateShim/elevate-shim.cpp
@@ -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
 //
@@ -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.
@@ -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;
 }


Test patch: