Created
August 28, 2026 02:15
-
-
Save jborean93/6a4b7e70bf67cea4f880554f5b641e36 to your computer and use it in GitHub Desktop.
Gets the App Exec Alias reparse data buffer into a structured value
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Copyright: (c) 2026, Jordan Borean (@jborean93) <jborean93@gmail.com> | |
| # MIT License (see LICENSE or https://opensource.org/licenses/MIT) | |
| function Get-AppExecAlias { | |
| <# | |
| .SYNOPSIS | |
| Gets the reparse data buffer details of an app exec alias. | |
| .PARAMETER Name | |
| Either the path to the app exec alias or the name of the alias to check. | |
| If set to just a filename, it will automatically be found under $env:LocalAppData\Microsoft\WindowsApps. | |
| .EXAMPLE | |
| Get-AppExecAlias -Name python.exe | |
| #> | |
| [CmdletBinding()] | |
| param ( | |
| [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] | |
| [string[]] | |
| [Alias('PSPath')] | |
| $Name | |
| ) | |
| process { | |
| foreach ($appName in $Name) { | |
| try { | |
| if (Split-Path -Path $appName -Parent) { | |
| # If provided an actual path with a parent dir, resolve it from that. | |
| $aliasPath = $appName | |
| } | |
| else { | |
| # Otherwise treat it as an app exec alias name. | |
| $aliasPath = [IO.Path]::Combine($env:LocalAppData, 'Microsoft', 'WindowsApps', $appName) | |
| if (-not $appName.EndsWith('.exe')) { | |
| $aliasPath = "$aliasPath.exe" | |
| } | |
| } | |
| if (-not (Test-Path -LiteralPath $aliasPath)) { | |
| throw "Alias path does not exist: ${aliasPath}" | |
| } | |
| $fileAttributes = [IO.File]::GetAttributes($aliasPath) | |
| if (-not $fileAttributes.HasFlag([IO.FileAttributes]::ReparsePoint)) { | |
| throw "Alias path is not a reparse point: ${aliasPath}" | |
| } | |
| $rawReparseDataOut = fsutil reparsepoint query $aliasPath | |
| if ($LASTEXITCODE) { | |
| throw "Failed to query reparse point for ${aliasPath}: $LASTEXITCODE" | |
| } | |
| $reparseTagType = $rawReparseDataOut | | |
| Where-Object { $_ -match 'Reparse Tag Value : (0x[a-f0-9]{8})' } | | |
| ForEach-Object { [Convert]::ToInt32($Matches[1], 16) } | | |
| Select-Object -First 1 | |
| if (-not $reparseTagType) { | |
| throw "Failed to get reparse tag type for ${aliasPath}" | |
| } | |
| if ($reparseTagType -ne 0x8000001b) { | |
| $tagHex = '{0:x8}' -f $reparseTagType | |
| throw "Reparse tag type is not an app exec alias for ${aliasPath}: expected 0x8000001b but got 0x$tagHex" | |
| } | |
| [byte[]]$rpBytes = $rawReparseDataOut | | |
| Where-Object { $_ -match '^[0-9a-fA-F]{4}:\s+((?:[0-9a-fA-F]{2}\s*)+)' } | | |
| ForEach-Object { | |
| $Matches[1] -split '\s+' | | |
| Where-Object { $_ } | | |
| ForEach-Object { [Convert]::ToByte($_, 16) } | |
| } | |
| # The reparse point data is a 4 byte version followed by null | |
| # terminated strings for the package, entry point, executable, | |
| # and app type. | |
| $appVersion = [BitConverter]::ToInt32($rpBytes, 0) | |
| $entries = [Text.Encoding]::Unicode.GetString($rpBytes, 4, $rpBytes.Length - 4) -split "`0" | |
| # This should always be present but just in case. The app type | |
| # is a string representation of an integer. | |
| $appType = if ($entries.Length -gt 3) { | |
| switch ($entries[3].TrimEnd("`0")) { | |
| 0 { 'Desktop' } | |
| 1 { 'UWP1' } | |
| 2 { 'UWP2' } | |
| 3 { 'UWP3' } | |
| default { "Unknown type '$_'" } | |
| } | |
| } | |
| [PSCustomObject]@{ | |
| Path = $aliasPath | |
| Version = $appVersion | |
| PackageID = $entries[0] | |
| EntryPoint = $entries[1] | |
| Executable = $entries[2] | |
| AppType = $appType | |
| Raw = $rpBytes | |
| } | |
| } | |
| catch { | |
| $PSCmdlet.WriteError($_) | |
| } | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment