mirror of
https://github.com/microsoft/PowerToys.git
synced 2024-11-24 04:12:32 +08: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:
parent
789312c6ce
commit
28e6545f6f
@ -15,6 +15,7 @@ using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Microsoft.Plugin.Program.Logger;
|
||||
using Microsoft.Plugin.Program.Utils;
|
||||
using Microsoft.Win32;
|
||||
using Wox.Infrastructure;
|
||||
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)
|
||||
{
|
||||
var program = GetProgramFromPath(path);
|
||||
program.AppType = ApplicationType.RunCommand;
|
||||
|
||||
if (TryGetIcoPathForRunCommandProgram(program, out var icoPath))
|
||||
{
|
||||
program.IcoPath = icoPath;
|
||||
}
|
||||
|
||||
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()));
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user