tl;dr
$env:LOCALAPPDATA\Microsoft\WindowsApps\wt.exe
is an AppX reparse point aka app execution alias aka (internally) AppExecLink
, a Microsoft-specific NTFS reparse point, used with UWP / Microsoft Store applications as entry points, i.e. the executables that launch them.
- Hard links are unrelated, both conceptually and technically.
While such a reparse point's data does point to another file - similar to a symbolic link - the data is considered an undocumented implementation detail by Microsoft, subject to change.[1]
Therefore, while you technically can parse that data, you shouldn't, or at least you shouldn't rely on it in production code, as there is no guarantee of long-term stability.
Such reparse points can be executed like any other executable, which is usually sufficient.
While you may want to know which different executable is ultimately launched behind the scenes, static analysis of the reparse data isn't guaranteed to give you that information, as in some cases a generic launcher executable is used, which opaquely launches the ultimate target executable (see below for details). In other words: the target executable stored in the reparse data isn't necessarily the real target executable.
Resolving an AppX reparse point to its target executable is deeply embedded in the system, namely in the CreateProcess
family of WinAPI functions. You need runtime analysis to see what executable a given launched AppX reparse point ends up invoking; a simple way to is to invoke interactively and look for the resulting process in Task Manager; this comment on GitHub shows a complex programmatic approach based on the QueryFullProcessImageName
WinAPI function
How do you follow a hard link (reparse point?) to a file?
A hard link is not a reparse point.
Hard links, in fact, do not point to other file paths, the way that symbolic links do, for instance.
Instead, a group of related hard links directly point to the same file data, without any relationship between their paths.
However, unlike on Unix-like platforms, the system APIs on Windows allow you to directly discover all paths pointing to the same file data, given one of those paths.
In Windows PowerShell, this array of related paths is exposed via the .Target
property that System.IO.FileInfo
instances emitted by Get-ChildItem
/ Get-Item
are decorated with.
Unfortunately, this was removed in PowerShell (Core) v6+ - see this answer for details.
By contrast, other forms of NTFS links - notably symbolic links (symlinks), but also the older junctions and volume mount point types - are reparse points. I'll refer to them collectively as symlinks below.
Reparse points are essentially just metadata attached to a file-system entry, and their use is open-ended; for instance, reparse points are also used to manage on-demand downloads of cloud-hosted OneDrive files.
In short: Symlinks are just one type of reparse point, and not every reparse point is a link.
The subclass of reparse points that publicly point to other file-system paths are known as name surrogates.
$env:LOCALAPPDATA\Microsoft\WindowsApps\wt.exe
is an example of yet another kind of reparse point: an AppX reparse point aka app execution alias aka (internally) AppExecLink
.
These are used by UWP applications, which notably includes all applications downloaded from the Microsoft Store.
While they are conceptually similar to symlinks, there are important differences:
The link target isn't necessarily the true target executable; it may just be the generic UWP launcher, C:\WINDOWS\system32\SystemUWPLauncher.exe
, such as in the case of Microsoft Edge.
More importantly, though, Microsoft considers the reparse data associated with app execution aliases (AppX reparse points) to be an undocumented implementation detail, subject to change.[1] In other words:
You should treat an app execution alias just like any other (regular, self-contained) executable and not as a link to another executable.
You shouldn't try to parse the reparse data yourself.
Of course, you can try to parse it yourself, but you cannot rely on the data format not to change.
- Note: Even when parsed correctly, the data doesn't tell you the full story, such as in the case of Microsoft Edge: the path of the executable that is ultimately used -
C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe
, presumably launched by C:\WINDOWS\system32\SystemUWPLauncher.exe
- cannot be gleaned from the reparse data (alone).[2]
In fact, PowerShell (Core) briefly implemented its own parsing, but removed it again later after internal feedback.
The since-removed code can be found here, which involves P/Invoke operations and is therefore not directly available in PowerShell (though you could try ad hoc-compilation of embedded C# code via Add-Type
).
As JosefZ's answer shows, you can also glean the raw byte data from the output from fsutil reparsepoint query
, but that is both less robust and slow.
Here's an alternative implementation - slightly more robust, but still slow; once defined, invoke it as follows:
Get-ChildItem -File $env:LOCALAPPDATA\Microsoft\WindowsApps | Resolve-AppXExePath
function Resolve-AppXExePath {
<#
.SYNOPSIS
Resolves AppX execution aliases to their app IDs and target paths.
.EXAMPLE
Get-ChildItem -File $env:LOCALAPPDATA\Microsoft\WindowsApps | Resolve-AppXExePath
.NOTES
This command is slow, because a call to fsutil.exe is made for each input path.
#>
param(
[Parameter(ValueFromPipeline)]
[Alias('PSPath')]
[string] $LiteralPath
)
process {
$fullName = Convert-Path -LiteralPath $LiteralPath
if (-not $?) { return }
# Get a hex-dump representation of the reparse-point data via fsutil reparsepoint query $fullName
$hexDump = fsutil reparsepoint query $fullName 2>&1
if ($LASTEXITCODE) { Write-Error $hexDump; return }
# Extract the raw bytes that make up the reparse-point data.
[byte[]] $bytes = -split (-join ($hexDump -match '^[a-f0-9]+:' -replace '^[a-f0-9]+:\s+(((?:[a-f0-9]{2}) +){1,16}).+$', '$1')) -replace '^', '0x'
# Convert the data to a UTF-16 string and split into fields by NUL bytes.
$props = [System.Text.Encoding]::Unicode.GetString($bytes) -split "`0"
# Output a custom object with the App ID (Package ID + entry-point name)
# and the target path (which may just be the universal UWP launcher)
[PSCustomObject] @{
AppId = $props[2]
Target = $props[3]
}
}
}
Sample output:
AppId Target
----- ------
Microsoft.XboxGamingOverlay_8wekyb3d8bbwe!App C:\Program Files\WindowsApps\Microsoft.XboxGamingOverlay_5.721.12013.0_x64__8wekyb3d8bbwe\GameBarElevatedFT.exe
Microsoft.MicrosoftEdge_8wekyb3d8bbwe!MicrosoftEdge C:\WINDOWS\system32\SystemUWPLauncher.exe
Microsoft.DesktopAppInstaller_8wekyb3d8bbwe!PythonRedirector C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_1.17.10271.0_x64__8wekyb3d8bbwe\AppInstallerPythonRedirector.exe
Microsoft.DesktopAppInstaller_8wekyb3d8bbwe!PythonRedirector C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_1.17.10271.0_x64__8wekyb3d8bbwe\AppInstallerPythonRedirector.exe
CanonicalGroupLimited.UbuntuonWindows_79rhkp1fndgsc!ubuntuonwindows C:\Program Files\WindowsApps\CanonicalGroupLimited.UbuntuonWindows_2004.2022.1.0_x64__79rhkp1fndgsc\ubuntu.exe
CanonicalGroupLimited.Ubuntu18.04onWindows_79rhkp1fndgsc!ubuntu1804 C:\Program Files\WindowsApps\CanonicalGroupLimited.Ubuntu18.04onWindows_1804.2020.824.0_x64__79rhkp1fndgsc\ubuntu1804.exe
Microsoft.DesktopAppInstaller_8wekyb3d8bbwe!winget C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_1.17.10271.0_x64__8wekyb3d8bbwe\winget.exe
Microsoft.WindowsTerminal_8wekyb3d8bbwe!App C:\Program Files\WindowsApps\Microsoft.WindowsTerminal_1.11.3471.0_x64__8wekyb3d8bbwe\wt.exe
[1] See this comment by the .NET team on GitHub.
[2] Whether the generic SystemUWPLauncher.exe
launcher is used may be related to whether a given application is a bona fide UWP application vs. a repackaged desktop application, via the Desktop Bridge. It is unclear to me how, when SystemUWPLauncher.exe
is used, the ultimate target executable is determined.
(Get-Item $env:LOCALAPPDATA\Microsoft\WindowsApps\wt.exe).Target
inpwsh.exe
(PSVersion7.1.5
) – TrevethickGet-ExecutionAlias
command that works. I can make that an answer if you like. It would be nice if we could isolate only the necessary code though. – OpiumStart-Process -FilePath $env:LOCALAPPDATA\Microsoft\WindowsApps\wt.exe
should work… – Trevethick