How do I find the target of a Windows App Execution Alias in C#/Win32 api?
Asked Answered
L

4

6

Microsft Windows Terminal (installed via the Microsoft Store) creates a 0 bytes wt.exe file which is a Windows execution alias. AFAIK it is somthing similar to a symbolic link, except it seems to be resolved at the CreateProcess Api level as opposed to a symlink that is translated at the file system.

In powershell:

❯ dir ~\AppData\Local\Microsoft\WindowsApps\wt.exe

Mode    Name
----    ----
la---   wt.exe -> C:\Program Files\WindowsApps\Microsoft.WindowsTerminal_1.0.1401.0_x64__8wekyb3d8bbwe\WindowsTerminal.exe

❯ Get-Item .\wt.exe | fl

Name           : wt.exe
Length         : 0
LinkType       : AppExeCLink
Target         : C:\Program Files\WindowsApps\Microsoft.WindowsTerminal_1.0.1401.0_x64__8wekyb3d8bbwe\WindowsTerminal.exe

I wasn't able to find proper documentation for these "new" kind of aliases. Even googling PS AppExeCLink value is not very useful.

I need a fast way to resolve an execution alias (get the target file) in a C# App. Given my requirements, I prefer an unmanaged (Win32 Api) way over adding a reference to the slow WMI or an external 300kb nuget package.

Thanks!

Lepine answered 19/6, 2020 at 15:55 Comment(3)
This can help: https://mcmap.net/q/613607/-how-to-obtain-the-target-of-a-symbolic-link-or-reparse-point-using-netQuitrent
Thanks @Gabor. I tried it. It works with symbolic links, but not with this kind of aliases.Lepine
One baby step forward: IO_REPARSE_TAG_APPEXECLINKLepine
J
14

I faced the same problem in my System Tools Library, which choked on these new type of links. This library is written in C, and uses the WIN32 API.

Here's what I found so far:

  • Three years after their introduction in Windows, cmd.exe and PowerShell 5.1 are still unaware of App Exec Links, and report them as 0-byte files.

    But PowerShell Core 7 knows about them:

    PS C:\Temp> dir $env:LOCALAPPDATA\Microsoft\WindowsApps | ?{$_.LinkType} | select Name,LinkType,Target
    
    Name                        LinkType    Target
    ----                        --------    ------
    GameBarElevatedFT_Alias.exe AppExeCLink C:\Program Files\WindowsApps\Microsoft.XboxGamingOverlay_5.420.11102.0_x64__8w...
    MicrosoftEdge.exe           AppExeCLink C:\WINDOWS\system32\SystemUWPLauncher.exe
    python.exe                  AppExeCLink C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_1.11.3162.0_x64__8w...
    python3.exe                 AppExeCLink C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_1.11.3162.0_x64__8w...
    ubuntu.exe                  AppExeCLink C:\Program Files\WindowsApps\CanonicalGroupLimited.UbuntuonWindows_2004.2020.8...
    ubuntu1804.exe              AppExeCLink C:\Program Files\WindowsApps\CanonicalGroupLimited.Ubuntu18.04onWindows_2020.1...
    WinFR.exe                   AppExeCLink C:\Program Files\WindowsApps\Microsoft.WindowsFileRecovery_0.1.13492.0_x64__8w...
    winget.exe                  AppExeCLink C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_1.11.3162.0_x64__8w...
    wt.exe                      AppExeCLink C:\Program Files\WindowsApps\Microsoft.WindowsTerminal_1.4.3243.0_x64__8wekyb3...
    
    PS C:\Temp>
    
  • These App Exec Links are NTFS Reparse Points, tagged as
    IO_REPARSE_TAG_APPEXECLINK = 0x8000001b.

    You can read them in a C/C++/C# program using the WIN32 API DeviceIoControl() with the FSCTL_GET_REPARSE_POINT control code.

    My readlink.c module contains a ReadReparsePointW() routine demonstrating this.

  • You can dump the content of such a link, by running:

    fsutil reparsepoint query <REPARSE_POINT_PATHNAME>
    

    Ex:

    C:\Temp>fsutil reparsepoint query "%LOCALAPPDATA%\Microsoft\WindowsApps\wt.exe"
    Reparse Tag Value : 0x8000001b
    Tag value: Microsoft
    
    Reparse Data Length: 0x168
    Reparse Data:
    0000:  03 00 00 00 4d 00 69 00  63 00 72 00 6f 00 73 00  ....M.i.c.r.o.s.
    0010:  6f 00 66 00 74 00 2e 00  57 00 69 00 6e 00 64 00  o.f.t...W.i.n.d.
    0020:  6f 00 77 00 73 00 54 00  65 00 72 00 6d 00 69 00  o.w.s.T.e.r.m.i.
    0030:  6e 00 61 00 6c 00 5f 00  38 00 77 00 65 00 6b 00  n.a.l._.8.w.e.k.
    0040:  79 00 62 00 33 00 64 00  38 00 62 00 62 00 77 00  y.b.3.d.8.b.b.w.
    0050:  65 00 00 00 4d 00 69 00  63 00 72 00 6f 00 73 00  e...M.i.c.r.o.s.
    0060:  6f 00 66 00 74 00 2e 00  57 00 69 00 6e 00 64 00  o.f.t...W.i.n.d.
    0070:  6f 00 77 00 73 00 54 00  65 00 72 00 6d 00 69 00  o.w.s.T.e.r.m.i.
    0080:  6e 00 61 00 6c 00 5f 00  38 00 77 00 65 00 6b 00  n.a.l._.8.w.e.k.
    0090:  79 00 62 00 33 00 64 00  38 00 62 00 62 00 77 00  y.b.3.d.8.b.b.w.
    00a0:  65 00 21 00 41 00 70 00  70 00 00 00 43 00 3a 00  e.!.A.p.p...C.:.
    00b0:  5c 00 50 00 72 00 6f 00  67 00 72 00 61 00 6d 00  \.P.r.o.g.r.a.m.
    00c0:  20 00 46 00 69 00 6c 00  65 00 73 00 5c 00 57 00   .F.i.l.e.s.\.W.
    00d0:  69 00 6e 00 64 00 6f 00  77 00 73 00 41 00 70 00  i.n.d.o.w.s.A.p.
    00e0:  70 00 73 00 5c 00 4d 00  69 00 63 00 72 00 6f 00  p.s.\.M.i.c.r.o.
    00f0:  73 00 6f 00 66 00 74 00  2e 00 57 00 69 00 6e 00  s.o.f.t...W.i.n.
    0100:  64 00 6f 00 77 00 73 00  54 00 65 00 72 00 6d 00  d.o.w.s.T.e.r.m.
    0110:  69 00 6e 00 61 00 6c 00  5f 00 31 00 2e 00 34 00  i.n.a.l._.1...4.
    0120:  2e 00 33 00 32 00 34 00  33 00 2e 00 30 00 5f 00  ..3.2.4.3...0._.
    0130:  78 00 36 00 34 00 5f 00  5f 00 38 00 77 00 65 00  x.6.4._._.8.w.e.
    0140:  6b 00 79 00 62 00 33 00  64 00 38 00 62 00 62 00  k.y.b.3.d.8.b.b.
    0150:  77 00 65 00 5c 00 77 00  74 00 2e 00 65 00 78 00  w.e.\.w.t...e.x.
    0160:  65 00 00 00 30 00 00 00                           e...0...
    
    C:\Temp>
    
  • The reparse data there is a structure with four wide strings, looking like:

    typedef struct _REPARSE_APPEXECLINK_READ_BUFFER { // For tag IO_REPARSE_TAG_APPEXECLINK
      DWORD  ReparseTag;
      WORD   ReparseDataLength;
      WORD   Reserved;
      ULONG  Version;    // Currently version 3
      WCHAR  StringList[1];  // Multistring (Consecutive UTF-16 strings each ending with a NUL)
      /* There are normally 4 strings here. Ex:
        Package ID:  L"Microsoft.WindowsTerminal_8wekyb3d8bbwe"
        Entry Point: L"Microsoft.WindowsTerminal_8wekyb3d8bbwe!App"
        Executable:  L"C:\Program Files\WindowsApps\Microsoft.WindowsTerminal_1.4.3243.0_x64__8wekyb3d8bbwe\wt.exe"
        Applic. Type: L"0"   // Integer as ASCII. "0" = Desktop bridge application; Else sandboxed UWP application
      */     
    } APPEXECLINK_READ_BUFFER, *PAPPEXECLINK_READ_BUFFER;
    
  • Running the App Exec Link target of wt.exe...

    "C:\Program Files\WindowsApps\Microsoft.WindowsTerminal_1.4.3243.0_x64__8wekyb3d8bbwe\wt.exe"
    

    ... works, and starts the Windows Terminal.

    But running the App Exec Link target of MicrosoftEdge.exe...

    "C:\WINDOWS\system32\SystemUWPLauncher.exe"
    

    ... does NOTHING.

    ⇒ The other parameters in the reparse data are somehow important. (But I don't know how to use them.)

  • Running

    "%LOCALAPPDATA%\Microsoft\WindowsApps\MicrosoftEdge.exe"
    

    Then looking in Task Manager, I see that the real executable running for MS Edge is

    "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe"
    

    ⇒ This proves that the App Exec Link target program is not always the real program.

  • It is possible to start the target application using the entry point string in the reparse data, instead of the target pathname, using:

    explorer.exe shell:appsFolder\<REPARSE_POINT_ENTRY_POINT_NAME>
    

    Ex, this starts Microsoft Edge:

    explorer.exe shell:appsFolder\Microsoft.MicrosoftEdge_8wekyb3d8bbwe!MicrosoftEdge
    

    But this is not what I want. I'd really want to find how to start the application using the target application pathname AND the other two parameters.

Anyway, I've updated the readlink() routine in my MsvcLibX library to return the target of App Exec Links. All the tools in my System Tools Library that can handle symbolic links now show that target.
But as this target is obviously not the complete answer, I consider this current version as a makeshift implementation at best.

If anybody finds more information on this subject, I'm very interested!

Jeanniejeannine answered 5/1, 2021 at 17:28 Comment(5)
This is super helpfull! What I learned is that when you execute an AppExecLink, the CreateProcess function behaves different, for example adding the WIN:SYSAPPID attribute to the security token. Hence, launching the link target directly will never get the same result. I've used cmd /c wt.exe as a workaround. I still have to re-read your info and find the best way to tell if it's an AppExecLink.Lepine
You also gave me the idea to check the Get-ChildItem implementation in PowerShell, found interesting WinInternalGetTarget and WinInternalGetLinkType functions in FileSystemProvider.csLepine
Looking the PowerShell Core sources is indeed a good idea! Unrelated: I've corrected an error in my reply above: ReadReparsePointW() is an internal routine in my MsvcLibX library. The WIN32 API to use is DeviceIoControl().Wainscot
The best way to tell if it's an AppExecLink is first to run GetFileAttributesW() to check if it's a reparse point; Then if it is, do a DeviceIoControl(FSCTL_GET_REPARSE_POINT) to check if the reparse point tag is IO_REPARSE_TAG_APPEXECLINK.Wainscot
Incidentally, Microsoft has removed AppExecLink reparse point support from PowerShell as of a preview of 7.2.0.... They literally just ripped it out, with the output still showing that the files are links, with name -> but then just blank. I guess they didn't want people having this level of access to UWP/AppX magic, but it still sucks that PowerShell isn't immune to these politics.Snapshot
Q
3

You can also try adding Microsoft.PowerShell.5.1.ReferenceAssemblies nuget package to your project.

Then the following code works:

using System.Linq;
using System.Management.Automation;

string wtPath =
    Path.Combine(
        Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
        "Microsoft",
        "WindowsApps",
        "wt.exe"
    );

using (var ps = PowerShell.Create())
{
    var psObject = ps.AddCommand("Get-Item").AddParameter("Path", wtPath).Invoke().Single();
    var psPropInfo = psObject.Properties["Target"];

    string[] targets = (psPropInfo.Value as List<string>).ToArray();

    Console.WriteLine($"[{psPropInfo.Name}]=[{String.Join("; ", targets)}]");
}
Quitrent answered 19/6, 2020 at 17:14 Comment(1)
Thanks Gabor. I think this will solve the problem for future visitors and deserves an upvote. But given that I need something faster than starting a PowerShell instance, I will not mark it as the accepted answer. Also, in a similar fashion, I could start a process pwsh -NoProfile -c '(Get-Item wt.exe).Target' and get the result.Lepine
G
1

Thought I would also add this related thread on how this ReparsePoint link type is used by UWP exes and the WindowsApp store.

tl;dr;
The ~\AppData\Local\Microsoft\WindowsApps\ directory has EXEs that are using a special form of IO_REPARSE_TAG_APPEXECLINK symlink to reference the actual UWP pkg located in C:/Program Files/WindowsApps/.

Gobi answered 31/5, 2021 at 5:12 Comment(0)
S
0

Some app execution links are in Registry: Computer\HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths

Stroud answered 17/6 at 21:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.