mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-07 19:57:07 +02:00
[PTRun][Program]Improve the icon shown for app execution aliases(#20358)
* Improve the icon shown for app execution aliases. * Reverse validation check. Resolve issue with null paths * Improve to utilise app execution alias reparse point. * Improve to utilise app execution alias reparse point. Co-authored-by: Mike Barker <mibarker@microsoft.com>
This commit is contained in:
@@ -15,6 +15,7 @@ using System.Text.RegularExpressions;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using Microsoft.Plugin.Program.Logger;
|
using Microsoft.Plugin.Program.Logger;
|
||||||
|
using Microsoft.Plugin.Program.Utils;
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
using Wox.Infrastructure;
|
using Wox.Infrastructure;
|
||||||
using Wox.Infrastructure.FileSystemHelper;
|
using Wox.Infrastructure.FileSystemHelper;
|
||||||
@@ -916,10 +917,46 @@ namespace Microsoft.Plugin.Program.Programs
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool TryGetIcoPathForRunCommandProgram(Win32Program program, out string icoPath)
|
||||||
|
{
|
||||||
|
if (program.AppType != ApplicationType.RunCommand)
|
||||||
|
{
|
||||||
|
icoPath = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(program.FullPath))
|
||||||
|
{
|
||||||
|
icoPath = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://msdn.microsoft.com/en-us/library/windows/desktop/ee872121
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var redirectionPath = ReparsePoint.GetTarget(program.FullPath);
|
||||||
|
icoPath = ExpandEnvironmentVariables(redirectionPath);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
ProgramLogger.Warn($"|Error whilst retrieving the redirection path from app execution alias {program.FullPath}", e, MethodBase.GetCurrentMethod().DeclaringType, program.FullPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
icoPath = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static Win32Program GetRunCommandProgramFromPath(string path)
|
private static Win32Program GetRunCommandProgramFromPath(string path)
|
||||||
{
|
{
|
||||||
var program = GetProgramFromPath(path);
|
var program = GetProgramFromPath(path);
|
||||||
program.AppType = ApplicationType.RunCommand;
|
program.AppType = ApplicationType.RunCommand;
|
||||||
|
|
||||||
|
if (TryGetIcoPathForRunCommandProgram(program, out var icoPath))
|
||||||
|
{
|
||||||
|
program.IcoPath = icoPath;
|
||||||
|
}
|
||||||
|
|
||||||
return program;
|
return program;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,384 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.Win32.SafeHandles;
|
||||||
|
using Windows.Storage.Streams;
|
||||||
|
using Wox.Plugin;
|
||||||
|
|
||||||
|
namespace Microsoft.Plugin.Program.Utils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to NTFS reparse points in .Net.
|
||||||
|
/// </summary>
|
||||||
|
public static class ReparsePoint
|
||||||
|
{
|
||||||
|
#pragma warning disable SA1310 // Field names should not contain underscore
|
||||||
|
|
||||||
|
private const int ERROR_NOT_A_REPARSE_POINT = 4390;
|
||||||
|
|
||||||
|
private const int ERROR_INSUFFICIENT_BUFFER = 122;
|
||||||
|
|
||||||
|
private const int ERROR_MORE_DATA = 234;
|
||||||
|
|
||||||
|
private const int FSCTL_GET_REPARSE_POINT = 0x000900A8;
|
||||||
|
|
||||||
|
private const uint IO_REPARSE_TAG_APPEXECLINK = 0x8000001B;
|
||||||
|
|
||||||
|
private const int MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16 * 1024;
|
||||||
|
|
||||||
|
private const int E_INVALID_PROTOCOL_FORMAT = unchecked((int)0x83760002);
|
||||||
|
#pragma warning restore SA1310 // Field names should not contain underscore
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
private enum FileAccessType : uint
|
||||||
|
{
|
||||||
|
DELETE = 0x00010000,
|
||||||
|
READ_CONTROL = 0x00020000,
|
||||||
|
WRITE_DAC = 0x00040000,
|
||||||
|
WRITE_OWNER = 0x00080000,
|
||||||
|
SYNCHRONIZE = 0x00100000,
|
||||||
|
|
||||||
|
STANDARD_RIGHTS_REQUIRED = 0x000F0000,
|
||||||
|
|
||||||
|
STANDARD_RIGHTS_READ = READ_CONTROL,
|
||||||
|
STANDARD_RIGHTS_WRITE = READ_CONTROL,
|
||||||
|
STANDARD_RIGHTS_EXECUTE = READ_CONTROL,
|
||||||
|
|
||||||
|
STANDARD_RIGHTS_ALL = 0x001F0000,
|
||||||
|
|
||||||
|
SPECIFIC_RIGHTS_ALL = 0x0000FFFF,
|
||||||
|
|
||||||
|
ACCESS_SYSTEM_SECURITY = 0x01000000,
|
||||||
|
|
||||||
|
MAXIMUM_ALLOWED = 0x02000000,
|
||||||
|
|
||||||
|
GENERIC_READ = 0x80000000,
|
||||||
|
GENERIC_WRITE = 0x40000000,
|
||||||
|
GENERIC_EXECUTE = 0x20000000,
|
||||||
|
GENERIC_ALL = 0x10000000,
|
||||||
|
|
||||||
|
FILE_READ_DATA = 0x0001,
|
||||||
|
FILE_WRITE_DATA = 0x0002,
|
||||||
|
FILE_APPEND_DATA = 0x0004,
|
||||||
|
FILE_READ_EA = 0x0008,
|
||||||
|
FILE_WRITE_EA = 0x0010,
|
||||||
|
FILE_EXECUTE = 0x0020,
|
||||||
|
FILE_READ_ATTRIBUTES = 0x0080,
|
||||||
|
FILE_WRITE_ATTRIBUTES = 0x0100,
|
||||||
|
|
||||||
|
FILE_ALL_ACCESS =
|
||||||
|
STANDARD_RIGHTS_REQUIRED |
|
||||||
|
SYNCHRONIZE
|
||||||
|
| 0x1FF,
|
||||||
|
|
||||||
|
FILE_GENERIC_READ =
|
||||||
|
STANDARD_RIGHTS_READ |
|
||||||
|
FILE_READ_DATA |
|
||||||
|
FILE_READ_ATTRIBUTES |
|
||||||
|
FILE_READ_EA |
|
||||||
|
SYNCHRONIZE,
|
||||||
|
|
||||||
|
FILE_GENERIC_WRITE =
|
||||||
|
STANDARD_RIGHTS_WRITE |
|
||||||
|
FILE_WRITE_DATA |
|
||||||
|
FILE_WRITE_ATTRIBUTES |
|
||||||
|
FILE_WRITE_EA |
|
||||||
|
FILE_APPEND_DATA |
|
||||||
|
SYNCHRONIZE,
|
||||||
|
|
||||||
|
FILE_GENERIC_EXECUTE =
|
||||||
|
STANDARD_RIGHTS_EXECUTE |
|
||||||
|
FILE_READ_ATTRIBUTES |
|
||||||
|
FILE_EXECUTE |
|
||||||
|
SYNCHRONIZE,
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
private enum FileShareType : uint
|
||||||
|
{
|
||||||
|
None = 0x00000000,
|
||||||
|
Read = 0x00000001,
|
||||||
|
Write = 0x00000002,
|
||||||
|
Delete = 0x00000004,
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum CreationDisposition : uint
|
||||||
|
{
|
||||||
|
New = 1,
|
||||||
|
CreateAlways = 2,
|
||||||
|
OpenExisting = 3,
|
||||||
|
OpenAlways = 4,
|
||||||
|
TruncateExisting = 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
private enum FileAttributes : uint
|
||||||
|
{
|
||||||
|
Readonly = 0x00000001,
|
||||||
|
Hidden = 0x00000002,
|
||||||
|
System = 0x00000004,
|
||||||
|
Directory = 0x00000010,
|
||||||
|
Archive = 0x00000020,
|
||||||
|
Device = 0x00000040,
|
||||||
|
Normal = 0x00000080,
|
||||||
|
Temporary = 0x00000100,
|
||||||
|
SparseFile = 0x00000200,
|
||||||
|
ReparsePoint = 0x00000400,
|
||||||
|
Compressed = 0x00000800,
|
||||||
|
Offline = 0x00001000,
|
||||||
|
NotContentIndexed = 0x00002000,
|
||||||
|
Encrypted = 0x00004000,
|
||||||
|
Write_Through = 0x80000000,
|
||||||
|
Overlapped = 0x40000000,
|
||||||
|
NoBuffering = 0x20000000,
|
||||||
|
RandomAccess = 0x10000000,
|
||||||
|
SequentialScan = 0x08000000,
|
||||||
|
DeleteOnClose = 0x04000000,
|
||||||
|
BackupSemantics = 0x02000000,
|
||||||
|
PosixSemantics = 0x01000000,
|
||||||
|
OpenReparsePoint = 0x00200000,
|
||||||
|
OpenNoRecall = 0x00100000,
|
||||||
|
FirstPipeInstance = 0x00080000,
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum AppExecutionAliasReparseTagBufferLayoutVersion : uint
|
||||||
|
{
|
||||||
|
Invalid = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initial version used package full name, aumid, exe path
|
||||||
|
/// </summary>
|
||||||
|
Initial = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This version replaces package full name with family name, to allow
|
||||||
|
/// optional packages to reference their main package across versions.
|
||||||
|
/// </summary>
|
||||||
|
PackageFamilyName = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This version appends a flag to the family Name version to differentiate
|
||||||
|
/// between UWP and Centennial
|
||||||
|
/// </summary>
|
||||||
|
MultiAppTypeSupport = 3,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used to check version validity, where valid is (Invalid, UpperBound)
|
||||||
|
/// </summary>
|
||||||
|
UpperBound,
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct AppExecutionAliasReparseTagHeader
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Reparse point tag.
|
||||||
|
/// </summary>
|
||||||
|
public uint ReparseTag;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Size, in bytes, of the data after the Reserved member.
|
||||||
|
/// </summary>
|
||||||
|
public ushort ReparseDataLength;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reserved; do not use.
|
||||||
|
/// </summary>
|
||||||
|
public ushort Reserved;
|
||||||
|
|
||||||
|
public AppExecutionAliasReparseTagBufferLayoutVersion Version;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||||
|
private static extern bool DeviceIoControl(
|
||||||
|
IntPtr hDevice,
|
||||||
|
uint dwIoControlCode,
|
||||||
|
IntPtr inBuffer,
|
||||||
|
int nInBufferSize,
|
||||||
|
IntPtr outBuffer,
|
||||||
|
int nOutBufferSize,
|
||||||
|
out int pBytesReturned,
|
||||||
|
IntPtr lpOverlapped);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||||
|
private static extern IntPtr CreateFile(
|
||||||
|
string lpFileName,
|
||||||
|
FileAccessType dwDesiredAccess,
|
||||||
|
FileShareType dwShareMode,
|
||||||
|
IntPtr lpSecurityAttributes,
|
||||||
|
CreationDisposition dwCreationDisposition,
|
||||||
|
FileAttributes dwFlagsAndAttributes,
|
||||||
|
IntPtr hTemplateFile);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the target of the specified reparse point.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reparsePoint">The path of the reparse point.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// The target of the reparse point.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="IOException">
|
||||||
|
/// Thrown when the reparse point specified is not a reparse point or is invalid.
|
||||||
|
/// </exception>
|
||||||
|
public static string GetTarget(string reparsePoint)
|
||||||
|
{
|
||||||
|
using (SafeFileHandle reparsePointHandle = new SafeFileHandle(
|
||||||
|
CreateFile(
|
||||||
|
reparsePoint,
|
||||||
|
FileAccessType.FILE_READ_ATTRIBUTES | FileAccessType.FILE_READ_EA,
|
||||||
|
FileShareType.Delete | FileShareType.Read | FileShareType.Write,
|
||||||
|
IntPtr.Zero,
|
||||||
|
CreationDisposition.OpenExisting,
|
||||||
|
FileAttributes.OpenReparsePoint,
|
||||||
|
IntPtr.Zero),
|
||||||
|
true))
|
||||||
|
{
|
||||||
|
if (Marshal.GetLastWin32Error() != 0)
|
||||||
|
{
|
||||||
|
ThrowLastWin32Error("Unable to open reparse point.");
|
||||||
|
}
|
||||||
|
|
||||||
|
int outBufferSize = 512;
|
||||||
|
IntPtr outBuffer = Marshal.AllocHGlobal(outBufferSize);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// For-loop allows an attempt with 512-bytes buffer, before retrying with a 'MAXIMUM_REPARSE_DATA_BUFFER_SIZE' bytes buffer.
|
||||||
|
for (int i = 0; i < 2; ++i)
|
||||||
|
{
|
||||||
|
int bytesReturned;
|
||||||
|
bool result = DeviceIoControl(
|
||||||
|
reparsePointHandle.DangerousGetHandle(),
|
||||||
|
FSCTL_GET_REPARSE_POINT,
|
||||||
|
IntPtr.Zero,
|
||||||
|
0,
|
||||||
|
outBuffer,
|
||||||
|
outBufferSize,
|
||||||
|
out bytesReturned,
|
||||||
|
IntPtr.Zero);
|
||||||
|
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
int error = Marshal.GetLastWin32Error();
|
||||||
|
if (error == ERROR_NOT_A_REPARSE_POINT)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((error == ERROR_INSUFFICIENT_BUFFER) || (error == ERROR_MORE_DATA))
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal(outBuffer);
|
||||||
|
outBuffer = IntPtr.Zero;
|
||||||
|
|
||||||
|
outBufferSize = MAXIMUM_REPARSE_DATA_BUFFER_SIZE;
|
||||||
|
outBuffer = Marshal.AllocHGlobal(outBufferSize);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ThrowLastWin32Error("Unable to get information about reparse point.");
|
||||||
|
}
|
||||||
|
|
||||||
|
AppExecutionAliasReparseTagHeader aliasReparseHeader = Marshal.PtrToStructure<AppExecutionAliasReparseTagHeader>(outBuffer);
|
||||||
|
|
||||||
|
if (aliasReparseHeader.ReparseTag == IO_REPARSE_TAG_APPEXECLINK)
|
||||||
|
{
|
||||||
|
var metadata = AppExecutionAliasMetadata.FromPersistedRepresentationIntPtr(
|
||||||
|
outBuffer,
|
||||||
|
aliasReparseHeader.Version);
|
||||||
|
|
||||||
|
return metadata.ExePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal(outBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AppExecutionAliasMetadata
|
||||||
|
{
|
||||||
|
public string PackageFullName { get; init; }
|
||||||
|
|
||||||
|
public string PackageFamilyName { get; init; }
|
||||||
|
|
||||||
|
public string Aumid { get; init; }
|
||||||
|
|
||||||
|
public string ExePath { get; init; }
|
||||||
|
|
||||||
|
public static AppExecutionAliasMetadata FromPersistedRepresentationIntPtr(IntPtr reparseDataBufferPtr, AppExecutionAliasReparseTagBufferLayoutVersion version)
|
||||||
|
{
|
||||||
|
var dataOffset = Marshal.SizeOf(typeof(AppExecutionAliasReparseTagHeader));
|
||||||
|
IntPtr dataBufferPtr = reparseDataBufferPtr + dataOffset;
|
||||||
|
|
||||||
|
string packageFullName = null;
|
||||||
|
string packageFamilyName = null;
|
||||||
|
string aumid = null;
|
||||||
|
string exePath = null;
|
||||||
|
|
||||||
|
VerifyVersion(version);
|
||||||
|
|
||||||
|
switch (version)
|
||||||
|
{
|
||||||
|
case AppExecutionAliasReparseTagBufferLayoutVersion.Initial:
|
||||||
|
packageFullName = Marshal.PtrToStringUni(dataBufferPtr);
|
||||||
|
dataBufferPtr += Encoding.Unicode.GetByteCount(packageFullName) + Encoding.Unicode.GetByteCount("\0");
|
||||||
|
|
||||||
|
aumid = Marshal.PtrToStringUni(dataBufferPtr);
|
||||||
|
dataBufferPtr += Encoding.Unicode.GetByteCount(aumid) + Encoding.Unicode.GetByteCount("\0");
|
||||||
|
|
||||||
|
exePath = Marshal.PtrToStringUni(dataBufferPtr);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AppExecutionAliasReparseTagBufferLayoutVersion.PackageFamilyName:
|
||||||
|
case AppExecutionAliasReparseTagBufferLayoutVersion.MultiAppTypeSupport:
|
||||||
|
packageFamilyName = Marshal.PtrToStringUni(dataBufferPtr);
|
||||||
|
dataBufferPtr += Encoding.Unicode.GetByteCount(packageFamilyName) + Encoding.Unicode.GetByteCount("\0");
|
||||||
|
|
||||||
|
aumid = Marshal.PtrToStringUni(dataBufferPtr);
|
||||||
|
dataBufferPtr += Encoding.Unicode.GetByteCount(aumid) + Encoding.Unicode.GetByteCount("\0");
|
||||||
|
|
||||||
|
exePath = Marshal.PtrToStringUni(dataBufferPtr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AppExecutionAliasMetadata
|
||||||
|
{
|
||||||
|
PackageFullName = packageFullName,
|
||||||
|
PackageFamilyName = packageFamilyName,
|
||||||
|
Aumid = aumid,
|
||||||
|
ExePath = exePath,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void VerifyVersion(AppExecutionAliasReparseTagBufferLayoutVersion version)
|
||||||
|
{
|
||||||
|
uint uintVersion = (uint)version;
|
||||||
|
|
||||||
|
if (uintVersion > (uint)AppExecutionAliasReparseTagBufferLayoutVersion.Invalid &&
|
||||||
|
uintVersion < (uint)AppExecutionAliasReparseTagBufferLayoutVersion.UpperBound)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IOException("Invalid app execution alias reparse version.", E_INVALID_PROTOCOL_FORMAT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ThrowLastWin32Error(string message)
|
||||||
|
{
|
||||||
|
throw new IOException(message, Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user