Rename the [Ee]xts dir to ext (#38852)

**WARNING:** This PR will probably blow up all in-flight PRs

at some point in the early days of CmdPal, two of us created seperate
`Exts` and `exts` dirs. Depending on what the casing was on the branch
that you checked one of those out from, it'd get stuck like that on your
PC forever.

Windows didn't care, so we never noticed.

But GitHub does care, and now browsing the source on GitHub is basically
impossible.

Closes #38081
This commit is contained in:
Mike Griese
2025-04-15 06:07:22 -05:00
committed by GitHub
parent 60f50d853b
commit 2b5181b4c9
379 changed files with 35 additions and 35 deletions

View File

@@ -0,0 +1,14 @@
// 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.Runtime.InteropServices;
namespace Microsoft.CmdPal.Ext.Apps.Programs;
// Reference : https://stackoverflow.com/questions/32122679/getting-icon-of-modern-windows-app-from-a-desktop-application
[Guid("5842a140-ff9f-4166-8f5c-62f5b7b0c781")]
[ComImport]
public class AppxFactory
{
}

View File

@@ -0,0 +1,45 @@
// 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.Collections.Generic;
using System.Runtime.InteropServices;
using Windows.Win32.System.Com;
using static Microsoft.CmdPal.Ext.Apps.Utils.Native;
namespace Microsoft.CmdPal.Ext.Apps.Programs;
public static class AppxPackageHelper
{
private static readonly IAppxFactory AppxFactory = (IAppxFactory)new AppxFactory();
// This function returns a list of attributes of applications
internal static IEnumerable<IAppxManifestApplication> GetAppsFromManifest(IStream stream)
{
var reader = AppxFactory.CreateManifestReader(stream);
var manifestApps = reader.GetApplications();
while (manifestApps.GetHasCurrent())
{
var manifestApp = manifestApps.GetCurrent();
var hr = manifestApp.GetStringValue("AppListEntry", out var appListEntry);
_ = CheckHRAndReturnOrThrow(hr, appListEntry);
if (appListEntry != "none")
{
yield return manifestApp;
}
manifestApps.MoveNext();
}
}
internal static T CheckHRAndReturnOrThrow<T>(HRESULT hr, T result)
{
if (hr != HRESULT.S_OK)
{
Marshal.ThrowExceptionForHR((int)hr);
}
return result;
}
}

View File

@@ -0,0 +1,9 @@
// 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.
namespace Microsoft.CmdPal.Ext.Apps.Programs;
public class DisabledProgramSource : ProgramSource
{
}

View File

@@ -0,0 +1,47 @@
// 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.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Microsoft.CmdPal.Ext.Apps.Programs;
// Reference : https://github.com/MicrosoftEdge/edge-launcher/blob/108e63df0b4cb5cd9d5e45aa7a264690851ec51d/MIcrosoftEdgeLauncherCsharp/Program.cs
[Flags]
public enum ActivateOptions
{
None = 0x00000000,
DesignMode = 0x00000001,
NoErrorUI = 0x00000002,
NoSplashScreen = 0x00000004,
}
// ApplicationActivationManager
[ComImport]
[Guid("2e941141-7f97-4756-ba1d-9decde894a3d")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IApplicationActivationManager
{
IntPtr ActivateApplication([In] string appUserModelId, [In] string arguments, [In] ActivateOptions options, [Out] out uint processId);
IntPtr ActivateForFile([In] string appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] string verb, [Out] out uint processId);
IntPtr ActivateForProtocol([In] string appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out uint processId);
}
// Application Activation Manager Class
[ComImport]
[Guid("45BA127D-10A8-46EA-8AB7-56EA9078943C")]
public class ApplicationActivationManager : IApplicationActivationManager
{
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)/*, PreserveSig*/]
public extern IntPtr ActivateApplication([In] string appUserModelId, [In] string arguments, [In] ActivateOptions options, [Out] out uint processId);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
public extern IntPtr ActivateForFile([In] string appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] string verb, [Out] out uint processId);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
public extern IntPtr ActivateForProtocol([In] string appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out uint processId);
}

View File

@@ -0,0 +1,20 @@
// 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.Runtime.InteropServices;
using Windows.Win32.System.Com;
namespace Microsoft.CmdPal.Ext.Apps.Programs;
[Guid("BEB94909-E451-438B-B5A7-D79E767B75D8")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IAppxFactory
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "Implements COM Interface")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Implements COM Interface")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Implements COM Interface")]
void _VtblGap0_2(); // skip 2 methods
internal IAppxManifestReader CreateManifestReader(IStream inputStream);
}

View File

@@ -0,0 +1,19 @@
// 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.Runtime.InteropServices;
using static Microsoft.CmdPal.Ext.Apps.Utils.Native;
namespace Microsoft.CmdPal.Ext.Apps.Programs;
[Guid("5DA89BF4-3773-46BE-B650-7E744863B7E8")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IAppxManifestApplication
{
[PreserveSig]
HRESULT GetStringValue([MarshalAs(UnmanagedType.LPWStr)] string name, [MarshalAs(UnmanagedType.LPWStr)] out string value);
[PreserveSig]
HRESULT GetAppUserModelId([MarshalAs(UnmanagedType.LPWStr)] out string value);
}

View File

@@ -0,0 +1,19 @@
// 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.Runtime.InteropServices;
namespace Microsoft.CmdPal.Ext.Apps.Programs;
[Guid("9EB8A55A-F04B-4D0D-808D-686185D4847A")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IAppxManifestApplicationsEnumerator
{
IAppxManifestApplication GetCurrent();
bool GetHasCurrent();
bool MoveNext();
}

View File

@@ -0,0 +1,19 @@
// 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.Runtime.InteropServices;
using static Microsoft.CmdPal.Ext.Apps.Utils.Native;
namespace Microsoft.CmdPal.Ext.Apps.Programs;
[Guid("03FAF64D-F26F-4B2C-AAF7-8FE7789B8BCA")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IAppxManifestProperties
{
[PreserveSig]
HRESULT GetBoolValue([MarshalAs(UnmanagedType.LPWStr)] string name, out bool value);
[PreserveSig]
HRESULT GetStringValue([MarshalAs(UnmanagedType.LPWStr)] string name, [MarshalAs(UnmanagedType.LPWStr)] out string value);
}

View File

@@ -0,0 +1,27 @@
// 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.Runtime.InteropServices;
namespace Microsoft.CmdPal.Ext.Apps.Programs;
[Guid("4E1BD148-55A0-4480-A3D1-15544710637C")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IAppxManifestReader
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "Implements COM Interface")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Implements COM Interface")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Implements COM Interface")]
void _VtblGap0_1(); // skip 1 method
IAppxManifestProperties GetProperties();
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "Implements COM Interface")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Implements COM Interface")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Implements COM Interface")]
void _VtblGap1_5(); // skip 5 methods
IAppxManifestApplicationsEnumerator GetApplications();
}

View File

@@ -0,0 +1,44 @@
// 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.Diagnostics;
using System.IO.Abstractions;
namespace Microsoft.CmdPal.Ext.Apps.Programs;
public interface IFileVersionInfoWrapper
{
FileVersionInfo? GetVersionInfo(string path);
string FileDescription { get; set; }
}
public class FileVersionInfoWrapper : IFileVersionInfoWrapper
{
private readonly IFile _file;
public FileVersionInfoWrapper()
: this(new FileSystem().File)
{
}
public FileVersionInfoWrapper(IFile file)
{
_file = file;
}
public FileVersionInfo? GetVersionInfo(string path)
{
if (_file.Exists(path))
{
return FileVersionInfo.GetVersionInfo(path);
}
else
{
return null;
}
}
public string FileDescription { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,20 @@
// 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.
namespace Microsoft.CmdPal.Ext.Apps.Programs;
public interface IPackage
{
string Name { get; }
string FullName { get; }
string FamilyName { get; }
bool IsFramework { get; }
bool IsDevelopmentMode { get; }
string InstalledLocation { get; }
}

View File

@@ -0,0 +1,17 @@
// 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 Windows.ApplicationModel;
using Windows.Foundation;
namespace Microsoft.CmdPal.Ext.Apps.Programs;
internal interface IPackageCatalog
{
event TypedEventHandler<PackageCatalog, PackageInstallingEventArgs> PackageInstalling;
event TypedEventHandler<PackageCatalog, PackageUninstallingEventArgs> PackageUninstalling;
event TypedEventHandler<PackageCatalog, PackageUpdatingEventArgs> PackageUpdating;
}

View File

@@ -0,0 +1,12 @@
// 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.Collections.Generic;
namespace Microsoft.CmdPal.Ext.Apps.Programs;
public interface IPackageManager
{
IEnumerable<IPackage> FindPackagesForCurrentUser();
}

View File

@@ -0,0 +1,18 @@
// 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.
namespace Microsoft.CmdPal.Ext.Apps.Programs;
public interface IProgram
{
string UniqueIdentifier { get; set; }
string Name { get; }
string Description { get; set; }
string Location { get; }
bool Enabled { get; set; }
}

View File

@@ -0,0 +1,12 @@
// 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.
namespace Microsoft.CmdPal.Ext.Apps.Programs;
public enum LogoType
{
Error,
Colored,
HighContrast,
}

View File

@@ -0,0 +1,61 @@
// 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 Windows.ApplicationModel;
using Windows.Foundation;
namespace Microsoft.CmdPal.Ext.Apps.Programs;
internal sealed class PackageCatalogWrapper : IPackageCatalog
{
private PackageCatalog _packageCatalog;
public PackageCatalogWrapper()
{
// Opens the catalog of packages that is available for the current user.
_packageCatalog = PackageCatalog.OpenForCurrentUser();
}
// Summary: Indicates that an app package is installing.
public event TypedEventHandler<PackageCatalog, PackageInstallingEventArgs> PackageInstalling
{
add
{
_packageCatalog.PackageInstalling += value;
}
remove
{
_packageCatalog.PackageInstalling -= value;
}
}
// Summary: Indicates that an app package is uninstalling.
public event TypedEventHandler<PackageCatalog, PackageUninstallingEventArgs> PackageUninstalling
{
add
{
_packageCatalog.PackageUninstalling += value;
}
remove
{
_packageCatalog.PackageUninstalling -= value;
}
}
// Summary: Indicates that an app package is updating.
public event TypedEventHandler<PackageCatalog, PackageUpdatingEventArgs> PackageUpdating
{
add
{
_packageCatalog.PackageUpdating += value;
}
remove
{
_packageCatalog.PackageUpdating -= value;
}
}
}

View File

@@ -0,0 +1,34 @@
// 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.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using Windows.Management.Deployment;
namespace Microsoft.CmdPal.Ext.Apps.Programs;
public class PackageManagerWrapper : IPackageManager
{
private readonly PackageManager _packageManager;
public PackageManagerWrapper()
{
_packageManager = new PackageManager();
}
public IEnumerable<IPackage> FindPackagesForCurrentUser()
{
var user = WindowsIdentity.GetCurrent().User;
if (user != null)
{
var pkgs = _packageManager.FindPackagesForUser(user.Value);
return pkgs.Select(PackageWrapper.GetWrapperFromPackage).Where(package => package != null);
}
return Enumerable.Empty<IPackage>();
}
}

View File

@@ -0,0 +1,75 @@
// 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.IO;
using Windows.Foundation.Metadata;
using Package = Windows.ApplicationModel.Package;
namespace Microsoft.CmdPal.Ext.Apps.Programs;
public class PackageWrapper : IPackage
{
public string Name { get; } = string.Empty;
public string FullName { get; } = string.Empty;
public string FamilyName { get; } = string.Empty;
public bool IsFramework { get; }
public bool IsDevelopmentMode { get; }
public string InstalledLocation { get; } = string.Empty;
public PackageWrapper()
{
}
public PackageWrapper(string name, string fullName, string familyName, bool isFramework, bool isDevelopmentMode, string installedLocation)
{
Name = name;
FullName = fullName;
FamilyName = familyName;
IsFramework = isFramework;
IsDevelopmentMode = isDevelopmentMode;
InstalledLocation = installedLocation;
}
private static readonly Lazy<bool> IsPackageDotInstallationPathAvailable = new(() =>
ApiInformation.IsPropertyPresent(typeof(Package).FullName, nameof(Package.InstalledLocation.Path)));
public static PackageWrapper GetWrapperFromPackage(Package package)
{
ArgumentNullException.ThrowIfNull(package);
string path;
try
{
path = IsPackageDotInstallationPathAvailable.Value ? GetInstalledPath(package) : package.InstalledLocation.Path;
}
catch (Exception e) when (e is ArgumentException || e is FileNotFoundException || e is DirectoryNotFoundException)
{
return new PackageWrapper(
package.Id.Name,
package.Id.FullName,
package.Id.FamilyName,
package.IsFramework,
package.IsDevelopmentMode,
string.Empty);
}
return new PackageWrapper(
package.Id.Name,
package.Id.FullName,
package.Id.FamilyName,
package.IsFramework,
package.IsDevelopmentMode,
path);
}
// This is a separate method so the reference to .InstalledPath won't be loaded in API versions which do not support this API (e.g. older then Build 19041)
private static string GetInstalledPath(Package package)
=> package.InstalledLocation.Path;
}

View File

@@ -0,0 +1,24 @@
// 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.
namespace Microsoft.CmdPal.Ext.Apps.Programs;
/// <summary>
/// Contains user added folder location contents as well as all user disabled applications
/// </summary>
/// <remarks>
/// <para>Win32 class applications set UniqueIdentifier using their full file path</para>
/// <para>UWP class applications set UniqueIdentifier using their Application User Model ID</para>
/// <para>Custom user added program sources set UniqueIdentifier using their location</para>
/// </remarks>
public class ProgramSource
{
public string Location { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public bool Enabled { get; set; } = true;
public string UniqueIdentifier { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,397 @@
// 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;
namespace Microsoft.CmdPal.Ext.Apps.Programs;
/// <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.");
}
var outBufferSize = 512;
var 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 (var i = 0; i < 2; ++i)
{
int bytesReturned;
var result = DeviceIoControl(
reparsePointHandle.DangerousGetHandle(),
FSCTL_GET_REPARSE_POINT,
IntPtr.Zero,
0,
outBuffer,
outBufferSize,
out bytesReturned,
IntPtr.Zero);
if (!result)
{
var 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 sealed class AppExecutionAliasMetadata
{
public string PackageFullName { get; init; } = string.Empty;
public string PackageFamilyName { get; init; } = string.Empty;
public string Aumid { get; init; } = string.Empty;
public string ExePath { get; init; } = string.Empty;
public static AppExecutionAliasMetadata FromPersistedRepresentationIntPtr(IntPtr reparseDataBufferPtr, AppExecutionAliasReparseTagBufferLayoutVersion version)
{
var dataOffset = Marshal.SizeOf(typeof(AppExecutionAliasReparseTagHeader));
var 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);
if (packageFullName is not null)
{
dataBufferPtr += Encoding.Unicode.GetByteCount(packageFullName) + Encoding.Unicode.GetByteCount("\0");
aumid = Marshal.PtrToStringUni(dataBufferPtr);
if (aumid is not null)
{
dataBufferPtr += Encoding.Unicode.GetByteCount(aumid) + Encoding.Unicode.GetByteCount("\0");
exePath = Marshal.PtrToStringUni(dataBufferPtr);
}
}
break;
case AppExecutionAliasReparseTagBufferLayoutVersion.PackageFamilyName:
case AppExecutionAliasReparseTagBufferLayoutVersion.MultiAppTypeSupport:
packageFamilyName = Marshal.PtrToStringUni(dataBufferPtr);
if (packageFamilyName is not null)
{
dataBufferPtr += Encoding.Unicode.GetByteCount(packageFamilyName) + Encoding.Unicode.GetByteCount("\0");
aumid = Marshal.PtrToStringUni(dataBufferPtr);
if (aumid is not null)
{
dataBufferPtr += Encoding.Unicode.GetByteCount(aumid) + Encoding.Unicode.GetByteCount("\0");
exePath = Marshal.PtrToStringUni(dataBufferPtr);
}
}
break;
}
return new AppExecutionAliasMetadata
{
PackageFullName = packageFullName ?? string.Empty,
PackageFamilyName = packageFamilyName ?? string.Empty,
Aumid = aumid ?? string.Empty,
ExePath = exePath ?? string.Empty,
};
}
private static void VerifyVersion(AppExecutionAliasReparseTagBufferLayoutVersion version)
{
var 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()));
}
}

View File

@@ -0,0 +1,203 @@
// 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.Collections.Generic;
using System.IO.Abstractions;
using System.Linq;
using System.Xml.Linq;
using Microsoft.CmdPal.Ext.Apps.Utils;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.System.Com;
using static Microsoft.CmdPal.Ext.Apps.Utils.Native;
namespace Microsoft.CmdPal.Ext.Apps.Programs;
[Serializable]
public partial class UWP
{
private static readonly IPath Path = new FileSystem().Path;
private static readonly Dictionary<string, PackageVersion> _versionFromNamespace = new()
{
{ "http://schemas.microsoft.com/appx/manifest/foundation/windows10", PackageVersion.Windows10 },
{ "http://schemas.microsoft.com/appx/2013/manifest", PackageVersion.Windows81 },
{ "http://schemas.microsoft.com/appx/2010/manifest", PackageVersion.Windows8 },
};
public string Name { get; }
public string FullName { get; }
public string FamilyName { get; }
public string Location { get; set; } = string.Empty;
// Localized path based on windows display language
public string LocationLocalized { get; set; } = string.Empty;
public IList<UWPApplication> Apps { get; private set; } = new List<UWPApplication>();
public PackageVersion Version { get; set; }
public static IPackageManager PackageManagerWrapper { get; set; } = new PackageManagerWrapper();
public UWP(IPackage package)
{
ArgumentNullException.ThrowIfNull(package);
Name = package.Name;
FullName = package.FullName;
FamilyName = package.FamilyName;
}
public void InitializeAppInfo(string installedLocation)
{
Location = installedLocation;
LocationLocalized = ShellLocalization.Instance.GetLocalizedPath(installedLocation);
var path = Path.Combine(installedLocation, "AppxManifest.xml");
var namespaces = XmlNamespaces(path);
InitPackageVersion(namespaces);
const uint noAttribute = 0x80;
var access = (uint)STGM.READ;
var hResult = PInvoke.SHCreateStreamOnFileEx(path, access, noAttribute, false, null, out IStream stream);
// S_OK
if (hResult == 0)
{
Apps = AppxPackageHelper.GetAppsFromManifest(stream).Select(appInManifest => new UWPApplication(appInManifest, this)).Where(a =>
{
var valid =
!string.IsNullOrEmpty(a.UserModelId) &&
!string.IsNullOrEmpty(a.DisplayName) &&
a.AppListEntry != "none";
return valid;
}).ToList();
}
else
{
Apps = Array.Empty<UWPApplication>();
}
}
// http://www.hanselman.com/blog/GetNamespacesFromAnXMLDocumentWithXPathDocumentAndLINQToXML.aspx
private static string[] XmlNamespaces(string path)
{
var z = XDocument.Load(path);
if (z.Root != null)
{
var namespaces = z.Root.Attributes().
Where(a => a.IsNamespaceDeclaration).
GroupBy(
a => a.Name.Namespace == XNamespace.None ? string.Empty : a.Name.LocalName,
a => XNamespace.Get(a.Value)).Select(
g => g.First().ToString()).ToArray();
return namespaces;
}
else
{
return Array.Empty<string>();
}
}
private void InitPackageVersion(string[] namespaces)
{
foreach (var n in _versionFromNamespace.Keys.Where(namespaces.Contains))
{
Version = _versionFromNamespace[n];
return;
}
Version = PackageVersion.Unknown;
}
public static UWPApplication[] All()
{
var windows10 = new Version(10, 0);
var support = Environment.OSVersion.Version.Major >= windows10.Major;
if (support)
{
var applications = CurrentUserPackages().AsParallel().SelectMany(p =>
{
UWP u;
try
{
u = new UWP(p);
u.InitializeAppInfo(p.InstalledLocation);
}
catch (Exception )
{
return Array.Empty<UWPApplication>();
}
return u.Apps;
});
var updatedListWithoutDisabledApps = applications
.Where(t1 => AllAppsSettings.Instance.DisabledProgramSources.All(x => x.UniqueIdentifier != t1.UniqueIdentifier))
.Select(x => x);
return updatedListWithoutDisabledApps.ToArray();
}
else
{
return Array.Empty<UWPApplication>();
}
}
private static IEnumerable<IPackage> CurrentUserPackages()
{
return PackageManagerWrapper.FindPackagesForCurrentUser().Where(p =>
{
try
{
var f = p.IsFramework;
var path = p.InstalledLocation;
return !f && !string.IsNullOrEmpty(path);
}
catch (Exception )
{
return false;
}
});
}
public override string ToString()
{
return FamilyName;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1309:Use ordinal string comparison", Justification = "Using CurrentCultureIgnoreCase since this is used with FamilyName")]
public override bool Equals(object? obj)
{
if (obj is UWP uwp)
{
// Using CurrentCultureIgnoreCase since this is used with FamilyName
return FamilyName.Equals(uwp.FamilyName, StringComparison.CurrentCultureIgnoreCase);
}
else
{
return false;
}
}
public override int GetHashCode()
{
// Using CurrentCultureIgnoreCase since this is used with FamilyName
return FamilyName.GetHashCode(StringComparison.CurrentCultureIgnoreCase);
}
public enum PackageVersion
{
Windows10,
Windows81,
Windows8,
Unknown,
}
}

View File

@@ -0,0 +1,608 @@
// 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.Collections.Generic;
using System.IO.Abstractions;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Xml;
using Microsoft.CmdPal.Ext.Apps.Commands;
using Microsoft.CmdPal.Ext.Apps.Properties;
using Microsoft.CmdPal.Ext.Apps.Utils;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using static Microsoft.CmdPal.Ext.Apps.Utils.Native;
using PackageVersion = Microsoft.CmdPal.Ext.Apps.Programs.UWP.PackageVersion;
namespace Microsoft.CmdPal.Ext.Apps.Programs;
[Serializable]
public class UWPApplication : IProgram
{
private static readonly IFileSystem FileSystem = new FileSystem();
private static readonly IPath Path = FileSystem.Path;
private static readonly IFile File = FileSystem.File;
public string AppListEntry { get; set; } = string.Empty;
public string UniqueIdentifier { get; set; }
public string DisplayName { get; set; }
public string Description { get; set; }
public string UserModelId { get; set; }
public string BackgroundColor { get; set; }
public string EntryPoint { get; set; }
public string Name => DisplayName;
public string Location => Package.Location;
// Localized path based on windows display language
public string LocationLocalized => Package.LocationLocalized;
public bool Enabled { get; set; }
public bool CanRunElevated { get; set; }
public string LogoPath { get; set; } = string.Empty;
public LogoType LogoType { get; set; }
public UWP Package { get; set; }
private string logoUri;
private const string ContrastWhite = "contrast-white";
private const string ContrastBlack = "contrast-black";
// Function to set the subtitle based on the Type of application
public static string Type()
{
return Resources.packaged_application;
}
public List<CommandContextItem> GetCommands()
{
List<CommandContextItem> commands = new List<CommandContextItem>();
if (CanRunElevated)
{
commands.Add(
new CommandContextItem(
new RunAsAdminCommand(UniqueIdentifier, string.Empty, true)));
// We don't add context menu to 'run as different user', because UWP applications normally installed per user and not for all users.
}
commands.Add(
new CommandContextItem(
new OpenPathCommand(Location)
{
Name = Resources.open_containing_folder,
Icon = new("\ue838"),
}));
commands.Add(
new CommandContextItem(
new OpenInConsoleCommand(Package.Location)));
return commands;
}
public UWPApplication(IAppxManifestApplication manifestApp, UWP package)
{
ArgumentNullException.ThrowIfNull(manifestApp);
var hr = manifestApp.GetAppUserModelId(out var tmpUserModelId);
UserModelId = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpUserModelId);
hr = manifestApp.GetAppUserModelId(out var tmpUniqueIdentifier);
UniqueIdentifier = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpUniqueIdentifier);
hr = manifestApp.GetStringValue("DisplayName", out var tmpDisplayName);
DisplayName = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpDisplayName);
hr = manifestApp.GetStringValue("Description", out var tmpDescription);
Description = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpDescription);
hr = manifestApp.GetStringValue("BackgroundColor", out var tmpBackgroundColor);
BackgroundColor = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpBackgroundColor);
hr = manifestApp.GetStringValue("EntryPoint", out var tmpEntryPoint);
EntryPoint = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpEntryPoint);
Package = package ?? throw new ArgumentNullException(nameof(package));
DisplayName = ResourceFromPri(package.FullName, DisplayName);
Description = ResourceFromPri(package.FullName, Description);
logoUri = LogoUriFromManifest(manifestApp);
Enabled = true;
CanRunElevated = IfApplicationCanRunElevated();
}
private bool IfApplicationCanRunElevated()
{
if (EntryPoint == "Windows.FullTrustApplication")
{
return true;
}
else
{
var manifest = Package.Location + "\\AppxManifest.xml";
if (File.Exists(manifest))
{
try
{
// Check the manifest to verify if the Trust Level for the application is "mediumIL"
var file = File.ReadAllText(manifest);
var xmlDoc = new XmlDocument();
xmlDoc.LoadXml(file);
var xmlRoot = xmlDoc.DocumentElement;
var namespaceManager = new XmlNamespaceManager(xmlDoc.NameTable);
namespaceManager.AddNamespace("uap10", "http://schemas.microsoft.com/appx/manifest/uap/windows10/10");
var trustLevelNode = xmlRoot?.SelectSingleNode("//*[local-name()='Application' and @uap10:TrustLevel]", namespaceManager); // According to https://learn.microsoft.com/windows/apps/desktop/modernize/grant-identity-to-nonpackaged-apps#create-a-package-manifest-for-the-sparse-package and https://learn.microsoft.com/uwp/schemas/appxpackage/uapmanifestschema/element-application#attributes
if (trustLevelNode?.Attributes?["uap10:TrustLevel"]?.Value == "mediumIL")
{
return true;
}
}
catch (Exception)
{
}
}
}
return false;
}
internal string ResourceFromPri(string packageFullName, string resourceReference)
{
const string prefix = "ms-resource:";
// Using OrdinalIgnoreCase since this is used internally
if (!string.IsNullOrWhiteSpace(resourceReference) && resourceReference.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
// magic comes from @talynone
// https://github.com/talynone/Wox.Plugin.WindowsUniversalAppLauncher/blob/master/StoreAppLauncher/Helpers/NativeApiHelper.cs#L139-L153
var key = resourceReference.Substring(prefix.Length);
string parsed;
var parsedFallback = string.Empty;
// Using Ordinal/OrdinalIgnoreCase since these are used internally
if (key.StartsWith("//", StringComparison.Ordinal))
{
parsed = prefix + key;
}
else if (key.StartsWith('/'))
{
parsed = prefix + "//" + key;
}
else if (key.Contains("resources", StringComparison.OrdinalIgnoreCase))
{
parsed = prefix + key;
}
else
{
parsed = prefix + "///resources/" + key;
// e.g. for Windows Terminal version >= 1.12 DisplayName and Description resources are not in the 'resources' subtree
parsedFallback = prefix + "///" + key;
}
var outBuffer = new StringBuilder(128);
var source = $"@{{{packageFullName}? {parsed}}}";
var capacity = (uint)outBuffer.Capacity;
var hResult = SHLoadIndirectString(source, outBuffer, capacity, IntPtr.Zero);
if (hResult != HRESULT.S_OK)
{
if (!string.IsNullOrEmpty(parsedFallback))
{
var sourceFallback = $"@{{{packageFullName}? {parsedFallback}}}";
hResult = SHLoadIndirectString(sourceFallback, outBuffer, capacity, IntPtr.Zero);
if (hResult == HRESULT.S_OK)
{
var loaded = outBuffer.ToString();
if (!string.IsNullOrEmpty(loaded))
{
return loaded;
}
else
{
return string.Empty;
}
}
}
// https://github.com/Wox-launcher/Wox/issues/964
// known hresult 2147942522:
// 'Microsoft Corporation' violates pattern constraint of '\bms-resource:.{1,256}'.
// for
// Microsoft.MicrosoftOfficeHub_17.7608.23501.0_x64__8wekyb3d8bbwe: ms-resource://Microsoft.MicrosoftOfficeHub/officehubintl/AppManifest_GetOffice_Description
// Microsoft.BingFoodAndDrink_3.0.4.336_x64__8wekyb3d8bbwe: ms-resource:AppDescription
return string.Empty;
}
else
{
var loaded = outBuffer.ToString();
if (!string.IsNullOrEmpty(loaded))
{
return loaded;
}
else
{
return string.Empty;
}
}
}
else
{
return resourceReference;
}
}
private static readonly Dictionary<PackageVersion, string> _logoKeyFromVersion = new Dictionary<PackageVersion, string>
{
{ PackageVersion.Windows10, "Square44x44Logo" },
{ PackageVersion.Windows81, "Square30x30Logo" },
{ PackageVersion.Windows8, "SmallLogo" },
};
internal string LogoUriFromManifest(IAppxManifestApplication app)
{
if (_logoKeyFromVersion.TryGetValue(Package.Version, out var key))
{
var hr = app.GetStringValue(key, out var logoUriFromApp);
_ = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, logoUriFromApp);
return logoUriFromApp;
}
else
{
return string.Empty;
}
}
public void UpdateLogoPath(Theme theme)
{
LogoPathFromUri(logoUri, theme);
}
// scale factors on win10: https://learn.microsoft.com/windows/uwp/controls-and-patterns/tiles-and-notifications-app-assets#asset-size-tables,
private static readonly Dictionary<PackageVersion, List<int>> _scaleFactors = new Dictionary<PackageVersion, List<int>>
{
{ PackageVersion.Windows10, new List<int> { 100, 125, 150, 200, 400 } },
{ PackageVersion.Windows81, new List<int> { 100, 120, 140, 160, 180 } },
{ PackageVersion.Windows8, new List<int> { 100 } },
};
private bool SetScaleIcons(string path, string colorscheme, bool highContrast = false)
{
var extension = Path.GetExtension(path);
if (extension != null)
{
var end = path.Length - extension.Length;
var prefix = path.Substring(0, end);
var paths = new List<string> { };
if (!highContrast)
{
paths.Add(path);
}
if (_scaleFactors.TryGetValue(Package.Version, out var factors))
{
foreach (var factor in factors)
{
if (highContrast)
{
paths.Add($"{prefix}.scale-{factor}_{colorscheme}{extension}");
paths.Add($"{prefix}.{colorscheme}_scale-{factor}{extension}");
}
else
{
paths.Add($"{prefix}.scale-{factor}{extension}");
}
}
}
var selectedIconPath = paths.FirstOrDefault(File.Exists);
if (!string.IsNullOrEmpty(selectedIconPath))
{
LogoPath = selectedIconPath;
if (highContrast)
{
LogoType = LogoType.HighContrast;
}
else
{
LogoType = LogoType.Colored;
}
return true;
}
}
return false;
}
private bool SetTargetSizeIcon(string path, string colorscheme, bool highContrast = false)
{
var extension = Path.GetExtension(path);
if (extension != null)
{
var end = path.Length - extension.Length;
var prefix = path.Substring(0, end);
var paths = new List<string> { };
const int appIconSize = 36;
var targetSizes = new List<int> { 16, 24, 30, 36, 44, 60, 72, 96, 128, 180, 256 }.AsParallel();
var pathFactorPairs = new Dictionary<string, int>();
foreach (var factor in targetSizes)
{
if (highContrast)
{
var suffixThemePath = $"{prefix}.targetsize-{factor}_{colorscheme}{extension}";
var prefixThemePath = $"{prefix}.{colorscheme}_targetsize-{factor}{extension}";
paths.Add(suffixThemePath);
paths.Add(prefixThemePath);
pathFactorPairs.Add(suffixThemePath, factor);
pathFactorPairs.Add(prefixThemePath, factor);
}
else
{
var simplePath = $"{prefix}.targetsize-{factor}{extension}";
var altformUnPlatedPath = $"{prefix}.targetsize-{factor}_altform-unplated{extension}";
paths.Add(simplePath);
paths.Add(altformUnPlatedPath);
pathFactorPairs.Add(simplePath, factor);
pathFactorPairs.Add(altformUnPlatedPath, factor);
}
}
var selectedIconPath = paths.OrderBy(x => Math.Abs(pathFactorPairs.GetValueOrDefault(x) - appIconSize)).FirstOrDefault(File.Exists);
if (!string.IsNullOrEmpty(selectedIconPath))
{
LogoPath = selectedIconPath;
if (highContrast)
{
LogoType = LogoType.HighContrast;
}
else
{
LogoType = LogoType.Colored;
}
return true;
}
}
return false;
}
private bool SetColoredIcon(string path, string colorscheme)
{
var isSetColoredScaleIcon = SetScaleIcons(path, colorscheme);
if (isSetColoredScaleIcon)
{
return true;
}
var isSetColoredTargetIcon = SetTargetSizeIcon(path, colorscheme);
if (isSetColoredTargetIcon)
{
return true;
}
var isSetHighContrastScaleIcon = SetScaleIcons(path, colorscheme, true);
if (isSetHighContrastScaleIcon)
{
return true;
}
var isSetHighContrastTargetIcon = SetTargetSizeIcon(path, colorscheme, true);
if (isSetHighContrastTargetIcon)
{
return true;
}
return false;
}
private bool SetHighContrastIcon(string path, string colorscheme)
{
var isSetHighContrastScaleIcon = SetScaleIcons(path, colorscheme, true);
if (isSetHighContrastScaleIcon)
{
return true;
}
var isSetHighContrastTargetIcon = SetTargetSizeIcon(path, colorscheme, true);
if (isSetHighContrastTargetIcon)
{
return true;
}
var isSetColoredScaleIcon = SetScaleIcons(path, colorscheme);
if (isSetColoredScaleIcon)
{
return true;
}
var isSetColoredTargetIcon = SetTargetSizeIcon(path, colorscheme);
if (isSetColoredTargetIcon)
{
return true;
}
return false;
}
internal void LogoPathFromUri(string uri, Theme theme)
{
// all https://learn.microsoft.com/windows/uwp/controls-and-patterns/tiles-and-notifications-app-assets
// windows 10 https://msdn.microsoft.com/library/windows/apps/dn934817.aspx
// windows 8.1 https://msdn.microsoft.com/library/windows/apps/hh965372.aspx#target_size
// windows 8 https://msdn.microsoft.com/library/windows/apps/br211475.aspx
string path;
bool isLogoUriSet;
// Using Ordinal since this is used internally with uri
if (uri.Contains('\\', StringComparison.Ordinal))
{
path = Path.Combine(Package.Location, uri);
}
else
{
// for C:\Windows\MiracastView etc
path = Path.Combine(Package.Location, "Assets", uri);
}
switch (theme)
{
case Theme.HighContrastBlack:
case Theme.HighContrastOne:
case Theme.HighContrastTwo:
isLogoUriSet = SetHighContrastIcon(path, ContrastBlack);
break;
case Theme.HighContrastWhite:
isLogoUriSet = SetHighContrastIcon(path, ContrastWhite);
break;
case Theme.Light:
isLogoUriSet = SetColoredIcon(path, ContrastWhite);
break;
default:
isLogoUriSet = SetColoredIcon(path, ContrastBlack);
break;
}
if (!isLogoUriSet)
{
LogoPath = string.Empty;
LogoType = LogoType.Error;
}
}
/*
public ImageSource Logo()
{
if (LogoType == LogoType.Colored)
{
var logo = ImageFromPath(LogoPath);
var platedImage = PlatedImage(logo);
return platedImage;
}
else
{
return ImageFromPath(LogoPath);
}
}
private const int _dpiScale100 = 96;
private ImageSource PlatedImage(BitmapImage image)
{
if (!string.IsNullOrEmpty(BackgroundColor))
{
string currentBackgroundColor;
if (BackgroundColor == "transparent")
{
// Using InvariantCulture since this is internal
currentBackgroundColor = SystemParameters.WindowGlassBrush.ToString(CultureInfo.InvariantCulture);
}
else
{
currentBackgroundColor = BackgroundColor;
}
var padding = 8;
var width = image.Width + (2 * padding);
var height = image.Height + (2 * padding);
var x = 0;
var y = 0;
var group = new DrawingGroup();
var converted = ColorConverter.ConvertFromString(currentBackgroundColor);
if (converted != null)
{
var color = (Color)converted;
var brush = new SolidColorBrush(color);
var pen = new Pen(brush, 1);
var backgroundArea = new Rect(0, 0, width, height);
var rectangleGeometry = new RectangleGeometry(backgroundArea, 8, 8);
var rectDrawing = new GeometryDrawing(brush, pen, rectangleGeometry);
group.Children.Add(rectDrawing);
var imageArea = new Rect(x + padding, y + padding, image.Width, image.Height);
var imageDrawing = new ImageDrawing(image, imageArea);
group.Children.Add(imageDrawing);
// http://stackoverflow.com/questions/6676072/get-system-drawing-bitmap-of-a-wpf-area-using-visualbrush
var visual = new DrawingVisual();
var context = visual.RenderOpen();
context.DrawDrawing(group);
context.Close();
var bitmap = new RenderTargetBitmap(
Convert.ToInt32(width),
Convert.ToInt32(height),
_dpiScale100,
_dpiScale100,
PixelFormats.Pbgra32);
bitmap.Render(visual);
return bitmap;
}
else
{
ProgramLogger.Exception($"Unable to convert background string {BackgroundColor} to color for {Package.Location}", new InvalidOperationException(), GetType(), Package.Location);
return new BitmapImage(new Uri(Constant.ErrorIcon));
}
}
else
{
// todo use windows theme as background
return image;
}
}
private BitmapImage ImageFromPath(string path)
{
if (File.Exists(path))
{
var memoryStream = new MemoryStream();
using (var fileStream = File.OpenRead(path))
{
fileStream.CopyTo(memoryStream);
memoryStream.Position = 0;
var image = new BitmapImage();
image.BeginInit();
image.StreamSource = memoryStream;
image.EndInit();
return image;
}
}
else
{
// ProgramLogger.Exception($"Unable to get logo for {UserModelId} from {path} and located in {Package.Location}", new FileNotFoundException(), GetType(), path);
return new BitmapImage(new Uri(ImageLoader.ErrorIconPath));
}
}
*/
public override string ToString()
{
return $"{DisplayName}: {Description}";
}
}

View File

@@ -0,0 +1,847 @@
// 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.Collections.Generic;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Reflection;
using System.Security;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Input;
using Microsoft.CmdPal.Ext.Apps.Commands;
using Microsoft.CmdPal.Ext.Apps.Properties;
using Microsoft.CmdPal.Ext.Apps.Utils;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Win32;
namespace Microsoft.CmdPal.Ext.Apps.Programs;
[Serializable]
public class Win32Program : IProgram
{
public static readonly Win32Program InvalidProgram = new Win32Program { Valid = false, Enabled = false };
private static readonly IFileSystem FileSystem = new FileSystem();
private static readonly IPath Path = FileSystem.Path;
private static readonly IFile File = FileSystem.File;
private static readonly IDirectory Directory = FileSystem.Directory;
public string Name { get; set; } = string.Empty;
// Localized name based on windows display language
public string NameLocalized { get; set; } = string.Empty;
public string UniqueIdentifier { get; set; } = string.Empty;
public string IcoPath { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
// Path of app executable or lnk target executable
public string FullPath { get; set; } = string.Empty;
// Localized path based on windows display language
public string FullPathLocalized { get; set; } = string.Empty;
public string ParentDirectory { get; set; } = string.Empty;
public string ExecutableName { get; set; } = string.Empty;
// Localized executable name based on windows display language
public string ExecutableNameLocalized { get; set; } = string.Empty;
// Path to the lnk file on LnkProgram
public string LnkFilePath { get; set; } = string.Empty;
public string LnkResolvedExecutableName { get; set; } = string.Empty;
// Localized path based on windows display language
public string LnkResolvedExecutableNameLocalized { get; set; } = string.Empty;
public bool Valid { get; set; }
public bool Enabled { get; set; }
public bool HasArguments => !string.IsNullOrEmpty(Arguments);
public string Arguments { get; set; } = string.Empty;
public string Location => ParentDirectory;
public ApplicationType AppType { get; set; }
// Wrappers for File Operations
public static IFileVersionInfoWrapper FileVersionInfoWrapper { get; set; } = new FileVersionInfoWrapper();
public static IFile FileWrapper { get; set; } = new FileSystem().File;
private const string ShortcutExtension = "lnk";
private const string ApplicationReferenceExtension = "appref-ms";
private const string InternetShortcutExtension = "url";
private static readonly HashSet<string> ExecutableApplicationExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "exe", "bat", "bin", "com", "cpl", "msc", "msi", "cmd", "ps1", "job", "msp", "mst", "sct", "ws", "wsh", "wsf" };
private const string ProxyWebApp = "_proxy.exe";
private const string AppIdArgument = "--app-id";
public enum ApplicationType
{
WebApplication = 0,
InternetShortcutApplication = 1,
Win32Application = 2,
ShortcutApplication = 3,
ApprefApplication = 4,
RunCommand = 5,
Folder = 6,
GenericFile = 7,
}
public bool IsWebApplication()
{
// To Filter PWAs when the user searches for the main application
// All Chromium based applications contain the --app-id argument
// Reference : https://codereview.chromium.org/399045
// Using Ordinal IgnoreCase since this is used internally
return !string.IsNullOrEmpty(FullPath) &&
!string.IsNullOrEmpty(Arguments) &&
FullPath.Contains(ProxyWebApp, StringComparison.OrdinalIgnoreCase) &&
Arguments.Contains(AppIdArgument, StringComparison.OrdinalIgnoreCase);
}
// Condition to Filter pinned Web Applications or PWAs when searching for the main application
public bool FilterWebApplication(string query)
{
// If the app is not a web application, then do not filter it
if (!IsWebApplication())
{
return false;
}
var subqueries = query?.Split() ?? Array.Empty<string>();
var nameContainsQuery = false;
var pathContainsQuery = false;
// check if any space separated query is a part of the app name or path name
foreach (var subquery in subqueries)
{
// Using OrdinalIgnoreCase since these are used internally
if (FullPath.Contains(subquery, StringComparison.OrdinalIgnoreCase))
{
pathContainsQuery = true;
}
if (Name.Contains(subquery, StringComparison.OrdinalIgnoreCase))
{
nameContainsQuery = true;
}
}
return pathContainsQuery && !nameContainsQuery;
}
// Function to set the subtitle based on the Type of application
public string Type()
{
switch (AppType)
{
case ApplicationType.Win32Application:
case ApplicationType.ShortcutApplication:
case ApplicationType.ApprefApplication:
return Resources.application;
case ApplicationType.InternetShortcutApplication:
return Resources.internet_shortcut_application;
case ApplicationType.WebApplication:
return Resources.web_application;
case ApplicationType.RunCommand:
return Resources.run_command;
case ApplicationType.Folder:
return Resources.folder;
case ApplicationType.GenericFile:
return Resources.file;
default:
return string.Empty;
}
}
public bool QueryEqualsNameForRunCommands(string query)
{
if (query != null && AppType == ApplicationType.RunCommand)
{
// Using OrdinalIgnoreCase since this is used internally
if (!query.Equals(Name, StringComparison.OrdinalIgnoreCase) && !query.Equals(ExecutableName, StringComparison.OrdinalIgnoreCase))
{
return false;
}
}
return true;
}
public List<CommandContextItem> GetCommands()
{
List<CommandContextItem> commands = new List<CommandContextItem>();
if (AppType != ApplicationType.InternetShortcutApplication && AppType != ApplicationType.Folder && AppType != ApplicationType.GenericFile)
{
commands.Add(new CommandContextItem(
new RunAsAdminCommand(!string.IsNullOrEmpty(LnkFilePath) ? LnkFilePath : FullPath, ParentDirectory, false)));
commands.Add(new CommandContextItem(
new RunAsUserCommand(!string.IsNullOrEmpty(LnkFilePath) ? LnkFilePath : FullPath, ParentDirectory)));
}
commands.Add(new CommandContextItem(
new OpenPathCommand(ParentDirectory)));
commands.Add(new CommandContextItem(
new OpenInConsoleCommand(ParentDirectory)));
return commands;
}
public override string ToString()
{
return ExecutableName;
}
private static Win32Program CreateWin32Program(string path)
{
try
{
var parentDir = Directory.GetParent(path);
return new Win32Program
{
Name = Path.GetFileNameWithoutExtension(path),
ExecutableName = Path.GetFileName(path),
IcoPath = path,
// Using InvariantCulture since this is user facing
FullPath = path,
UniqueIdentifier = path,
ParentDirectory = parentDir is null ? string.Empty : parentDir.FullName,
Description = string.Empty,
Valid = true,
Enabled = true,
AppType = ApplicationType.Win32Application,
// Localized name, path and executable based on windows display language
NameLocalized = ShellLocalization.Instance.GetLocalizedName(path),
FullPathLocalized = ShellLocalization.Instance.GetLocalizedPath(path),
ExecutableNameLocalized = Path.GetFileName(ShellLocalization.Instance.GetLocalizedPath(path)),
};
}
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
{
return InvalidProgram;
}
catch (Exception)
{
return InvalidProgram;
}
}
private static readonly Regex InternetShortcutURLPrefixes = new Regex(@"^steam:\/\/(rungameid|run|open)\/|^com\.epicgames\.launcher:\/\/apps\/", RegexOptions.Compiled);
// This function filters Internet Shortcut programs
private static Win32Program InternetShortcutProgram(string path)
{
try
{
// We don't want to read the whole file if we don't need to
var lines = FileWrapper.ReadLines(path);
var iconPath = string.Empty;
var urlPath = string.Empty;
var validApp = false;
const string urlPrefix = "URL=";
const string iconFilePrefix = "IconFile=";
foreach (var line in lines)
{
// Using OrdinalIgnoreCase since this is used internally
if (line.StartsWith(urlPrefix, StringComparison.OrdinalIgnoreCase))
{
urlPath = line.Substring(urlPrefix.Length);
if (!Uri.TryCreate(urlPath, UriKind.RelativeOrAbsolute, out var _))
{
return InvalidProgram;
}
// To filter out only those steam shortcuts which have 'run' or 'rungameid' as the hostname
if (InternetShortcutURLPrefixes.Match(urlPath).Success)
{
validApp = true;
}
}
else if (line.StartsWith(iconFilePrefix, StringComparison.OrdinalIgnoreCase))
{
iconPath = line.Substring(iconFilePrefix.Length);
}
// If we resolved an urlPath & and an iconPath quit reading the file
if (!string.IsNullOrEmpty(urlPath) && !string.IsNullOrEmpty(iconPath))
{
break;
}
}
if (!validApp)
{
return InvalidProgram;
}
try
{
var parentDir = Directory.GetParent(path);
return new Win32Program
{
Name = Path.GetFileNameWithoutExtension(path),
ExecutableName = Path.GetFileName(path),
IcoPath = iconPath,
FullPath = urlPath,
UniqueIdentifier = path,
ParentDirectory = parentDir is null ? string.Empty : parentDir.FullName,
Valid = true,
Enabled = true,
AppType = ApplicationType.InternetShortcutApplication,
};
}
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
{
return InvalidProgram;
}
}
catch (Exception)
{
return InvalidProgram;
}
}
private static Win32Program LnkProgram(string path)
{
try
{
var program = CreateWin32Program(path);
var shellLinkHelper = new ShellLinkHelper();
var target = shellLinkHelper.RetrieveTargetPath(path);
if (!string.IsNullOrEmpty(target))
{
if (!(File.Exists(target) || Directory.Exists(target)))
{
// If the link points nowhere, consider it invalid.
return InvalidProgram;
}
program.LnkFilePath = program.FullPath;
program.LnkResolvedExecutableName = Path.GetFileName(target);
program.LnkResolvedExecutableNameLocalized = Path.GetFileName(ShellLocalization.Instance.GetLocalizedPath(target));
// Using CurrentCulture since this is user facing
program.FullPath = Path.GetFullPath(target);
program.FullPathLocalized = ShellLocalization.Instance.GetLocalizedPath(target);
program.Arguments = shellLinkHelper.Arguments;
// A .lnk could be a (Chrome) PWA, set correct AppType
program.AppType = program.IsWebApplication()
? ApplicationType.WebApplication
: GetAppTypeFromPath(target);
var description = shellLinkHelper.Description;
if (!string.IsNullOrEmpty(description))
{
program.Description = description;
}
else
{
var info = FileVersionInfoWrapper.GetVersionInfo(target);
if (!string.IsNullOrEmpty(info?.FileDescription))
{
program.Description = info.FileDescription;
}
}
}
return program;
}
catch (System.IO.FileLoadException)
{
return InvalidProgram;
}
// Only do a catch all in production. This is so make developer aware of any unhandled exception and add the exception handling in.
// Error caused likely due to trying to get the description of the program
catch (Exception)
{
return InvalidProgram;
}
}
private static Win32Program ExeProgram(string path)
{
try
{
var program = CreateWin32Program(path);
var info = FileVersionInfoWrapper.GetVersionInfo(path);
if (!string.IsNullOrEmpty(info?.FileDescription))
{
program.Description = info.FileDescription;
}
return program;
}
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
{
return InvalidProgram;
}
catch (FileNotFoundException)
{
return InvalidProgram;
}
catch (Exception)
{
return InvalidProgram;
}
}
// Function to get the application type, given the path to the application
public static ApplicationType GetAppTypeFromPath(string path)
{
ArgumentNullException.ThrowIfNull(path);
var extension = Extension(path);
// Using OrdinalIgnoreCase since these are used internally with paths
if (ExecutableApplicationExtensions.Contains(extension))
{
return ApplicationType.Win32Application;
}
else if (extension.Equals(ShortcutExtension, StringComparison.OrdinalIgnoreCase))
{
return ApplicationType.ShortcutApplication;
}
else if (extension.Equals(ApplicationReferenceExtension, StringComparison.OrdinalIgnoreCase))
{
return ApplicationType.ApprefApplication;
}
else if (extension.Equals(InternetShortcutExtension, StringComparison.OrdinalIgnoreCase))
{
return ApplicationType.InternetShortcutApplication;
}
else if (string.IsNullOrEmpty(extension) && System.IO.Directory.Exists(path))
{
return ApplicationType.Folder;
}
return ApplicationType.GenericFile;
}
// Function to get the Win32 application, given the path to the application
public static Win32Program? GetAppFromPath(string path)
{
ArgumentNullException.ThrowIfNull(path);
Win32Program? app;
switch (GetAppTypeFromPath(path))
{
case ApplicationType.Win32Application:
app = ExeProgram(path);
break;
case ApplicationType.ShortcutApplication:
app = LnkProgram(path);
break;
case ApplicationType.ApprefApplication:
app = CreateWin32Program(path);
app.AppType = ApplicationType.ApprefApplication;
break;
case ApplicationType.InternetShortcutApplication:
app = InternetShortcutProgram(path);
break;
case ApplicationType.WebApplication:
case ApplicationType.RunCommand:
case ApplicationType.Folder:
case ApplicationType.GenericFile:
default:
app = null;
break;
}
// if the app is valid, only then return the application, else return null
return app?.Valid == true
? app
: null;
}
private static IEnumerable<string> ProgramPaths(string directory, IList<string> suffixes, bool recursiveSearch = true)
{
if (!Directory.Exists(directory))
{
return Array.Empty<string>();
}
var files = new List<string>();
var folderQueue = new Queue<string>();
folderQueue.Enqueue(directory);
// Keep track of already visited directories to avoid cycles.
var alreadyVisited = new HashSet<string>();
do
{
var currentDirectory = folderQueue.Dequeue();
if (alreadyVisited.Contains(currentDirectory))
{
continue;
}
alreadyVisited.Add(currentDirectory);
try
{
foreach (var suffix in suffixes)
{
try
{
files.AddRange(Directory.EnumerateFiles(currentDirectory, $"*.{suffix}", SearchOption.TopDirectoryOnly));
}
catch (DirectoryNotFoundException)
{
}
}
}
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
{
}
catch (Exception)
{
}
try
{
// If the search is set to be non-recursive, then do not enqueue the child directories.
if (!recursiveSearch)
{
continue;
}
foreach (var childDirectory in Directory.EnumerateDirectories(currentDirectory, "*", new EnumerationOptions()
{
// https://learn.microsoft.com/dotnet/api/system.io.enumerationoptions?view=net-6.0
// Exclude directories with the Reparse Point file attribute, to avoid loops due to symbolic links / directory junction / mount points.
AttributesToSkip = FileAttributes.Hidden | FileAttributes.System | FileAttributes.ReparsePoint,
RecurseSubdirectories = false,
}))
{
folderQueue.Enqueue(childDirectory);
}
}
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
{
}
catch (Exception)
{
}
}
while (folderQueue.Count > 0);
return files;
}
private static string Extension(string path)
{
// Using InvariantCulture since this is user facing
var extension = Path.GetExtension(path)?.ToLowerInvariant();
return !string.IsNullOrEmpty(extension)
? extension.Substring(1)
: string.Empty;
}
private static IEnumerable<string> CustomProgramPaths(IEnumerable<ProgramSource> sources, IList<string> suffixes)
=> sources?.Where(programSource => Directory.Exists(programSource.Location) && programSource.Enabled)
.SelectMany(programSource => ProgramPaths(programSource.Location, suffixes))
.ToList() ?? Enumerable.Empty<string>();
// Function to obtain the list of applications, the locations of which have been added to the env variable PATH
private static List<string> PathEnvironmentProgramPaths(IList<string> suffixes)
{
// To get all the locations stored in the PATH env variable
var pathEnvVariable = Environment.GetEnvironmentVariable("PATH");
var searchPaths = pathEnvVariable?.Split(Path.PathSeparator);
var toFilterAllPaths = new List<string>();
var isRecursiveSearch = true;
if (searchPaths is not null)
{
foreach (var path in searchPaths)
{
if (path.Length > 0)
{
// to expand any environment variables present in the path
var directory = Environment.ExpandEnvironmentVariables(path);
var paths = ProgramPaths(directory, suffixes, !isRecursiveSearch);
toFilterAllPaths.AddRange(paths);
}
}
}
return toFilterAllPaths;
}
private static List<string> IndexPath(IList<string> suffixes, List<string> indexLocations)
=> indexLocations
.SelectMany(indexLocation => ProgramPaths(indexLocation, suffixes))
.ToList();
private static List<string> StartMenuProgramPaths(IList<string> suffixes)
{
var directory1 = Environment.GetFolderPath(Environment.SpecialFolder.StartMenu);
var directory2 = Environment.GetFolderPath(Environment.SpecialFolder.CommonStartMenu);
var indexLocation = new List<string>() { directory1, directory2 };
return IndexPath(suffixes, indexLocation);
}
private static List<string> DesktopProgramPaths(IList<string> suffixes)
{
var directory1 = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
var directory2 = Environment.GetFolderPath(Environment.SpecialFolder.CommonDesktopDirectory);
var indexLocation = new List<string>() { directory1, directory2 };
return IndexPath(suffixes, indexLocation);
}
private static List<string> RegistryAppProgramPaths(IList<string> suffixes)
{
// https://msdn.microsoft.com/library/windows/desktop/ee872121
const string appPaths = @"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths";
var paths = new List<string>();
using (var root = Registry.LocalMachine.OpenSubKey(appPaths))
{
if (root != null)
{
paths.AddRange(GetPathsFromRegistry(root));
}
}
using (var root = Registry.CurrentUser.OpenSubKey(appPaths))
{
if (root != null)
{
paths.AddRange(GetPathsFromRegistry(root));
}
}
return paths
.Where(path => suffixes.Any(suffix => path.EndsWith(suffix, StringComparison.InvariantCultureIgnoreCase)))
.Select(ExpandEnvironmentVariables)
.Where(path => path is not null)
.ToList();
}
private static IEnumerable<string> GetPathsFromRegistry(RegistryKey root)
=> root
.GetSubKeyNames()
.Select(x => GetPathFromRegistrySubkey(root, x));
private static string GetPathFromRegistrySubkey(RegistryKey root, string subkey)
{
var path = string.Empty;
try
{
using (var key = root.OpenSubKey(subkey))
{
if (key == null)
{
return string.Empty;
}
var defaultValue = string.Empty;
path = key.GetValue(defaultValue) as string;
}
if (string.IsNullOrEmpty(path))
{
return string.Empty;
}
// fix path like this: ""\"C:\\folder\\executable.exe\""
return path = path.Trim('"', ' ');
}
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
{
return string.Empty;
}
}
private static string ExpandEnvironmentVariables(string path) =>
!string.IsNullOrEmpty(path)
? Environment.ExpandEnvironmentVariables(path)
: string.Empty;
// Overriding the object.GetHashCode() function to aid in removing duplicates while adding and removing apps from the concurrent dictionary storage
public override int GetHashCode()
=> Win32ProgramEqualityComparer.Default.GetHashCode(this);
public override bool Equals(object? obj)
=> obj is Win32Program win32Program && Win32ProgramEqualityComparer.Default.Equals(this, win32Program);
private sealed class Win32ProgramEqualityComparer : IEqualityComparer<Win32Program>
{
public static readonly Win32ProgramEqualityComparer Default = new Win32ProgramEqualityComparer();
public bool Equals(Win32Program? app1, Win32Program? app2)
{
if (app1 == null && app2 == null)
{
return true;
}
return app1 != null
&& app2 != null
&& (app1.Name?.ToUpperInvariant(), app1.ExecutableName?.ToUpperInvariant(), app1.FullPath?.ToUpperInvariant())
.Equals((app2.Name?.ToUpperInvariant(), app2.ExecutableName?.ToUpperInvariant(), app2.FullPath?.ToUpperInvariant()));
}
public int GetHashCode(Win32Program obj)
=> (obj.Name?.ToUpperInvariant(), obj.ExecutableName?.ToUpperInvariant(), obj.FullPath?.ToUpperInvariant()).GetHashCode();
}
public static List<Win32Program> DeduplicatePrograms(IEnumerable<Win32Program> programs)
=> new HashSet<Win32Program>(programs, Win32ProgramEqualityComparer.Default).ToList();
private static Win32Program GetProgramFromPath(string path)
{
var extension = Extension(path);
if (ExecutableApplicationExtensions.Contains(extension))
{
return ExeProgram(path);
}
switch (extension)
{
case ShortcutExtension:
return LnkProgram(path);
case ApplicationReferenceExtension:
return CreateWin32Program(path);
case InternetShortcutExtension:
return InternetShortcutProgram(path);
default:
return InvalidProgram;
}
}
private static bool TryGetIcoPathForRunCommandProgram(Win32Program program, out string? icoPath)
{
icoPath = null;
if (program.AppType != ApplicationType.RunCommand)
{
return false;
}
if (string.IsNullOrEmpty(program.FullPath))
{
return false;
}
// https://msdn.microsoft.com/library/windows/desktop/ee872121
try
{
var redirectionPath = ReparsePoint.GetTarget(program.FullPath);
if (string.IsNullOrEmpty(redirectionPath))
{
return false;
}
icoPath = ExpandEnvironmentVariables(redirectionPath);
return true;
}
catch (IOException)
{
}
icoPath = null;
return false;
}
private static Win32Program GetRunCommandProgramFromPath(string path)
{
var program = GetProgramFromPath(path);
if (program.Valid)
{
program.AppType = ApplicationType.RunCommand;
if (TryGetIcoPathForRunCommandProgram(program, out var icoPath))
{
program.IcoPath = icoPath ?? string.Empty;
}
}
return program;
}
public static IList<Win32Program> All(AllAppsSettings settings)
{
ArgumentNullException.ThrowIfNull(settings);
try
{
// Set an initial size to an expected size to prevent multiple hashSet resizes
const int defaultHashsetSize = 1000;
// Multiple paths could have the same programPaths and we don't want to resolve / lookup them multiple times
var paths = new HashSet<string>(defaultHashsetSize);
var runCommandPaths = new HashSet<string>(defaultHashsetSize);
// Parallelize multiple sources, and priority based on paths which most likely contain .lnks which are formatted
var sources = new (bool IsEnabled, Func<IEnumerable<string>> GetPaths)[]
{
(true, () => CustomProgramPaths(settings.ProgramSources, settings.ProgramSuffixes)),
(settings.EnableStartMenuSource, () => StartMenuProgramPaths(settings.ProgramSuffixes)),
(settings.EnableDesktopSource, () => DesktopProgramPaths(settings.ProgramSuffixes)),
(settings.EnableRegistrySource, () => RegistryAppProgramPaths(settings.ProgramSuffixes)),
};
// Run commands are always set as AppType "RunCommand"
var runCommandSources = new (bool IsEnabled, Func<IEnumerable<string>> GetPaths)[]
{
(settings.EnablePathEnvironmentVariableSource, () => PathEnvironmentProgramPaths(settings.RunCommandSuffixes)),
};
var disabledProgramsList = settings.DisabledProgramSources;
// Get all paths but exclude all normal .Executables
paths.UnionWith(sources
.AsParallel()
.SelectMany(source => source.IsEnabled ? source.GetPaths() : Enumerable.Empty<string>())
.Where(programPath => disabledProgramsList.All(x => x.UniqueIdentifier != programPath))
.Where(path => !ExecutableApplicationExtensions.Contains(Extension(path))));
runCommandPaths.UnionWith(runCommandSources
.AsParallel()
.SelectMany(source => source.IsEnabled ? source.GetPaths() : Enumerable.Empty<string>())
.Where(programPath => disabledProgramsList.All(x => x.UniqueIdentifier != programPath)));
var programs = paths.AsParallel().Select(source => GetProgramFromPath(source));
var runCommandPrograms = runCommandPaths.AsParallel().Select(source => GetRunCommandProgramFromPath(source));
return DeduplicatePrograms(programs.Concat(runCommandPrograms).Where(program => program?.Valid == true));
}
catch (Exception)
{
return Array.Empty<Win32Program>();
}
}
}