Skip to content

Instantly share code, notes, and snippets.

@glopesdev
Created February 15, 2023 18:43
Show Gist options
  • Select an option

  • Save glopesdev/47e4d87e0eea7d8154e806713a6d975c to your computer and use it in GitHub Desktop.

Select an option

Save glopesdev/47e4d87e0eea7d8154e806713a6d975c to your computer and use it in GitHub Desktop.
Resolve UWP package reparse point metadata
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using System.IO;
/// Resolve UWP package reparse point metadata (IO_REPARSE_TAG_APPEXECLINK)
/// inspired by: https://stackoverflow.com/a/65583702/628228
internal static class AppExecLinkHelper
{
const string Kernel32 = "kernel32.dll";
const int MAX_REPARSE_SIZE = 0x4000;
const uint FILE_GENERIC_READ = 0x80000000;
const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
const uint FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000;
const uint IO_REPARSE_TAG_APPEXECLINK = 0x8000001B;
const int FSCTL_GET_REPARSE_POINT = 0x900A8;
public static string GetReparsePointTarget(string path)
{
using (var handle = OpenReparsePoint(path))
{
var buffer = GetReparsePointData(handle);
var reparseTag = BitConverter.ToUInt32(buffer, 0);
if (reparseTag == IO_REPARSE_TAG_APPEXECLINK)
{
// from REPARSE_GUID_DATA_BUFFER (winnt.h)
// (0) DWORD ReparseTag;
// (4) WORD ReparseDataLength;
// (6) WORD Reserved;
// (8) ULONG ReparseGuid;
// (12) WCHAR DataBuffer;
var reparseDataLength = BitConverter.ToUInt16(buffer, 4) - 4;
var reparseDataOffset = buffer.Length - reparseDataLength;
var reparseData = GetStringListFromBuffer(buffer, reparseDataOffset, reparseDataLength);
if (reparseData.Count > 2 && path.Contains(reparseData[0]))
{
return reparseData[2];
}
}
return path;
}
}
static byte[] GetReparsePointData(SafeFileHandle hReparsePoint)
{
var buffer = new byte[MAX_REPARSE_SIZE];
if (!DeviceIoControl(
hReparsePoint,
FSCTL_GET_REPARSE_POINT,
null, 0,
buffer, MAX_REPARSE_SIZE,
out uint bytesReturned,
IntPtr.Zero))
{
var errorCode = Marshal.GetHRForLastWin32Error();
Marshal.ThrowExceptionForHR(errorCode);
}
Array.Resize(ref buffer, (int)bytesReturned);
return buffer;
}
static SafeFileHandle OpenReparsePoint(string path)
{
var handle = CreateFile(
path,
FILE_GENERIC_READ,
FileShare.Read,
IntPtr.Zero,
FileMode.Open,
FILE_FLAG_BACKUP_SEMANTICS |
FILE_FLAG_OPEN_REPARSE_POINT,
IntPtr.Zero);
if (handle.IsInvalid)
{
var errorCode = Marshal.GetHRForLastWin32Error();
Marshal.ThrowExceptionForHR(errorCode);
}
return handle;
}
private static List<string> GetStringListFromBuffer(byte[] buffer, int offset, int length)
{
var list = new List<string>();
char[] chars = new char[length];
Buffer.BlockCopy(buffer, offset, chars, 0, length);
offset = 0;
var len = 0;
for (int i = 0; i < length; i++, len++)
{
if (chars[i] == '\0')
{
if (--len == 0) break;
list.Add(new string(chars, offset, len));
offset = i + 1;
len = 0;
}
}
return list;
}
[DllImport(Kernel32, SetLastError = true, CharSet = CharSet.Unicode)]
static extern SafeFileHandle CreateFile(
string lpFileName,
uint dwDesiredAccess,
[MarshalAs(UnmanagedType.U4)] FileShare dwShareMode,
IntPtr lpSecurityAttributes,
[MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition,
uint dwFlagsAndAttributes,
IntPtr hTemplateFile);
[DllImport(Kernel32, SetLastError = true, CharSet = CharSet.Unicode)]
static extern bool DeviceIoControl(
SafeHandle hDevice,
uint dwIoControlCode,
[In] byte[] lpInBuffer,
uint nInBufferSize,
[Out] byte[] lpOutBuffer,
uint nOutBufferSize,
[Out] out uint lpBytesReturned,
[In] IntPtr lpOverlapped);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment