From 52f25619373ccb9af0fa0fbc7da916297ecac897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pol=C3=A1=C5=A1ek?= Date: Wed, 3 Dec 2025 17:16:25 +0100 Subject: [PATCH] CmdPal: Find app for WinGet package (#43943) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary of the Pull Request This PR introduces a bit of dark magic to resolve the correct installed app for a given WinGet package: - Packaged apps: matched using their package family name. - Everything else: matched using the product code (GUID) and heuristic registry lookup. - The registry rarely stores the executable path directly, so the logic compares install locations with known apps. - It attempts to pick the best candidate while avoiding uninstallers. - It’s not science — let’s call it `#666666` magic. - MSI API support was removed because it's too slow for this scenario. - If no reliable match is found, the command is skipped for now. The future plan is to redirect the user to the list of installed apps and search by display name, but that needs some supporting infrastructure first. - The command order for WinGet list entries was updated: **Install / Uninstall** is now the primary action, ensuring a stable UI since this command is always available. ## PR Checklist - [x] Closes: #43671 - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx ## Detailed Description of the Pull Request / Additional comments ## Validation Steps Performed --- .github/actions/spell-check/expect.txt | 2 + .../cmdpal/Microsoft.CmdPal.UI/App.xaml.cs | 5 +- .../AllAppsCommandProviderTests.cs | 6 +- .../AllAppsCommandProvider.cs | 68 +++++- .../ext/Microsoft.CmdPal.Ext.Apps/AppItem.cs | 4 + .../Microsoft.CmdPal.Ext.Apps/AppListItem.cs | 8 + .../Helpers/UninstallRegistryAppLocator.cs | 205 ++++++++++++++++++ .../Programs/UWPApplication.cs | 1 + .../Programs/Win32Program.cs | 1 + .../Pages/InstallPackageCommand.cs | 2 +- .../Pages/InstallPackageListItem.cs | 97 +++++++-- .../WinGetExtensionCommandsProvider.cs | 6 +- .../WinGetStatics.cs | 4 +- 13 files changed, 376 insertions(+), 33 deletions(-) create mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Helpers/UninstallRegistryAppLocator.cs diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 0d68b5537c..77385974f4 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -773,6 +773,7 @@ INITGUID INITTOLOGFONTSTRUCT INLINEPREFIX inlines +Inno INPC inproc INPUTHARDWARE @@ -1848,6 +1849,7 @@ UNCPRIORITY UNDNAME UNICODETEXT unins +Uninstaller uninstalls Uniquifies unitconverter diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml.cs index f91b9e304a..53f47286b2 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml.cs @@ -135,8 +135,9 @@ public partial class App : Application try { var winget = new WinGetExtensionCommandsProvider(); - var callback = allApps.LookupApp; - winget.SetAllLookup(callback); + winget.SetAllLookup( + query => allApps.LookupAppByPackageFamilyName(query, requireSingleMatch: true), + query => allApps.LookupAppByProductCode(query, requireSingleMatch: true)); services.AddSingleton(winget); } catch (Exception ex) diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Apps.UnitTests/AllAppsCommandProviderTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Apps.UnitTests/AllAppsCommandProviderTests.cs index e7fbc6859d..cc24433931 100644 --- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Apps.UnitTests/AllAppsCommandProviderTests.cs +++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Apps.UnitTests/AllAppsCommandProviderTests.cs @@ -58,7 +58,7 @@ public class AllAppsCommandProviderTests : AppsTestBase var provider = new AllAppsCommandProvider(page); // Act - var result = provider.LookupApp(string.Empty); + var result = provider.LookupAppByDisplayName(string.Empty); // Assert Assert.IsNotNull(result); @@ -77,7 +77,7 @@ public class AllAppsCommandProviderTests : AppsTestBase await WaitForPageInitializationAsync(); // Act - var result = provider.LookupApp("TestApp"); + var result = provider.LookupAppByDisplayName("TestApp"); // Assert Assert.IsNotNull(result); @@ -97,7 +97,7 @@ public class AllAppsCommandProviderTests : AppsTestBase await WaitForPageInitializationAsync(); // Act - var result = provider.LookupApp("NonExistentApp"); + var result = provider.LookupAppByDisplayName("NonExistentApp"); // Assert Assert.IsNull(result); diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AllAppsCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AllAppsCommandProvider.cs index 7232e955d7..317087847e 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AllAppsCommandProvider.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AllAppsCommandProvider.cs @@ -4,6 +4,8 @@ using System; using System.Collections.Generic; +using Microsoft.CmdPal.Ext.Apps.Helpers; +using Microsoft.CmdPal.Ext.Apps.Programs; using Microsoft.CmdPal.Ext.Apps.Properties; using Microsoft.CmdPal.Ext.Apps.State; using Microsoft.CommandPalette.Extensions; @@ -66,7 +68,71 @@ public partial class AllAppsCommandProvider : CommandProvider public override ICommandItem[] TopLevelCommands() => [_listItem, .. _page.GetPinnedApps()]; - public ICommandItem? LookupApp(string displayName) + public ICommandItem? LookupAppByPackageFamilyName(string packageFamilyName, bool requireSingleMatch) + { + if (string.IsNullOrEmpty(packageFamilyName)) + { + return null; + } + + var items = _page.GetItems(); + List matches = []; + + foreach (var item in items) + { + if (item is AppListItem appItem && string.Equals(packageFamilyName, appItem.App.PackageFamilyName, StringComparison.OrdinalIgnoreCase)) + { + matches.Add(item); + if (!requireSingleMatch) + { + // Return early if we don't require uniqueness. + return item; + } + } + } + + return requireSingleMatch && matches.Count == 1 ? matches[0] : null; + } + + public ICommandItem? LookupAppByProductCode(string productCode, bool requireSingleMatch) + { + if (string.IsNullOrEmpty(productCode)) + { + return null; + } + + if (!UninstallRegistryAppLocator.TryGetInstallInfo(productCode, out _, out var candidates) || candidates.Count <= 0) + { + return null; + } + + var items = _page.GetItems(); + List matches = []; + + foreach (var item in items) + { + if (item is not AppListItem appListItem || string.IsNullOrEmpty(appListItem.App.FullExecutablePath)) + { + continue; + } + + foreach (var candidate in candidates) + { + if (string.Equals(appListItem.App.FullExecutablePath, candidate, StringComparison.OrdinalIgnoreCase)) + { + matches.Add(item); + if (!requireSingleMatch) + { + return item; + } + } + } + } + + return requireSingleMatch && matches.Count == 1 ? matches[0] : null; + } + + public ICommandItem? LookupAppByDisplayName(string displayName) { var items = _page.GetItems(); diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AppItem.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AppItem.cs index b7d01593cb..14f9597418 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AppItem.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AppItem.cs @@ -29,6 +29,10 @@ public sealed class AppItem public string AppIdentifier { get; set; } = string.Empty; + public string? PackageFamilyName { get; set; } + + public string? FullExecutablePath { get; set; } + public AppItem() { } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AppListItem.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AppListItem.cs index e99ffae352..5689b70698 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AppListItem.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AppListItem.cs @@ -40,6 +40,8 @@ public sealed partial class AppListItem : ListItem public string AppIdentifier => _app.AppIdentifier; + public AppItem App => _app; + public AppListItem(AppItem app, bool useThumbnails, bool isPinned) { Command = _appCommand = new AppCommand(app); @@ -82,6 +84,12 @@ public sealed partial class AppListItem : ListItem metadata.Add(new DetailsElement() { Key = "Path", Data = new DetailsLink() { Text = _app.ExePath } }); } +#if DEBUG + metadata.Add(new DetailsElement() { Key = "[DEBUG] AppIdentifier", Data = new DetailsLink() { Text = _app.AppIdentifier } }); + metadata.Add(new DetailsElement() { Key = "[DEBUG] ExePath", Data = new DetailsLink() { Text = _app.ExePath } }); + metadata.Add(new DetailsElement() { Key = "[DEBUG] IcoPath", Data = new DetailsLink() { Text = _app.IcoPath } }); +#endif + // Icon IconInfo? heroImage = null; if (_app.IsPackaged) diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Helpers/UninstallRegistryAppLocator.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Helpers/UninstallRegistryAppLocator.cs new file mode 100644 index 0000000000..8e59a26395 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Helpers/UninstallRegistryAppLocator.cs @@ -0,0 +1,205 @@ +// 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; +using System.Linq; +using Microsoft.Win32; + +namespace Microsoft.CmdPal.Ext.Apps.Helpers; + +internal static class UninstallRegistryAppLocator +{ + private static readonly string[] UninstallBaseKeys = + [ + @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", + @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall", + ]; + + /// + /// Tries to find install directory and a list of plausible main EXEs from an uninstall key + /// (e.g. Inno Setup keys like "{guid}_is1"). + /// may be empty if we couldn't pick any safe EXEs. + /// + /// + /// Returns true if the uninstall key is found and an install directory is resolved. + /// + public static bool TryGetInstallInfo( + string uninstallKeyName, + out string? installDir, + out IReadOnlyList exeCandidates, + string? expectedExeName = null) + { + installDir = null; + exeCandidates = []; + + if (string.IsNullOrWhiteSpace(uninstallKeyName)) + { + throw new ArgumentException("Key name must not be null or empty.", nameof(uninstallKeyName)); + } + + uninstallKeyName = uninstallKeyName.Trim(); + + foreach (var baseKeyPath in UninstallBaseKeys) + { + // HKLM + using (var key = Registry.LocalMachine.OpenSubKey($"{baseKeyPath}\\{uninstallKeyName}")) + { + if (TryFromUninstallKey(key, expectedExeName, out installDir, out exeCandidates)) + { + return true; + } + } + + // HKCU + using (var key = Registry.CurrentUser.OpenSubKey($"{baseKeyPath}\\{uninstallKeyName}")) + { + if (TryFromUninstallKey(key, expectedExeName, out installDir, out exeCandidates)) + { + return true; + } + } + } + + return false; + } + + private static bool TryFromUninstallKey( + RegistryKey? key, + string? expectedExeName, + out string? installDir, + out IReadOnlyList exeCandidates) + { + installDir = null; + exeCandidates = []; + + if (key is null) + { + return false; + } + + var location = (key.GetValue("InstallLocation") as string)?.Trim('"', ' ', '\t'); + if (string.IsNullOrEmpty(location)) + { + location = (key.GetValue("Inno Setup: App Path") as string)?.Trim('"', ' ', '\t'); + } + + if (string.IsNullOrEmpty(location)) + { + var uninstall = key.GetValue("UninstallString") as string; + var uninsExe = ExtractFirstPath(uninstall); + if (!string.IsNullOrEmpty(uninsExe)) + { + var dir = Path.GetDirectoryName(uninsExe); + if (!string.IsNullOrEmpty(dir) && Directory.Exists(dir)) + { + location = dir; + } + } + } + + if (string.IsNullOrEmpty(location) || !Directory.Exists(location)) + { + return false; + } + + installDir = location; + + // Collect safe EXE candidates; may be empty if ambiguous or only uninstall exes exist. + exeCandidates = GetExeCandidates(location, expectedExeName); + return true; + } + + private static IReadOnlyList GetExeCandidates(string root, string? expectedExeName) + { + // Look at root and a "bin" subfolder (very common pattern) + var allExes = Directory.EnumerateFiles(root, "*.exe", SearchOption.TopDirectoryOnly) + .Concat(GetBinExes(root)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToArray(); + + if (allExes.Length == 0) + { + return []; + } + + var result = new List(); + + // 1) Exact match on expected exe name (if provided), ignoring case, and not uninstall/setup-like. + if (!string.IsNullOrWhiteSpace(expectedExeName)) + { + foreach (var exe in allExes) + { + if (string.Equals(Path.GetFileName(exe), expectedExeName, StringComparison.OrdinalIgnoreCase) && + !LooksLikeUninstallerOrSetup(exe)) + { + result.Add(exe); + } + } + } + + // 2) All other non-uninstall/setup exes + foreach (var exe in allExes) + { + if (LooksLikeUninstallerOrSetup(exe)) + { + continue; + } + + // Skip ones already added as expectedExeName matches + if (result.Contains(exe, StringComparer.OrdinalIgnoreCase)) + { + continue; + } + + result.Add(exe); + } + + // 3) We intentionally do NOT add uninstall/setup/update exes here. + // If you ever want them, you can add a separate API to expose them. + return result; + } + + private static IEnumerable GetBinExes(string root) + { + var bin = Path.Combine(root, "bin"); + return !Directory.Exists(bin) + ? [] + : Directory.EnumerateFiles(bin, "*.exe", SearchOption.TopDirectoryOnly); + } + + private static bool LooksLikeUninstallerOrSetup(string path) + { + var name = Path.GetFileName(path); + return name.StartsWith("unins", StringComparison.OrdinalIgnoreCase) // e.g. Inno: unins000.exe + || name.Contains("setup", StringComparison.OrdinalIgnoreCase) // setup.exe + || name.Contains("installer", StringComparison.OrdinalIgnoreCase) // installer.exe / MyAppInstaller.exe + || name.Contains("update", StringComparison.OrdinalIgnoreCase); // updater/updater.exe + } + + private static string? ExtractFirstPath(string? commandLine) + { + if (string.IsNullOrWhiteSpace(commandLine)) + { + return null; + } + + commandLine = commandLine.Trim(); + + if (commandLine.StartsWith('"')) + { + var endQuote = commandLine.IndexOf('"', 1); + if (endQuote > 1) + { + return commandLine[1..endQuote]; + } + } + + var firstSpace = commandLine.IndexOf(' '); + var candidate = firstSpace > 0 ? commandLine[..firstSpace] : commandLine; + candidate = candidate.Trim('"'); + return candidate.Length > 0 ? candidate : null; + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/UWPApplication.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/UWPApplication.cs index 37698d972d..4ec9598483 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/UWPApplication.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/UWPApplication.cs @@ -558,6 +558,7 @@ public class UWPApplication : IUWPApplication IsPackaged = true, Commands = app.GetCommands(), AppIdentifier = app.GetAppIdentifier(), + PackageFamilyName = app.Package.FamilyName, }; return item; } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/Win32Program.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/Win32Program.cs index 9b89afc425..fda8e2c697 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/Win32Program.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/Win32Program.cs @@ -1065,6 +1065,7 @@ public class Win32Program : IProgram DirPath = app.Location, Commands = app.GetCommands(), AppIdentifier = app.GetAppIdentifier(), + FullExecutablePath = app.FullPath, }; } } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/InstallPackageCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/InstallPackageCommand.cs index d2c1ea7283..7dbe740d95 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/InstallPackageCommand.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/InstallPackageCommand.cs @@ -62,7 +62,7 @@ public partial class InstallPackageCommand : InvokableCommand { PackageInstallCommandState.Install => Icons.DownloadIcon, PackageInstallCommandState.Update => Icons.UpdateIcon, - PackageInstallCommandState.Uninstall => Icons.CompletedIcon, + PackageInstallCommandState.Uninstall => Icons.DeleteIcon, _ => throw new NotImplementedException(), }; Name = InstallCommandState switch diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/InstallPackageListItem.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/InstallPackageListItem.cs index a8eda1bae9..1e1f337944 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/InstallPackageListItem.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/InstallPackageListItem.cs @@ -194,46 +194,95 @@ public partial class InstallPackageListItem : ListItem var isInstalled = _package.InstalledVersion is not null; var installedState = isInstalled ? - (_package.IsUpdateAvailable ? - PackageInstallCommandState.Update : PackageInstallCommandState.Uninstall) : + (_package.IsUpdateAvailable ? PackageInstallCommandState.Update : PackageInstallCommandState.Uninstall) : PackageInstallCommandState.Install; // might be an uninstall command InstallPackageCommand installCommand = new(_package, installedState); - if (isInstalled) + if (_package.InstalledVersion is not null) { - this.Icon = installCommand.Icon; - this.Command = new NoOpCommand(); +#if DEBUG + var installerType = _package.InstalledVersion.GetMetadata(PackageVersionMetadataField.InstallerType); + Subtitle = installerType + " | " + Subtitle; +#endif + List contextMenu = []; - CommandContextItem uninstallContextItem = new(installCommand) + Command = installCommand; + Icon = installedState switch { - IsCritical = true, - Icon = Icons.DeleteIcon, + PackageInstallCommandState.Install => Icons.DownloadIcon, + PackageInstallCommandState.Update => Icons.UpdateIcon, + PackageInstallCommandState.Uninstall => Icons.CompletedIcon, + _ => Icons.DownloadIcon, }; - if (WinGetStatics.AppSearchCallback is not null) + TryLocateAndAppendActionForApp(contextMenu); + + MoreCommands = contextMenu.ToArray(); + } + else + { + _installCommand = new InstallPackageCommand(_package, installedState); + _installCommand.InstallStateChanged += InstallStateChangedHandler; + Command = _installCommand; + Icon = _installCommand.Icon; + } + } + + private void TryLocateAndAppendActionForApp(List contextMenu) + { + try + { + // Let's try to connect it to an installed app if possible + // This is a bit of dark magic, since there's no direct link between + // WinGet packages and installed apps. + var lookupByPackageName = WinGetStatics.AppSearchByPackageFamilyNameCallback; + if (lookupByPackageName is not null) { - var callback = WinGetStatics.AppSearchCallback; - var installedApp = callback(_package.DefaultInstallVersion is null ? _package.Name : _package.DefaultInstallVersion.DisplayName); - if (installedApp is not null) + var names = _package.InstalledVersion.PackageFamilyNames; + for (var i = 0; i < names.Count; i++) { - this.Command = installedApp.Command; - contextMenu = [.. installedApp.MoreCommands]; + var installedAppByPfn = lookupByPackageName(names[i]); + if (installedAppByPfn is not null) + { + contextMenu.Add(new Separator()); + contextMenu.Add(new CommandContextItem(installedAppByPfn.Command)); + foreach (var item in installedAppByPfn.MoreCommands) + { + contextMenu.Add(item); + } + + return; + } } } - contextMenu.Add(uninstallContextItem); - this.MoreCommands = contextMenu.ToArray(); - return; + var lookupByProductCode = WinGetStatics.AppSearchByProductCodeCallback; + if (lookupByProductCode is not null) + { + var productCodes = _package.InstalledVersion.ProductCodes; + for (var i = 0; i < productCodes.Count; i++) + { + var installedAppByProductCode = lookupByProductCode(productCodes[i]); + if (installedAppByProductCode is not null) + { + contextMenu.Add(new Separator()); + contextMenu.Add(new CommandContextItem(installedAppByProductCode.Command)); + foreach (var item in installedAppByProductCode.MoreCommands) + { + contextMenu.Add(item); + } + + return; + } + } + } + } + catch (Exception ex) + { + Logger.LogError($"Failed to retrieve app context menu items for package '{_package?.Name ?? "Unknown"}'", ex); } - - // didn't find the app - _installCommand = new InstallPackageCommand(_package, installedState); - this.Command = _installCommand; - - Icon = _installCommand.Icon; - _installCommand.InstallStateChanged += InstallStateChangedHandler; } private void InstallStateChangedHandler(object? sender, InstallPackageCommand e) diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/WinGetExtensionCommandsProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/WinGetExtensionCommandsProvider.cs index 14c7ec0831..a2608ef8a8 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/WinGetExtensionCommandsProvider.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/WinGetExtensionCommandsProvider.cs @@ -41,5 +41,9 @@ public partial class WinGetExtensionCommandsProvider : CommandProvider public override void InitializeWithHost(IExtensionHost host) => WinGetExtensionHost.Instance.Initialize(host); - public void SetAllLookup(Func callback) => WinGetStatics.AppSearchCallback = callback; + public void SetAllLookup(Func lookupByPackageName, Func lookupByProductCode) + { + WinGetStatics.AppSearchByPackageFamilyNameCallback = lookupByPackageName; + WinGetStatics.AppSearchByProductCodeCallback = lookupByProductCode; + } } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/WinGetStatics.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/WinGetStatics.cs index da591c566c..001ba5539d 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/WinGetStatics.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/WinGetStatics.cs @@ -34,7 +34,9 @@ internal static class WinGetStatics private static readonly StatusMessage _errorMessage = new() { State = MessageState.Error }; - public static Func? AppSearchCallback { get; set; } + public static Func? AppSearchByPackageFamilyNameCallback { get; set; } + + public static Func? AppSearchByProductCodeCallback { get; set; } private static readonly CompositeFormat CreateCatalogErrorMessage = System.Text.CompositeFormat.Parse(Properties.Resources.winget_create_catalog_error);