mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 11:48:06 +01:00
[AOT][CmdPal] Enable NativeAOT Compatibility for CmdPal Apps Extension (#39678)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request This PR introduces necessary changes to make the CmdPal.Apps extension compatible with NativeAOT publishing. The main updates include: 1. Project configuration updates: Added NativeAOT-related properties to the .csproj. 2. Native interop adjustments: > - Introduced NativeMethods.json and used [CsWin32](https://github.com/microsoft/cswin32) to generate P/Invoke bindings. > - Replaced some DllImport declarations with source-generated [LibraryImport] for improved AOT support.
This commit is contained in:
3
.github/actions/spell-check/expect.txt
vendored
3
.github/actions/spell-check/expect.txt
vendored
@@ -14,6 +14,7 @@ AColumn
|
|||||||
acrt
|
acrt
|
||||||
ACTIVATEAPP
|
ACTIVATEAPP
|
||||||
activationaction
|
activationaction
|
||||||
|
ACTIVATEOPTIONS
|
||||||
ACVS
|
ACVS
|
||||||
adaptivecards
|
adaptivecards
|
||||||
ADate
|
ADate
|
||||||
@@ -1588,6 +1589,7 @@ steamapps
|
|||||||
STGC
|
STGC
|
||||||
STGM
|
STGM
|
||||||
STGMEDIUM
|
STGMEDIUM
|
||||||
|
STGMREAD
|
||||||
STICKYKEYS
|
STICKYKEYS
|
||||||
sticpl
|
sticpl
|
||||||
storelogo
|
storelogo
|
||||||
@@ -1692,6 +1694,7 @@ TLayout
|
|||||||
tlb
|
tlb
|
||||||
tlbimp
|
tlbimp
|
||||||
tlc
|
tlc
|
||||||
|
TGM
|
||||||
TNP
|
TNP
|
||||||
Toolhelp
|
Toolhelp
|
||||||
toolkitconverters
|
toolkitconverters
|
||||||
|
|||||||
@@ -7,6 +7,6 @@
|
|||||||
<CsWinRTAotWarningLevel>2</CsWinRTAotWarningLevel>
|
<CsWinRTAotWarningLevel>2</CsWinRTAotWarningLevel>
|
||||||
|
|
||||||
<!-- Suppress DynamicallyAccessedMemberTypes.PublicParameterlessConstructor in fallback code path of Windows SDK projection -->
|
<!-- Suppress DynamicallyAccessedMemberTypes.PublicParameterlessConstructor in fallback code path of Windows SDK projection -->
|
||||||
<WarningsNotAsErrors>IL2081;$(WarningsNotAsErrors)</WarningsNotAsErrors>
|
<WarningsNotAsErrors>IL2081;CsWinRT1028;$(WarningsNotAsErrors)</WarningsNotAsErrors>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ public class AllAppsSettings : JsonSettingsManager
|
|||||||
|
|
||||||
internal static string SettingsJsonPath()
|
internal static string SettingsJsonPath()
|
||||||
{
|
{
|
||||||
string directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
|
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
|
||||||
Directory.CreateDirectory(directory);
|
Directory.CreateDirectory(directory);
|
||||||
|
|
||||||
// now, the state is just next to the exe
|
// now, the state is just next to the exe
|
||||||
|
|||||||
@@ -8,7 +8,12 @@ using System.Threading.Tasks;
|
|||||||
using ManagedCommon;
|
using ManagedCommon;
|
||||||
using Microsoft.CmdPal.Ext.Apps.Programs;
|
using Microsoft.CmdPal.Ext.Apps.Programs;
|
||||||
using Microsoft.CmdPal.Ext.Apps.Properties;
|
using Microsoft.CmdPal.Ext.Apps.Properties;
|
||||||
|
using Microsoft.CmdPal.Ext.Apps.Utils;
|
||||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
|
using Windows.Services.Maps;
|
||||||
|
using Windows.Win32;
|
||||||
|
using Windows.Win32.System.Com;
|
||||||
|
using Windows.Win32.UI.Shell;
|
||||||
using WyHash;
|
using WyHash;
|
||||||
|
|
||||||
namespace Microsoft.CmdPal.Ext.Apps;
|
namespace Microsoft.CmdPal.Ext.Apps;
|
||||||
@@ -27,26 +32,31 @@ internal sealed partial class AppCommand : InvokableCommand
|
|||||||
|
|
||||||
internal static async Task StartApp(string aumid)
|
internal static async Task StartApp(string aumid)
|
||||||
{
|
{
|
||||||
var appManager = new ApplicationActivationManager();
|
|
||||||
const ActivateOptions noFlags = ActivateOptions.None;
|
|
||||||
await Task.Run(() =>
|
await Task.Run(() =>
|
||||||
{
|
{
|
||||||
try
|
unsafe
|
||||||
{
|
{
|
||||||
appManager.ActivateApplication(aumid, /*queryArguments*/ string.Empty, noFlags, out var unusedPid);
|
IApplicationActivationManager* appManager = null;
|
||||||
}
|
try
|
||||||
catch (System.Exception ex)
|
{
|
||||||
{
|
PInvoke.CoCreateInstance(typeof(ApplicationActivationManager).GUID, null, CLSCTX.CLSCTX_INPROC_SERVER, out appManager).ThrowOnFailure();
|
||||||
Logger.LogError(ex.Message);
|
using var handle = new SafeComHandle((IntPtr)appManager);
|
||||||
|
appManager->ActivateApplication(
|
||||||
|
aumid,
|
||||||
|
string.Empty,
|
||||||
|
ACTIVATEOPTIONS.AO_NONE,
|
||||||
|
out var unusedPid);
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex.Message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static async Task StartExe(string path)
|
internal static async Task StartExe(string path)
|
||||||
{
|
{
|
||||||
var appManager = new ApplicationActivationManager();
|
|
||||||
|
|
||||||
// const ActivateOptions noFlags = ActivateOptions.None;
|
|
||||||
await Task.Run(() =>
|
await Task.Run(() =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
|
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||||
|
<Import Project="..\..\..\..\Common.Dotnet.AotCompatibility.props" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<RootNamespace>Microsoft.CmdPal.Ext.Apps</RootNamespace>
|
<RootNamespace>Microsoft.CmdPal.Ext.Apps</RootNamespace>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal</OutputPath>
|
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal</OutputPath>
|
||||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||||
|
<DisableRuntimeMarshalling>true</DisableRuntimeMarshalling>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -49,4 +51,9 @@
|
|||||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<AdditionalFiles Include="NativeMethods.txt" />
|
||||||
|
<AdditionalFiles Include="NativeMethods.json" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://aka.ms/CsWin32.schema.json",
|
||||||
|
"allowMarshaling": false,
|
||||||
|
"comInterop": {
|
||||||
|
"preserveSigMethods": [ "*" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,20 @@
|
|||||||
GetPhysicallyInstalledSystemMemory
|
IStream
|
||||||
GlobalMemoryStatusEx
|
|
||||||
GetSystemInfo
|
|
||||||
CoCreateInstance
|
CoCreateInstance
|
||||||
SetForegroundWindow
|
IApplicationActivationManager
|
||||||
IsIconic
|
ApplicationActivationManager
|
||||||
RegisterHotKey
|
|
||||||
SetWindowLongPtr
|
|
||||||
CallWindowProc
|
|
||||||
ShowWindow
|
|
||||||
SetForegroundWindow
|
|
||||||
SetFocus
|
|
||||||
SetActiveWindow
|
|
||||||
MonitorFromWindow
|
|
||||||
GetMonitorInfo
|
|
||||||
SHCreateStreamOnFileEx
|
SHCreateStreamOnFileEx
|
||||||
CoAllowSetForegroundWindow
|
SHCreateItemFromParsingName
|
||||||
SHCreateStreamOnFileEx
|
IShellItem
|
||||||
SHLoadIndirectString
|
ISequentialStream
|
||||||
|
SHLoadIndirectString
|
||||||
|
IAppxFactory
|
||||||
|
AppxFactory
|
||||||
|
IAppxManifestReader
|
||||||
|
IAppxManifestApplicationsEnumerator
|
||||||
|
IAppxManifestApplication
|
||||||
|
IAppxManifestProperties
|
||||||
|
IShellLinkW
|
||||||
|
ShellLink
|
||||||
|
IPersistFile
|
||||||
|
CoTaskMemFree
|
||||||
|
IUnknown
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
// 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
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -2,42 +2,75 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.InteropServices;
|
using ManagedCommon;
|
||||||
|
using Microsoft.CmdPal.Ext.Apps.Utils;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using Windows.Win32;
|
||||||
|
using Windows.Win32.Foundation;
|
||||||
|
using Windows.Win32.Storage.Packaging.Appx;
|
||||||
using Windows.Win32.System.Com;
|
using Windows.Win32.System.Com;
|
||||||
using static Microsoft.CmdPal.Ext.Apps.Utils.Native;
|
|
||||||
|
|
||||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||||
|
|
||||||
public static class AppxPackageHelper
|
public static class AppxPackageHelper
|
||||||
{
|
{
|
||||||
private static readonly IAppxFactory AppxFactory = (IAppxFactory)new AppxFactory();
|
internal static unsafe List<IntPtr> GetAppsFromManifest(IStream* stream)
|
||||||
|
|
||||||
// This function returns a list of attributes of applications
|
|
||||||
internal static IEnumerable<IAppxManifestApplication> GetAppsFromManifest(IStream stream)
|
|
||||||
{
|
{
|
||||||
var reader = AppxFactory.CreateManifestReader(stream);
|
PInvoke.CoCreateInstance(typeof(AppxFactory).GUID, null, CLSCTX.CLSCTX_INPROC_SERVER, out IAppxFactory* appxFactory).ThrowOnFailure();
|
||||||
var manifestApps = reader.GetApplications();
|
using var handle = new SafeComHandle((IntPtr)appxFactory);
|
||||||
|
|
||||||
while (manifestApps.GetHasCurrent())
|
IAppxManifestReader* reader = null;
|
||||||
|
IAppxManifestApplicationsEnumerator* manifestApps = null;
|
||||||
|
var result = new List<IntPtr>();
|
||||||
|
|
||||||
|
appxFactory->CreateManifestReader(stream, &reader);
|
||||||
|
using var readerHandle = new SafeComHandle((IntPtr)reader);
|
||||||
|
reader->GetApplications(&manifestApps);
|
||||||
|
using var manifestAppsHandle = new SafeComHandle((IntPtr)manifestApps);
|
||||||
|
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
var manifestApp = manifestApps.GetCurrent();
|
manifestApps->GetHasCurrent(out var hasCurrent);
|
||||||
var hr = manifestApp.GetStringValue("AppListEntry", out var appListEntry);
|
if (hasCurrent == false)
|
||||||
_ = CheckHRAndReturnOrThrow(hr, appListEntry);
|
|
||||||
if (appListEntry != "none")
|
|
||||||
{
|
{
|
||||||
yield return manifestApp;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestApps.MoveNext();
|
IAppxManifestApplication* manifestApp = null;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static T CheckHRAndReturnOrThrow<T>(HRESULT hr, T result)
|
try
|
||||||
{
|
{
|
||||||
if (hr != HRESULT.S_OK)
|
manifestApps->GetCurrent(&manifestApp).ThrowOnFailure();
|
||||||
{
|
|
||||||
Marshal.ThrowExceptionForHR((int)hr);
|
var hr = manifestApp->GetStringValue("AppListEntry", out var appListEntryPtr);
|
||||||
|
var appListEntry = ComFreeHelper.GetStringAndFree(hr, appListEntryPtr);
|
||||||
|
|
||||||
|
if (appListEntry != "none")
|
||||||
|
{
|
||||||
|
result.Add((IntPtr)manifestApp);
|
||||||
|
}
|
||||||
|
else if (manifestApp != null)
|
||||||
|
{
|
||||||
|
manifestApp->Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (manifestApp != null)
|
||||||
|
{
|
||||||
|
manifestApp->Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogError($"Failed to get current application from manifest: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
manifestApps->MoveNext(out var hasNext);
|
||||||
|
if (hasNext == false)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
// 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);
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
// 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);
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
// 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);
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
// 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();
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
// 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);
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
// 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();
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@ namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides access to NTFS reparse points in .Net.
|
/// Provides access to NTFS reparse points in .Net.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class ReparsePoint
|
public static partial class ReparsePoint
|
||||||
{
|
{
|
||||||
#pragma warning disable SA1310 // Field names should not contain underscore
|
#pragma warning disable SA1310 // Field names should not contain underscore
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ public static class ReparsePoint
|
|||||||
#pragma warning restore SA1310 // Field names should not contain underscore
|
#pragma warning restore SA1310 // Field names should not contain underscore
|
||||||
|
|
||||||
[Flags]
|
[Flags]
|
||||||
private enum FileAccessType : uint
|
internal enum FileAccessType : uint
|
||||||
{
|
{
|
||||||
DELETE = 0x00010000,
|
DELETE = 0x00010000,
|
||||||
READ_CONTROL = 0x00020000,
|
READ_CONTROL = 0x00020000,
|
||||||
@@ -100,7 +101,7 @@ public static class ReparsePoint
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Flags]
|
[Flags]
|
||||||
private enum FileShareType : uint
|
internal enum FileShareType : uint
|
||||||
{
|
{
|
||||||
None = 0x00000000,
|
None = 0x00000000,
|
||||||
Read = 0x00000001,
|
Read = 0x00000001,
|
||||||
@@ -108,7 +109,7 @@ public static class ReparsePoint
|
|||||||
Delete = 0x00000004,
|
Delete = 0x00000004,
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum CreationDisposition : uint
|
internal enum CreationDisposition : uint
|
||||||
{
|
{
|
||||||
New = 1,
|
New = 1,
|
||||||
CreateAlways = 2,
|
CreateAlways = 2,
|
||||||
@@ -118,7 +119,7 @@ public static class ReparsePoint
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Flags]
|
[Flags]
|
||||||
private enum FileAttributes : uint
|
internal enum FileAttributes : uint
|
||||||
{
|
{
|
||||||
Readonly = 0x00000001,
|
Readonly = 0x00000001,
|
||||||
Hidden = 0x00000002,
|
Hidden = 0x00000002,
|
||||||
@@ -195,8 +196,9 @@ public static class ReparsePoint
|
|||||||
public AppExecutionAliasReparseTagBufferLayoutVersion Version;
|
public AppExecutionAliasReparseTagBufferLayoutVersion Version;
|
||||||
}
|
}
|
||||||
|
|
||||||
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
[LibraryImport("kernel32.dll", SetLastError = true)]
|
||||||
private static extern bool DeviceIoControl(
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
internal static partial bool DeviceIoControl(
|
||||||
IntPtr hDevice,
|
IntPtr hDevice,
|
||||||
uint dwIoControlCode,
|
uint dwIoControlCode,
|
||||||
IntPtr inBuffer,
|
IntPtr inBuffer,
|
||||||
@@ -206,8 +208,8 @@ public static class ReparsePoint
|
|||||||
out int pBytesReturned,
|
out int pBytesReturned,
|
||||||
IntPtr lpOverlapped);
|
IntPtr lpOverlapped);
|
||||||
|
|
||||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
[LibraryImport("kernel32.dll", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
||||||
private static extern IntPtr CreateFile(
|
internal static partial int CreateFile(
|
||||||
string lpFileName,
|
string lpFileName,
|
||||||
FileAccessType dwDesiredAccess,
|
FileAccessType dwDesiredAccess,
|
||||||
FileShareType dwShareMode,
|
FileShareType dwShareMode,
|
||||||
@@ -284,15 +286,18 @@ public static class ReparsePoint
|
|||||||
ThrowLastWin32Error("Unable to get information about reparse point.");
|
ThrowLastWin32Error("Unable to get information about reparse point.");
|
||||||
}
|
}
|
||||||
|
|
||||||
AppExecutionAliasReparseTagHeader aliasReparseHeader = Marshal.PtrToStructure<AppExecutionAliasReparseTagHeader>(outBuffer);
|
unsafe
|
||||||
|
|
||||||
if (aliasReparseHeader.ReparseTag == IO_REPARSE_TAG_APPEXECLINK)
|
|
||||||
{
|
{
|
||||||
var metadata = AppExecutionAliasMetadata.FromPersistedRepresentationIntPtr(
|
var aliasReparseHeader = Unsafe.Read<AppExecutionAliasReparseTagHeader>((void*)outBuffer);
|
||||||
outBuffer,
|
|
||||||
aliasReparseHeader.Version);
|
|
||||||
|
|
||||||
return metadata.ExePath;
|
if (aliasReparseHeader.ReparseTag == IO_REPARSE_TAG_APPEXECLINK)
|
||||||
|
{
|
||||||
|
var metadata = AppExecutionAliasMetadata.FromPersistedRepresentationIntPtr(
|
||||||
|
outBuffer,
|
||||||
|
aliasReparseHeader.Version);
|
||||||
|
|
||||||
|
return metadata.ExePath;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -319,61 +324,65 @@ public static class ReparsePoint
|
|||||||
|
|
||||||
public static AppExecutionAliasMetadata FromPersistedRepresentationIntPtr(IntPtr reparseDataBufferPtr, AppExecutionAliasReparseTagBufferLayoutVersion version)
|
public static AppExecutionAliasMetadata FromPersistedRepresentationIntPtr(IntPtr reparseDataBufferPtr, AppExecutionAliasReparseTagBufferLayoutVersion version)
|
||||||
{
|
{
|
||||||
var dataOffset = Marshal.SizeOf<AppExecutionAliasReparseTagHeader>();
|
unsafe
|
||||||
var dataBufferPtr = reparseDataBufferPtr + dataOffset;
|
|
||||||
|
|
||||||
string? packageFullName = null;
|
|
||||||
string? packageFamilyName = null;
|
|
||||||
string? aumid = null;
|
|
||||||
string? exePath = null;
|
|
||||||
|
|
||||||
VerifyVersion(version);
|
|
||||||
|
|
||||||
switch (version)
|
|
||||||
{
|
{
|
||||||
case AppExecutionAliasReparseTagBufferLayoutVersion.Initial:
|
var dataOffset = Unsafe.SizeOf<AppExecutionAliasReparseTagHeader>();
|
||||||
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)
|
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(aumid) + Encoding.Unicode.GetByteCount("\0");
|
dataBufferPtr += Encoding.Unicode.GetByteCount(packageFullName) + Encoding.Unicode.GetByteCount("\0");
|
||||||
exePath = Marshal.PtrToStringUni(dataBufferPtr);
|
aumid = Marshal.PtrToStringUni(dataBufferPtr);
|
||||||
|
|
||||||
|
if (aumid is not null)
|
||||||
|
{
|
||||||
|
dataBufferPtr += Encoding.Unicode.GetByteCount(aumid) + Encoding.Unicode.GetByteCount("\0");
|
||||||
|
exePath = Marshal.PtrToStringUni(dataBufferPtr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AppExecutionAliasReparseTagBufferLayoutVersion.PackageFamilyName:
|
case AppExecutionAliasReparseTagBufferLayoutVersion.PackageFamilyName:
|
||||||
case AppExecutionAliasReparseTagBufferLayoutVersion.MultiAppTypeSupport:
|
case AppExecutionAliasReparseTagBufferLayoutVersion.MultiAppTypeSupport:
|
||||||
packageFamilyName = Marshal.PtrToStringUni(dataBufferPtr);
|
packageFamilyName = Marshal.PtrToStringUni(dataBufferPtr);
|
||||||
|
|
||||||
if (packageFamilyName is not null)
|
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");
|
dataBufferPtr += Encoding.Unicode.GetByteCount(packageFamilyName) + Encoding.Unicode.GetByteCount("\0");
|
||||||
|
aumid = Marshal.PtrToStringUni(dataBufferPtr);
|
||||||
|
|
||||||
exePath = Marshal.PtrToStringUni(dataBufferPtr);
|
if (aumid is not null)
|
||||||
|
{
|
||||||
|
dataBufferPtr += Encoding.Unicode.GetByteCount(aumid) + Encoding.Unicode.GetByteCount("\0");
|
||||||
|
|
||||||
|
exePath = Marshal.PtrToStringUni(dataBufferPtr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AppExecutionAliasMetadata
|
||||||
|
{
|
||||||
|
PackageFullName = packageFullName ?? string.Empty,
|
||||||
|
PackageFamilyName = packageFamilyName ?? string.Empty,
|
||||||
|
Aumid = aumid ?? string.Empty,
|
||||||
|
ExePath = exePath ?? string.Empty,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
private static void VerifyVersion(AppExecutionAliasReparseTagBufferLayoutVersion version)
|
||||||
|
|||||||
@@ -3,17 +3,19 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO.Abstractions;
|
using System.IO.Abstractions;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using ManagedCommon;
|
using ManagedCommon;
|
||||||
using Microsoft.CmdPal.Ext.Apps.Utils;
|
using Microsoft.CmdPal.Ext.Apps.Utils;
|
||||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
using Windows.Win32;
|
using Windows.Win32;
|
||||||
using Windows.Win32.Foundation;
|
using Windows.Win32.Foundation;
|
||||||
|
using Windows.Win32.Storage.Packaging.Appx;
|
||||||
using Windows.Win32.System.Com;
|
using Windows.Win32.System.Com;
|
||||||
using static Microsoft.CmdPal.Ext.Apps.Utils.Native;
|
|
||||||
|
|
||||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||||
|
|
||||||
@@ -55,7 +57,7 @@ public partial class UWP
|
|||||||
FamilyName = package.FamilyName;
|
FamilyName = package.FamilyName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void InitializeAppInfo(string installedLocation)
|
public unsafe void InitializeAppInfo(string installedLocation)
|
||||||
{
|
{
|
||||||
Location = installedLocation;
|
Location = installedLocation;
|
||||||
LocationLocalized = ShellLocalization.Instance.GetLocalizedPath(installedLocation);
|
LocationLocalized = ShellLocalization.Instance.GetLocalizedPath(installedLocation);
|
||||||
@@ -65,26 +67,31 @@ public partial class UWP
|
|||||||
InitPackageVersion(namespaces);
|
InitPackageVersion(namespaces);
|
||||||
|
|
||||||
const uint noAttribute = 0x80;
|
const uint noAttribute = 0x80;
|
||||||
|
const uint STGMREAD = 0x00000000;
|
||||||
var access = (uint)STGM.READ;
|
try
|
||||||
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 =>
|
IStream* stream = null;
|
||||||
|
PInvoke.SHCreateStreamOnFileEx(path, STGMREAD, noAttribute, false, null, &stream).ThrowOnFailure();
|
||||||
|
using var streamHandle = new SafeComHandle((IntPtr)stream);
|
||||||
|
|
||||||
|
Apps = AppxPackageHelper.GetAppsFromManifest(stream).Select(appInManifest =>
|
||||||
|
{
|
||||||
|
using var appHandle = new SafeComHandle(appInManifest);
|
||||||
|
return new UWPApplication((IAppxManifestApplication*)appInManifest, this);
|
||||||
|
}).Where(a =>
|
||||||
{
|
{
|
||||||
var valid =
|
var valid =
|
||||||
!string.IsNullOrEmpty(a.UserModelId) &&
|
!string.IsNullOrEmpty(a.UserModelId) &&
|
||||||
!string.IsNullOrEmpty(a.DisplayName) &&
|
!string.IsNullOrEmpty(a.DisplayName) &&
|
||||||
a.AppListEntry != "none";
|
a.AppListEntry != "none";
|
||||||
|
|
||||||
return valid;
|
return valid;
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
else
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Apps = Array.Empty<UWPApplication>();
|
Apps = Array.Empty<UWPApplication>();
|
||||||
|
Logger.LogError($"Failed to initialize UWP app info for {Name} ({FullName}): {ex.Message}");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,35 +130,36 @@ public partial class UWP
|
|||||||
{
|
{
|
||||||
var windows10 = new Version(10, 0);
|
var windows10 = new Version(10, 0);
|
||||||
var support = Environment.OSVersion.Version.Major >= windows10.Major;
|
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 ex)
|
|
||||||
{
|
|
||||||
Logger.LogError(ex.Message);
|
|
||||||
return Array.Empty<UWPApplication>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return u.Apps;
|
if (!support)
|
||||||
});
|
|
||||||
|
|
||||||
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>();
|
return Array.Empty<UWPApplication>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var appsBag = new ConcurrentBag<UWPApplication>();
|
||||||
|
|
||||||
|
Parallel.ForEach(CurrentUserPackages(), p =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var u = new UWP(p);
|
||||||
|
u.InitializeAppInfo(p.InstalledLocation);
|
||||||
|
|
||||||
|
foreach (var app in u.Apps)
|
||||||
|
{
|
||||||
|
if (AllAppsSettings.Instance.DisabledProgramSources.All(x => x.UniqueIdentifier != app.UniqueIdentifier))
|
||||||
|
{
|
||||||
|
appsBag.Add(app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex.Message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return appsBag.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<IPackage> CurrentUserPackages()
|
private static IEnumerable<IPackage> CurrentUserPackages()
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO.Abstractions;
|
using System.IO.Abstractions;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using ManagedCommon;
|
using ManagedCommon;
|
||||||
@@ -13,7 +14,9 @@ using Microsoft.CmdPal.Ext.Apps.Commands;
|
|||||||
using Microsoft.CmdPal.Ext.Apps.Properties;
|
using Microsoft.CmdPal.Ext.Apps.Properties;
|
||||||
using Microsoft.CmdPal.Ext.Apps.Utils;
|
using Microsoft.CmdPal.Ext.Apps.Utils;
|
||||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
using static Microsoft.CmdPal.Ext.Apps.Utils.Native;
|
using Windows.Win32;
|
||||||
|
using Windows.Win32.Foundation;
|
||||||
|
using Windows.Win32.Storage.Packaging.Appx;
|
||||||
using PackageVersion = Microsoft.CmdPal.Ext.Apps.Programs.UWP.PackageVersion;
|
using PackageVersion = Microsoft.CmdPal.Ext.Apps.Programs.UWP.PackageVersion;
|
||||||
using Theme = Microsoft.CmdPal.Ext.Apps.Utils.Theme;
|
using Theme = Microsoft.CmdPal.Ext.Apps.Utils.Theme;
|
||||||
|
|
||||||
@@ -97,27 +100,27 @@ public class UWPApplication : IProgram
|
|||||||
return commands;
|
return commands;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UWPApplication(IAppxManifestApplication manifestApp, UWP package)
|
internal unsafe UWPApplication(IAppxManifestApplication* manifestApp, UWP package)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(manifestApp);
|
ArgumentNullException.ThrowIfNull(manifestApp);
|
||||||
|
|
||||||
var hr = manifestApp.GetAppUserModelId(out var tmpUserModelId);
|
var hr = manifestApp->GetAppUserModelId(out var tmpUserModelIdPtr);
|
||||||
UserModelId = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpUserModelId);
|
UserModelId = ComFreeHelper.GetStringAndFree(hr, tmpUserModelIdPtr);
|
||||||
|
|
||||||
hr = manifestApp.GetAppUserModelId(out var tmpUniqueIdentifier);
|
manifestApp->GetAppUserModelId(out var tmpUniqueIdentifierPtr);
|
||||||
UniqueIdentifier = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpUniqueIdentifier);
|
UniqueIdentifier = ComFreeHelper.GetStringAndFree(hr, tmpUniqueIdentifierPtr);
|
||||||
|
|
||||||
hr = manifestApp.GetStringValue("DisplayName", out var tmpDisplayName);
|
manifestApp->GetStringValue("DisplayName", out var tmpDisplayNamePtr);
|
||||||
DisplayName = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpDisplayName);
|
DisplayName = ComFreeHelper.GetStringAndFree(hr, tmpDisplayNamePtr);
|
||||||
|
|
||||||
hr = manifestApp.GetStringValue("Description", out var tmpDescription);
|
manifestApp->GetStringValue("Description", out var tmpDescriptionPtr);
|
||||||
Description = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpDescription);
|
Description = ComFreeHelper.GetStringAndFree(hr, tmpDescriptionPtr);
|
||||||
|
|
||||||
hr = manifestApp.GetStringValue("BackgroundColor", out var tmpBackgroundColor);
|
manifestApp->GetStringValue("BackgroundColor", out var tmpBackgroundColorPtr);
|
||||||
BackgroundColor = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpBackgroundColor);
|
BackgroundColor = ComFreeHelper.GetStringAndFree(hr, tmpBackgroundColorPtr);
|
||||||
|
|
||||||
hr = manifestApp.GetStringValue("EntryPoint", out var tmpEntryPoint);
|
manifestApp->GetStringValue("EntryPoint", out var tmpEntryPointPtr);
|
||||||
EntryPoint = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpEntryPoint);
|
EntryPoint = ComFreeHelper.GetStringAndFree(hr, tmpEntryPointPtr);
|
||||||
|
|
||||||
Package = package ?? throw new ArgumentNullException(nameof(package));
|
Package = package ?? throw new ArgumentNullException(nameof(package));
|
||||||
|
|
||||||
@@ -166,7 +169,7 @@ public class UWPApplication : IProgram
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal string ResourceFromPri(string packageFullName, string resourceReference)
|
internal unsafe string ResourceFromPri(string packageFullName, string resourceReference)
|
||||||
{
|
{
|
||||||
const string prefix = "ms-resource:";
|
const string prefix = "ms-resource:";
|
||||||
|
|
||||||
@@ -200,30 +203,8 @@ public class UWPApplication : IProgram
|
|||||||
parsedFallback = prefix + "///" + key;
|
parsedFallback = prefix + "///" + key;
|
||||||
}
|
}
|
||||||
|
|
||||||
var outBuffer = new StringBuilder(128);
|
if (string.IsNullOrEmpty(parsedFallback))
|
||||||
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
|
// https://github.com/Wox-launcher/Wox/issues/964
|
||||||
// known hresult 2147942522:
|
// known hresult 2147942522:
|
||||||
// 'Microsoft Corporation' violates pattern constraint of '\bms-resource:.{1,256}'.
|
// 'Microsoft Corporation' violates pattern constraint of '\bms-resource:.{1,256}'.
|
||||||
@@ -232,17 +213,40 @@ public class UWPApplication : IProgram
|
|||||||
// Microsoft.BingFoodAndDrink_3.0.4.336_x64__8wekyb3d8bbwe: ms-resource:AppDescription
|
// Microsoft.BingFoodAndDrink_3.0.4.336_x64__8wekyb3d8bbwe: ms-resource:AppDescription
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
var capacity = 1024U;
|
||||||
|
PWSTR outBuffer = new PWSTR((char*)(void*)Marshal.AllocHGlobal((int)capacity * sizeof(char)));
|
||||||
|
var source = $"@{{{packageFullName}? {parsed}}}";
|
||||||
|
void* reserved = null;
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
|
PInvoke.SHLoadIndirectString(source, outBuffer, capacity, ref reserved).ThrowOnFailure();
|
||||||
|
|
||||||
var loaded = outBuffer.ToString();
|
var loaded = outBuffer.ToString();
|
||||||
if (!string.IsNullOrEmpty(loaded))
|
return string.IsNullOrEmpty(loaded) ? string.Empty : loaded;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
return loaded;
|
var sourceFallback = $"@{{{packageFullName}?{parsedFallback}}}";
|
||||||
|
PInvoke.SHLoadIndirectString(sourceFallback, outBuffer, capacity, ref reserved).ThrowOnFailure();
|
||||||
|
var loaded = outBuffer.ToString();
|
||||||
|
return string.IsNullOrEmpty(loaded) ? string.Empty : loaded;
|
||||||
}
|
}
|
||||||
else
|
catch (Exception)
|
||||||
{
|
{
|
||||||
|
// ProgramLogger.Exception($"Unable to load resource {resourceReference} from {packageFullName}", new InvalidOperationException(), GetType(), packageFullName);
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal((IntPtr)outBuffer.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -258,13 +262,12 @@ public class UWPApplication : IProgram
|
|||||||
{ PackageVersion.Windows8, "SmallLogo" },
|
{ PackageVersion.Windows8, "SmallLogo" },
|
||||||
};
|
};
|
||||||
|
|
||||||
internal string LogoUriFromManifest(IAppxManifestApplication app)
|
internal unsafe string LogoUriFromManifest(IAppxManifestApplication* app)
|
||||||
{
|
{
|
||||||
if (_logoKeyFromVersion.TryGetValue(Package.Version, out var key))
|
if (_logoKeyFromVersion.TryGetValue(Package.Version, out var key))
|
||||||
{
|
{
|
||||||
var hr = app.GetStringValue(key, out var logoUriFromApp);
|
var hr = app->GetStringValue(key, out var logoUriFromAppPtr);
|
||||||
_ = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, logoUriFromApp);
|
return ComFreeHelper.GetStringAndFree(hr, logoUriFromAppPtr);
|
||||||
return logoUriFromApp;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -349,7 +352,7 @@ public class UWPApplication : IProgram
|
|||||||
var prefix = path.Substring(0, end);
|
var prefix = path.Substring(0, end);
|
||||||
var paths = new List<string> { };
|
var paths = new List<string> { };
|
||||||
const int appIconSize = 36;
|
const int appIconSize = 36;
|
||||||
var targetSizes = new List<int> { 16, 24, 30, 36, 44, 60, 72, 96, 128, 180, 256 }.AsParallel();
|
var targetSizes = new List<int> { 16, 24, 30, 36, 44, 60, 72, 96, 128, 180, 256 };
|
||||||
var pathFactorPairs = new Dictionary<string, int>();
|
var pathFactorPairs = new Dictionary<string, int>();
|
||||||
|
|
||||||
foreach (var factor in targetSizes)
|
foreach (var factor in targetSizes)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.Design;
|
using System.ComponentModel.Design;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
@@ -841,18 +842,69 @@ public class Win32Program : IProgram
|
|||||||
var disabledProgramsList = settings.DisabledProgramSources;
|
var disabledProgramsList = settings.DisabledProgramSources;
|
||||||
|
|
||||||
// Get all paths but exclude all normal .Executables
|
// Get all paths but exclude all normal .Executables
|
||||||
paths.UnionWith(sources
|
var pathBag = new ConcurrentBag<string>();
|
||||||
.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));
|
Parallel.ForEach(sources, source =>
|
||||||
var runCommandPrograms = runCommandPaths.AsParallel().Select(source => GetRunCommandProgramFromPath(source));
|
{
|
||||||
|
if (!source.IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var path in source.GetPaths())
|
||||||
|
{
|
||||||
|
if (disabledProgramsList.All(x => x.UniqueIdentifier != path) &&
|
||||||
|
!ExecutableApplicationExtensions.Contains(Extension(path)))
|
||||||
|
{
|
||||||
|
pathBag.Add(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
paths.UnionWith(pathBag);
|
||||||
|
|
||||||
|
var runCommandPathBag = new ConcurrentBag<string>();
|
||||||
|
|
||||||
|
Parallel.ForEach(runCommandSources, source =>
|
||||||
|
{
|
||||||
|
if (!source.IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var path in source.GetPaths())
|
||||||
|
{
|
||||||
|
if (disabledProgramsList.All(x => x.UniqueIdentifier != path))
|
||||||
|
{
|
||||||
|
runCommandPathBag.Add(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
runCommandPaths.UnionWith(runCommandPathBag);
|
||||||
|
|
||||||
|
var programsList = new ConcurrentBag<Win32Program>();
|
||||||
|
Parallel.ForEach(paths, source =>
|
||||||
|
{
|
||||||
|
var program = GetProgramFromPath(source);
|
||||||
|
if (program != null)
|
||||||
|
{
|
||||||
|
programsList.Add(program);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var runCommandProgramsList = new ConcurrentBag<Win32Program>();
|
||||||
|
Parallel.ForEach(runCommandPaths, source =>
|
||||||
|
{
|
||||||
|
var program = GetRunCommandProgramFromPath(source);
|
||||||
|
if (program != null)
|
||||||
|
{
|
||||||
|
runCommandProgramsList.Add(program);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var programs = programsList.ToList();
|
||||||
|
var runCommandPrograms = runCommandProgramsList.ToList();
|
||||||
|
|
||||||
return DeduplicatePrograms(programs.Concat(runCommandPrograms).Where(program => program?.Valid == true));
|
return DeduplicatePrograms(programs.Concat(runCommandPrograms).Where(program => program?.Valid == true));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,11 +69,6 @@ public class ListRepository<T> : IRepository<T>, IEnumerable<T>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ParallelQuery<T> AsParallel()
|
|
||||||
{
|
|
||||||
return _items.Values.AsParallel();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Contains(T item)
|
public bool Contains(T item)
|
||||||
{
|
{
|
||||||
if (item is not null)
|
if (item is not null)
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ using System;
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Abstractions;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using ManagedCommon;
|
using ManagedCommon;
|
||||||
using Microsoft.CmdPal.Ext.Apps.Programs;
|
using Microsoft.CmdPal.Ext.Apps.Programs;
|
||||||
@@ -15,11 +15,9 @@ using Win32Program = Microsoft.CmdPal.Ext.Apps.Programs.Win32Program;
|
|||||||
|
|
||||||
namespace Microsoft.CmdPal.Ext.Apps.Storage;
|
namespace Microsoft.CmdPal.Ext.Apps.Storage;
|
||||||
|
|
||||||
|
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
|
||||||
internal sealed partial class Win32ProgramRepository : ListRepository<Programs.Win32Program>, IProgramRepository
|
internal sealed partial class Win32ProgramRepository : ListRepository<Programs.Win32Program>, IProgramRepository
|
||||||
{
|
{
|
||||||
private static readonly IFileSystem FileSystem = new FileSystem();
|
|
||||||
private static readonly IPath Path = FileSystem.Path;
|
|
||||||
|
|
||||||
private const string LnkExtension = ".lnk";
|
private const string LnkExtension = ".lnk";
|
||||||
private const string UrlExtension = ".url";
|
private const string UrlExtension = ".url";
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// 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 Windows.Win32;
|
||||||
|
using Windows.Win32.Foundation;
|
||||||
|
using Windows.Win32.System.Com;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Ext.Apps.Utils;
|
||||||
|
|
||||||
|
public static class ComFreeHelper
|
||||||
|
{
|
||||||
|
internal static unsafe string GetStringAndFree(HRESULT hr, PWSTR ptr)
|
||||||
|
{
|
||||||
|
hr.ThrowOnFailure();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return ptr.ToString();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
PInvoke.CoTaskMemFree(ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static unsafe void ComObjectRelease<T>(T* comPtr)
|
||||||
|
where T : unmanaged
|
||||||
|
{
|
||||||
|
if (comPtr != null)
|
||||||
|
{
|
||||||
|
((IUnknown*)comPtr)->Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
// 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.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Microsoft.CmdPal.Ext.Apps.Utils;
|
|
||||||
|
|
||||||
[SuppressMessage("Interoperability", "CA1401:P/Invokes should not be visible", Justification = "We want plugins to share this NativeMethods class, instead of each one creating its own.")]
|
|
||||||
public sealed class Native
|
|
||||||
{
|
|
||||||
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
|
|
||||||
public static extern int SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, int cchOutBuf, nint ppvReserved);
|
|
||||||
|
|
||||||
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
||||||
public static extern int SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string path, nint pbc, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out IShellItem shellItem);
|
|
||||||
|
|
||||||
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
|
|
||||||
public static extern HRESULT SHCreateStreamOnFileEx(string fileName, STGM grfMode, uint attributes, bool create, System.Runtime.InteropServices.ComTypes.IStream reserved, out System.Runtime.InteropServices.ComTypes.IStream stream);
|
|
||||||
|
|
||||||
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
|
|
||||||
public static extern HRESULT SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, uint cchOutBuf, nint ppvReserved);
|
|
||||||
|
|
||||||
public enum HRESULT : uint
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Operation successful.
|
|
||||||
/// </summary>
|
|
||||||
S_OK = 0x00000000,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Operation successful. (negative condition/no operation)
|
|
||||||
/// </summary>
|
|
||||||
S_FALSE = 0x00000001,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Not implemented.
|
|
||||||
/// </summary>
|
|
||||||
E_NOTIMPL = 0x80004001,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// No such interface supported.
|
|
||||||
/// </summary>
|
|
||||||
E_NOINTERFACE = 0x80004002,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Pointer that is not valid.
|
|
||||||
/// </summary>
|
|
||||||
E_POINTER = 0x80004003,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Operation aborted.
|
|
||||||
/// </summary>
|
|
||||||
E_ABORT = 0x80004004,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unspecified failure.
|
|
||||||
/// </summary>
|
|
||||||
E_FAIL = 0x80004005,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unexpected failure.
|
|
||||||
/// </summary>
|
|
||||||
E_UNEXPECTED = 0x8000FFFF,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// General access denied error.
|
|
||||||
/// </summary>
|
|
||||||
E_ACCESSDENIED = 0x80070005,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handle that is not valid.
|
|
||||||
/// </summary>
|
|
||||||
E_HANDLE = 0x80070006,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Failed to allocate necessary memory.
|
|
||||||
/// </summary>
|
|
||||||
E_OUTOFMEMORY = 0x8007000E,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// One or more arguments are not valid.
|
|
||||||
/// </summary>
|
|
||||||
E_INVALIDARG = 0x80070057,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The operation was canceled by the user. (Error source 7 means Win32.)
|
|
||||||
/// </summary>
|
|
||||||
/// <SeeAlso href="https://learn.microsoft.com/windows/win32/debug/system-error-codes--1000-1299-"/>
|
|
||||||
/// <SeeAlso href="https://en.wikipedia.org/wiki/HRESULT"/>
|
|
||||||
E_CANCELLED = 0x800704C7,
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ShellItemTypeConstants
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Guid for type IShellItem.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly Guid ShellItemGuid = new("43826d1e-e718-42ee-bc55-a1e261c37bfe");
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Guid for type IShellItem2.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly Guid ShellItem2Guid = new("7E9FB0D3-919F-4307-AB2E-9B1860310C93");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The following are ShellItem DisplayName types.
|
|
||||||
/// </summary>
|
|
||||||
[Flags]
|
|
||||||
public enum SIGDN : uint
|
|
||||||
{
|
|
||||||
NORMALDISPLAY = 0,
|
|
||||||
PARENTRELATIVEPARSING = 0x80018001,
|
|
||||||
PARENTRELATIVEFORADDRESSBAR = 0x8001c001,
|
|
||||||
DESKTOPABSOLUTEPARSING = 0x80028000,
|
|
||||||
PARENTRELATIVEEDITING = 0x80031001,
|
|
||||||
DESKTOPABSOLUTEEDITING = 0x8004c000,
|
|
||||||
FILESYSPATH = 0x80058000,
|
|
||||||
URL = 0x80068000,
|
|
||||||
}
|
|
||||||
|
|
||||||
[ComImport]
|
|
||||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
||||||
[Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")]
|
|
||||||
public interface IShellItem
|
|
||||||
{
|
|
||||||
void BindToHandler(
|
|
||||||
nint pbc,
|
|
||||||
[MarshalAs(UnmanagedType.LPStruct)] Guid bhid,
|
|
||||||
[MarshalAs(UnmanagedType.LPStruct)] Guid riid,
|
|
||||||
out nint ppv);
|
|
||||||
|
|
||||||
void GetParent(out IShellItem ppsi);
|
|
||||||
|
|
||||||
void GetDisplayName(SIGDN sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName);
|
|
||||||
|
|
||||||
void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs);
|
|
||||||
|
|
||||||
void Compare(IShellItem psi, uint hint, out int piOrder);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// <see href="https://learn.microsoft.com/windows/win32/stg/stgm-constants">see all STGM values</see>
|
|
||||||
/// </summary>
|
|
||||||
[Flags]
|
|
||||||
public enum STGM : long
|
|
||||||
{
|
|
||||||
READ = 0x00000000L,
|
|
||||||
WRITE = 0x00000001L,
|
|
||||||
READWRITE = 0x00000002L,
|
|
||||||
CREATE = 0x00001000L,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// 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.Utils;
|
||||||
|
|
||||||
|
public partial class SafeComHandle : SafeHandle
|
||||||
|
{
|
||||||
|
public SafeComHandle()
|
||||||
|
: base(IntPtr.Zero, ownsHandle: true)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public SafeComHandle(IntPtr handle)
|
||||||
|
: base(IntPtr.Zero, ownsHandle: true)
|
||||||
|
{
|
||||||
|
SetHandle(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool IsInvalid => handle == IntPtr.Zero;
|
||||||
|
|
||||||
|
protected override bool ReleaseHandle()
|
||||||
|
{
|
||||||
|
var count = Marshal.Release(handle);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,124 +3,17 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Runtime.InteropServices.ComTypes;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using ManagedCommon;
|
using ManagedCommon;
|
||||||
|
using Windows.Win32;
|
||||||
|
using Windows.Win32.Foundation;
|
||||||
|
using Windows.Win32.System.Com;
|
||||||
|
using Windows.Win32.UI.Shell;
|
||||||
|
|
||||||
namespace Microsoft.CmdPal.Ext.Apps.Utils;
|
namespace Microsoft.CmdPal.Ext.Apps.Utils;
|
||||||
|
|
||||||
public class ShellLinkHelper : IShellLinkHelper
|
public class ShellLinkHelper : IShellLinkHelper
|
||||||
{
|
{
|
||||||
[Flags]
|
|
||||||
private enum SLGP_FLAGS
|
|
||||||
{
|
|
||||||
SLGP_SHORTPATH = 0x1,
|
|
||||||
SLGP_UNCPRIORITY = 0x2,
|
|
||||||
SLGP_RAWPATH = 0x4,
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Matching COM")]
|
|
||||||
private struct WIN32_FIND_DATAW
|
|
||||||
{
|
|
||||||
public uint dwFileAttributes;
|
|
||||||
public long ftCreationTime;
|
|
||||||
public long ftLastAccessTime;
|
|
||||||
public long ftLastWriteTime;
|
|
||||||
public uint nFileSizeHigh;
|
|
||||||
public uint nFileSizeLow;
|
|
||||||
public uint dwReserved0;
|
|
||||||
public uint dwReserved1;
|
|
||||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
|
|
||||||
public string cFileName;
|
|
||||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
|
|
||||||
public string cAlternateFileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Flags]
|
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Implements COM Interface")]
|
|
||||||
public enum SLR_FLAGS
|
|
||||||
{
|
|
||||||
SLR_NO_UI = 0x1,
|
|
||||||
SLR_ANY_MATCH = 0x2,
|
|
||||||
SLR_UPDATE = 0x4,
|
|
||||||
SLR_NOUPDATE = 0x8,
|
|
||||||
SLR_NOSEARCH = 0x10,
|
|
||||||
SLR_NOTRACK = 0x20,
|
|
||||||
SLR_NOLINKINFO = 0x40,
|
|
||||||
SLR_INVOKE_MSI = 0x80,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reference : http://www.pinvoke.net/default.aspx/Interfaces.IShellLinkW
|
|
||||||
|
|
||||||
// The IShellLink interface allows Shell links to be created, modified, and resolved
|
|
||||||
[ComImport]
|
|
||||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
||||||
[Guid("000214F9-0000-0000-C000-000000000046")]
|
|
||||||
private interface IShellLinkW
|
|
||||||
{
|
|
||||||
/// <summary>Retrieves the path and file name of a Shell link object</summary>
|
|
||||||
void GetPath([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, ref WIN32_FIND_DATAW pfd, SLGP_FLAGS fFlags);
|
|
||||||
|
|
||||||
/// <summary>Retrieves the list of item identifiers for a Shell link object</summary>
|
|
||||||
void GetIDList(out nint ppidl);
|
|
||||||
|
|
||||||
/// <summary>Sets the pointer to an item identifier list (PIDL) for a Shell link object.</summary>
|
|
||||||
void SetIDList(nint pidl);
|
|
||||||
|
|
||||||
/// <summary>Retrieves the description string for a Shell link object</summary>
|
|
||||||
void GetDescription([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName);
|
|
||||||
|
|
||||||
/// <summary>Sets the description for a Shell link object. The description can be any application-defined string</summary>
|
|
||||||
void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
|
|
||||||
|
|
||||||
/// <summary>Retrieves the name of the working directory for a Shell link object</summary>
|
|
||||||
void GetWorkingDirectory([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath);
|
|
||||||
|
|
||||||
/// <summary>Sets the name of the working directory for a Shell link object</summary>
|
|
||||||
void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
|
|
||||||
|
|
||||||
/// <summary>Retrieves the command-line arguments associated with a Shell link object</summary>
|
|
||||||
void GetArguments([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath);
|
|
||||||
|
|
||||||
/// <summary>Sets the command-line arguments for a Shell link object</summary>
|
|
||||||
void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
|
|
||||||
|
|
||||||
/// <summary>Retrieves the hot key for a Shell link object</summary>
|
|
||||||
void GetHotkey(out short pwHotkey);
|
|
||||||
|
|
||||||
/// <summary>Sets a hot key for a Shell link object</summary>
|
|
||||||
void SetHotkey(short wHotkey);
|
|
||||||
|
|
||||||
/// <summary>Retrieves the show command for a Shell link object</summary>
|
|
||||||
void GetShowCmd(out int piShowCmd);
|
|
||||||
|
|
||||||
/// <summary>Sets the show command for a Shell link object. The show command sets the initial show state of the window.</summary>
|
|
||||||
void SetShowCmd(int iShowCmd);
|
|
||||||
|
|
||||||
/// <summary>Retrieves the location (path and index) of the icon for a Shell link object</summary>
|
|
||||||
void GetIconLocation([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, int cchIconPath, out int piIcon);
|
|
||||||
|
|
||||||
/// <summary>Sets the location (path and index) of the icon for a Shell link object</summary>
|
|
||||||
void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
|
|
||||||
|
|
||||||
/// <summary>Sets the relative path to the Shell link object</summary>
|
|
||||||
void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved);
|
|
||||||
|
|
||||||
/// <summary>Attempts to find the target of a Shell link, even if it has been moved or renamed</summary>
|
|
||||||
void Resolve(ref nint hwnd, SLR_FLAGS fFlags);
|
|
||||||
|
|
||||||
/// <summary>Sets the path and file name of a Shell link object</summary>
|
|
||||||
void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
[ComImport]
|
|
||||||
[Guid("00021401-0000-0000-C000-000000000046")]
|
|
||||||
private class ShellLink
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// Contains the description of the app
|
// Contains the description of the app
|
||||||
public string Description { get; set; } = string.Empty;
|
public string Description { get; set; } = string.Empty;
|
||||||
|
|
||||||
@@ -130,60 +23,63 @@ public class ShellLinkHelper : IShellLinkHelper
|
|||||||
public bool HasArguments { get; set; }
|
public bool HasArguments { get; set; }
|
||||||
|
|
||||||
// Retrieve the target path using Shell Link
|
// Retrieve the target path using Shell Link
|
||||||
public string RetrieveTargetPath(string path)
|
public unsafe string RetrieveTargetPath(string path)
|
||||||
{
|
{
|
||||||
var link = new ShellLink();
|
var target = string.Empty;
|
||||||
const int STGM_READ = 0;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
((IPersistFile)link).Load(path, STGM_READ);
|
|
||||||
}
|
|
||||||
catch (System.IO.FileNotFoundException ex)
|
|
||||||
{
|
|
||||||
Logger.LogError(ex.Message);
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
var hwnd = default(nint);
|
|
||||||
((IShellLinkW)link).Resolve(ref hwnd, 0);
|
|
||||||
|
|
||||||
const int MAX_PATH = 260;
|
const int MAX_PATH = 260;
|
||||||
var buffer = new StringBuilder(MAX_PATH);
|
IShellLinkW* link = null;
|
||||||
|
|
||||||
var data = default(WIN32_FIND_DATAW);
|
PInvoke.CoCreateInstance(typeof(ShellLink).GUID, null, CLSCTX.CLSCTX_INPROC_SERVER, out link).ThrowOnFailure();
|
||||||
((IShellLinkW)link).GetPath(buffer, buffer.Capacity, ref data, SLGP_FLAGS.SLGP_SHORTPATH);
|
using var linkHandle = new SafeComHandle((IntPtr)link);
|
||||||
var target = buffer.ToString();
|
|
||||||
|
const int STGMREAD = 0;
|
||||||
|
|
||||||
|
IPersistFile* persistFile = null;
|
||||||
|
Guid iid = typeof(IPersistFile).GUID;
|
||||||
|
((IUnknown*)link)->QueryInterface(&iid, (void**)&persistFile);
|
||||||
|
if (persistFile != null)
|
||||||
|
{
|
||||||
|
using var persistFileHandle = new SafeComHandle((IntPtr)persistFile);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
persistFile->Load(path, STGMREAD);
|
||||||
|
}
|
||||||
|
catch (System.IO.FileNotFoundException)
|
||||||
|
{
|
||||||
|
// Log.Exception($"Failed to load {path}, {e.Message}", e, GetType());
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var hwnd = HWND.Null;
|
||||||
|
const uint SLR_NO_UI = 0x1;
|
||||||
|
link->Resolve(hwnd, SLR_NO_UI);
|
||||||
|
|
||||||
|
var buffer = stackalloc char[MAX_PATH];
|
||||||
|
|
||||||
|
var hr = link->GetPath((PWSTR)buffer, MAX_PATH, null, 0x1);
|
||||||
|
|
||||||
|
target = hr.Succeeded ? new string(buffer) : string.Empty;
|
||||||
|
|
||||||
// To set the app description
|
// To set the app description
|
||||||
if (!string.IsNullOrEmpty(target))
|
if (!string.IsNullOrEmpty(target))
|
||||||
{
|
{
|
||||||
buffer = new StringBuilder(MAX_PATH);
|
var descBuffer = stackalloc char[MAX_PATH];
|
||||||
try
|
var desHr = link->GetDescription(descBuffer, MAX_PATH);
|
||||||
{
|
Description = desHr.Succeeded ? new string(descBuffer) : string.Empty;
|
||||||
((IShellLinkW)link).GetDescription(buffer, MAX_PATH);
|
|
||||||
Description = buffer.ToString();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.LogError(ex.Message);
|
|
||||||
Description = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
var argumentBuffer = new StringBuilder(MAX_PATH);
|
var argsBuffer = stackalloc char[MAX_PATH];
|
||||||
((IShellLinkW)link).GetArguments(argumentBuffer, argumentBuffer.Capacity);
|
var argHr = link->GetArguments(argsBuffer, MAX_PATH);
|
||||||
Arguments = argumentBuffer.ToString();
|
|
||||||
|
Arguments = argHr.Succeeded ? new string(argsBuffer) : string.Empty;
|
||||||
|
|
||||||
// Set variable to true if the program takes in any arguments
|
// Set variable to true if the program takes in any arguments
|
||||||
if (argumentBuffer.Length != 0)
|
if (Arguments.Length != 0)
|
||||||
{
|
{
|
||||||
HasArguments = true;
|
HasArguments = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// To release unmanaged memory
|
|
||||||
Marshal.ReleaseComObject(link);
|
|
||||||
|
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using static Microsoft.CmdPal.Ext.Apps.Utils.Native;
|
using Windows.Win32;
|
||||||
|
using Windows.Win32.UI.Shell;
|
||||||
|
|
||||||
namespace Microsoft.CmdPal.Ext.Apps.Utils;
|
namespace Microsoft.CmdPal.Ext.Apps.Utils;
|
||||||
|
|
||||||
@@ -23,7 +24,7 @@ public class ShellLocalization
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">Path to the shell item (e. g. shortcut 'File Explorer.lnk').</param>
|
/// <param name="path">Path to the shell item (e. g. shortcut 'File Explorer.lnk').</param>
|
||||||
/// <returns>The localized name as string or <see cref="string.Empty"/>.</returns>
|
/// <returns>The localized name as string or <see cref="string.Empty"/>.</returns>
|
||||||
public string GetLocalizedName(string path)
|
public unsafe string GetLocalizedName(string path)
|
||||||
{
|
{
|
||||||
var lowerInvariantPath = path.ToLowerInvariant();
|
var lowerInvariantPath = path.ToLowerInvariant();
|
||||||
|
|
||||||
@@ -33,18 +34,29 @@ public class ShellLocalization
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
var shellItemType = ShellItemTypeConstants.ShellItemGuid;
|
void* shellItemPtrVoid = null;
|
||||||
var retCode = SHCreateItemFromParsingName(path, nint.Zero, ref shellItemType, out var shellItem);
|
try
|
||||||
if (retCode != 0)
|
{
|
||||||
|
var retCode = PInvoke.SHCreateItemFromParsingName(path, null, typeof(IShellItem).GUID, out shellItemPtrVoid).ThrowOnFailure();
|
||||||
|
using var shellItemHandle = new SafeComHandle((IntPtr)shellItemPtrVoid);
|
||||||
|
IShellItem* shellItemPtr = (IShellItem*)shellItemPtrVoid;
|
||||||
|
|
||||||
|
var hr = shellItemPtr->GetDisplayName(SIGDN.SIGDN_NORMALDISPLAY, out var filenamePtr);
|
||||||
|
|
||||||
|
var filename = ComFreeHelper.GetStringAndFree(hr, filenamePtr);
|
||||||
|
|
||||||
|
if (filename == null)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = _localizationCache.TryAdd(lowerInvariantPath, filename);
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
{
|
{
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
shellItem.GetDisplayName(SIGDN.NORMALDISPLAY, out var filename);
|
|
||||||
|
|
||||||
_ = _localizationCache.TryAdd(lowerInvariantPath, filename);
|
|
||||||
|
|
||||||
return filename;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user