Add the Command Palette module (#37908)
Windows Command Palette ("CmdPal") is the next iteration of PowerToys Run. With extensibility at its core, the Command Palette is your one-stop launcher to start _anything_.
By default, CmdPal is bound to <kbd>Win+Alt+Space</kbd>.


----
This brings the current preview version of CmdPal into the upstream PowerToys repo. There are still lots of bugs to work out, but it's reached the state we're ready to start sharing it with the world. From here, we can further collaborate with the community on the features that are important, and ensuring that we've got a most robust API to enable developers to build whatever extensions they want.
Most of the built-in PT Run modules have already been ported to CmdPal's extension API. Those include:
* Installed apps
* Shell commands
* File search (powered by the indexer)
* Windows Registry search
* Web search
* Windows Terminal Profiles
* Windows Services
* Windows settings
There are a couple new extensions built-in
* You can now search for packages on `winget` and install them right from the palette. This also powers searching for extensions for the palette
* The calculator has an entirely new implementation. This is currently less feature complete than the original PT Run one - we're looking forward to updating it to be more complete for future ingestion in Windows
* "Bookmarks" allow you to save shortcuts to files, folders, and webpages as top-level commands in the palette.
We've got a bunch of other samples too, in this repo and elsewhere
### PowerToys specific notes
CmdPal will eventually graduate out of PowerToys to live as its own application, which is why it's implemented just a little differently than most other modules. Enabling CmdPal will install its `msix` package.
The CI was minorly changed to support CmdPal version numbers independent of PowerToys itself. It doesn't make sense for us to start CmdPal at v0.90, and in the future, we want to be able to rev CmdPal independently of PT itself.
Closes #3200, closes #3600, closes #7770, closes #34273, closes #36471, closes #20976, closes #14495
-----
TODOs et al
**Blocking:**
- [ ] Images and descriptions in Settings and OOBE need to be properly defined, as mentioned before
- [ ] Niels is on it
- [x] Doesn't start properly from PowerToys unless the fix PR is merged.
- https://github.com/zadjii-msft/PowerToys/pull/556 merged
- [x] I seem to lose focus a lot when I press on some limits, like between the search bar and the results.
- This is https://github.com/zadjii-msft/PowerToys/issues/427
- [x] Turned off an extension like Calculator and it was still working.
- Need to get rid of that toggle, it doesn't do anything currently
- [x] `ListViewModel.<FetchItems>` crash
- Pretty confident that was fixed in https://github.com/zadjii-msft/PowerToys/pull/553
**Not blocking / improvements:**
- Show the shortcut through settings, as mentioned before, or create a button that would open CmdPalette settings.
- When PowerToys starts, CmdPalette is always shown if enabled. That's weird when just starting PowerToys/ logging in to the computer with PowerToys auto-start activated. I think this should at least be a setting.
- Needing to double press a result for it to do the default action seems quirky. If one is already selected, I think just pressing should be enough for it to do the action.
- This is currently a setting, though we're thinking of changing the setting even more: https://github.com/zadjii-msft/PowerToys/issues/392
- There's no URI extension. Was surprised when typing a URL that it only proposed a web search.
- [x] There's no System commands extension. Was expecting to be able to quickly restart the computer by typing restart but it wasn't there.
- This is in PR https://github.com/zadjii-msft/PowerToys/pull/452
---------
Co-authored-by: joadoumie <98557455+joadoumie@users.noreply.github.com>
Co-authored-by: Jordi Adoumie <jordiadoumie@microsoft.com>
Co-authored-by: Mike Griese <zadjii@gmail.com>
Co-authored-by: Niels Laute <niels.laute@live.nl>
Co-authored-by: Michael Hawker <24302614+michael-hawker@users.noreply.github.com>
Co-authored-by: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com>
Co-authored-by: Seraphima <zykovas91@gmail.com>
Co-authored-by: Jaime Bernardo <jaime@janeasystems.com>
Co-authored-by: Kristen Schau <47155823+krschau@users.noreply.github.com>
Co-authored-by: Eric Johnson <ericjohnson327@gmail.com>
Co-authored-by: Ethan Fang <ethanfang@microsoft.com>
Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
Co-authored-by: Clint Rutkas <clint@rutkas.com>
2025-03-19 03:39:57 -05:00
|
|
|
// 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 Microsoft.CmdPal.Common.Services;
|
|
|
|
using Microsoft.CommandPalette.Extensions;
|
|
|
|
using Windows.ApplicationModel;
|
|
|
|
using Windows.ApplicationModel.AppExtensions;
|
|
|
|
using Windows.Foundation;
|
|
|
|
using Windows.Foundation.Collections;
|
|
|
|
|
|
|
|
namespace Microsoft.CmdPal.UI.ViewModels.Models;
|
|
|
|
|
|
|
|
public class ExtensionService : IExtensionService, IDisposable
|
|
|
|
{
|
|
|
|
public event TypedEventHandler<IExtensionService, IEnumerable<IExtensionWrapper>>? OnExtensionAdded;
|
|
|
|
|
|
|
|
public event TypedEventHandler<IExtensionService, IEnumerable<IExtensionWrapper>>? OnExtensionRemoved;
|
|
|
|
|
|
|
|
private static readonly PackageCatalog _catalog = PackageCatalog.OpenForCurrentUser();
|
|
|
|
private static readonly Lock _lock = new();
|
|
|
|
private readonly SemaphoreSlim _getInstalledExtensionsLock = new(1, 1);
|
|
|
|
private readonly SemaphoreSlim _getInstalledWidgetsLock = new(1, 1);
|
|
|
|
|
|
|
|
// private readonly ILocalSettingsService _localSettingsService;
|
|
|
|
private bool _disposedValue;
|
|
|
|
|
|
|
|
private const string CreateInstanceProperty = "CreateInstance";
|
|
|
|
private const string ClassIdProperty = "@ClassId";
|
|
|
|
|
|
|
|
private static readonly List<IExtensionWrapper> _installedExtensions = [];
|
|
|
|
private static readonly List<IExtensionWrapper> _enabledExtensions = [];
|
|
|
|
|
|
|
|
public ExtensionService()
|
|
|
|
{
|
|
|
|
_catalog.PackageInstalling += Catalog_PackageInstalling;
|
|
|
|
_catalog.PackageUninstalling += Catalog_PackageUninstalling;
|
|
|
|
_catalog.PackageUpdating += Catalog_PackageUpdating;
|
|
|
|
|
|
|
|
//// These two were an investigation into getting updates when a package
|
|
|
|
//// gets redeployed from VS. Neither get raised (nor do the above)
|
|
|
|
//// _catalog.PackageStatusChanged += Catalog_PackageStatusChanged;
|
|
|
|
//// _catalog.PackageStaging += Catalog_PackageStaging;
|
|
|
|
// _localSettingsService = settingsService;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void Catalog_PackageInstalling(PackageCatalog sender, PackageInstallingEventArgs args)
|
|
|
|
{
|
|
|
|
if (args.IsComplete)
|
|
|
|
{
|
|
|
|
lock (_lock)
|
|
|
|
{
|
|
|
|
InstallPackageUnderLock(args.Package);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void Catalog_PackageUninstalling(PackageCatalog sender, PackageUninstallingEventArgs args)
|
|
|
|
{
|
|
|
|
if (args.IsComplete)
|
|
|
|
{
|
|
|
|
lock (_lock)
|
|
|
|
{
|
|
|
|
UninstallPackageUnderLock(args.Package);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void Catalog_PackageUpdating(PackageCatalog sender, PackageUpdatingEventArgs args)
|
|
|
|
{
|
|
|
|
if (args.IsComplete)
|
|
|
|
{
|
|
|
|
lock (_lock)
|
|
|
|
{
|
|
|
|
// Get any extension providers that we previously had from this app
|
|
|
|
UninstallPackageUnderLock(args.TargetPackage);
|
|
|
|
|
|
|
|
// then add the new ones.
|
|
|
|
InstallPackageUnderLock(args.TargetPackage);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void InstallPackageUnderLock(Package package)
|
|
|
|
{
|
|
|
|
var isCmdPalExtensionResult = Task.Run(() =>
|
|
|
|
{
|
|
|
|
return IsValidCmdPalExtension(package);
|
|
|
|
}).Result;
|
|
|
|
var isExtension = isCmdPalExtensionResult.IsExtension;
|
|
|
|
var extension = isCmdPalExtensionResult.Extension;
|
|
|
|
if (isExtension && extension != null)
|
|
|
|
{
|
|
|
|
CommandPaletteHost.Instance.DebugLog($"Installed new extension app {extension.DisplayName}");
|
|
|
|
|
|
|
|
Task.Run(async () =>
|
|
|
|
{
|
|
|
|
await _getInstalledExtensionsLock.WaitAsync();
|
|
|
|
try
|
|
|
|
{
|
|
|
|
var wrappers = await CreateWrappersForExtension(extension);
|
|
|
|
|
|
|
|
UpdateExtensionsListsFromWrappers(wrappers);
|
|
|
|
|
|
|
|
OnExtensionAdded?.Invoke(this, wrappers);
|
|
|
|
}
|
|
|
|
finally
|
|
|
|
{
|
|
|
|
_getInstalledExtensionsLock.Release();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void UninstallPackageUnderLock(Package package)
|
|
|
|
{
|
|
|
|
List<IExtensionWrapper> removedExtensions = [];
|
|
|
|
foreach (var extension in _installedExtensions)
|
|
|
|
{
|
|
|
|
if (extension.PackageFullName == package.Id.FullName)
|
|
|
|
{
|
|
|
|
CommandPaletteHost.Instance.DebugLog($"Uninstalled extension app {extension.PackageDisplayName}");
|
|
|
|
|
|
|
|
removedExtensions.Add(extension);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Task.Run(async () =>
|
|
|
|
{
|
|
|
|
await _getInstalledExtensionsLock.WaitAsync();
|
|
|
|
try
|
|
|
|
{
|
|
|
|
_installedExtensions.RemoveAll(i => removedExtensions.Contains(i));
|
2025-03-24 10:00:34 +00:00
|
|
|
_enabledExtensions.RemoveAll(i => removedExtensions.Contains(i));
|
Add the Command Palette module (#37908)
Windows Command Palette ("CmdPal") is the next iteration of PowerToys Run. With extensibility at its core, the Command Palette is your one-stop launcher to start _anything_.
By default, CmdPal is bound to <kbd>Win+Alt+Space</kbd>.


----
This brings the current preview version of CmdPal into the upstream PowerToys repo. There are still lots of bugs to work out, but it's reached the state we're ready to start sharing it with the world. From here, we can further collaborate with the community on the features that are important, and ensuring that we've got a most robust API to enable developers to build whatever extensions they want.
Most of the built-in PT Run modules have already been ported to CmdPal's extension API. Those include:
* Installed apps
* Shell commands
* File search (powered by the indexer)
* Windows Registry search
* Web search
* Windows Terminal Profiles
* Windows Services
* Windows settings
There are a couple new extensions built-in
* You can now search for packages on `winget` and install them right from the palette. This also powers searching for extensions for the palette
* The calculator has an entirely new implementation. This is currently less feature complete than the original PT Run one - we're looking forward to updating it to be more complete for future ingestion in Windows
* "Bookmarks" allow you to save shortcuts to files, folders, and webpages as top-level commands in the palette.
We've got a bunch of other samples too, in this repo and elsewhere
### PowerToys specific notes
CmdPal will eventually graduate out of PowerToys to live as its own application, which is why it's implemented just a little differently than most other modules. Enabling CmdPal will install its `msix` package.
The CI was minorly changed to support CmdPal version numbers independent of PowerToys itself. It doesn't make sense for us to start CmdPal at v0.90, and in the future, we want to be able to rev CmdPal independently of PT itself.
Closes #3200, closes #3600, closes #7770, closes #34273, closes #36471, closes #20976, closes #14495
-----
TODOs et al
**Blocking:**
- [ ] Images and descriptions in Settings and OOBE need to be properly defined, as mentioned before
- [ ] Niels is on it
- [x] Doesn't start properly from PowerToys unless the fix PR is merged.
- https://github.com/zadjii-msft/PowerToys/pull/556 merged
- [x] I seem to lose focus a lot when I press on some limits, like between the search bar and the results.
- This is https://github.com/zadjii-msft/PowerToys/issues/427
- [x] Turned off an extension like Calculator and it was still working.
- Need to get rid of that toggle, it doesn't do anything currently
- [x] `ListViewModel.<FetchItems>` crash
- Pretty confident that was fixed in https://github.com/zadjii-msft/PowerToys/pull/553
**Not blocking / improvements:**
- Show the shortcut through settings, as mentioned before, or create a button that would open CmdPalette settings.
- When PowerToys starts, CmdPalette is always shown if enabled. That's weird when just starting PowerToys/ logging in to the computer with PowerToys auto-start activated. I think this should at least be a setting.
- Needing to double press a result for it to do the default action seems quirky. If one is already selected, I think just pressing should be enough for it to do the action.
- This is currently a setting, though we're thinking of changing the setting even more: https://github.com/zadjii-msft/PowerToys/issues/392
- There's no URI extension. Was surprised when typing a URL that it only proposed a web search.
- [x] There's no System commands extension. Was expecting to be able to quickly restart the computer by typing restart but it wasn't there.
- This is in PR https://github.com/zadjii-msft/PowerToys/pull/452
---------
Co-authored-by: joadoumie <98557455+joadoumie@users.noreply.github.com>
Co-authored-by: Jordi Adoumie <jordiadoumie@microsoft.com>
Co-authored-by: Mike Griese <zadjii@gmail.com>
Co-authored-by: Niels Laute <niels.laute@live.nl>
Co-authored-by: Michael Hawker <24302614+michael-hawker@users.noreply.github.com>
Co-authored-by: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com>
Co-authored-by: Seraphima <zykovas91@gmail.com>
Co-authored-by: Jaime Bernardo <jaime@janeasystems.com>
Co-authored-by: Kristen Schau <47155823+krschau@users.noreply.github.com>
Co-authored-by: Eric Johnson <ericjohnson327@gmail.com>
Co-authored-by: Ethan Fang <ethanfang@microsoft.com>
Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
Co-authored-by: Clint Rutkas <clint@rutkas.com>
2025-03-19 03:39:57 -05:00
|
|
|
|
|
|
|
OnExtensionRemoved?.Invoke(this, removedExtensions);
|
|
|
|
}
|
|
|
|
finally
|
|
|
|
{
|
|
|
|
_getInstalledExtensionsLock.Release();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private static async Task<IsExtensionResult> IsValidCmdPalExtension(Package package)
|
|
|
|
{
|
|
|
|
var extensions = await AppExtensionCatalog.Open("com.microsoft.commandpalette").FindAllAsync();
|
|
|
|
foreach (var extension in extensions)
|
|
|
|
{
|
|
|
|
if (package.Id?.FullName == extension.Package?.Id?.FullName)
|
|
|
|
{
|
|
|
|
var (cmdPalProvider, classId) = await GetCmdPalExtensionPropertiesAsync(extension);
|
|
|
|
|
|
|
|
return new(cmdPalProvider != null && classId.Count != 0, extension);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return new(false, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static async Task<(IPropertySet? CmdPalProvider, List<string> ClassIds)> GetCmdPalExtensionPropertiesAsync(AppExtension extension)
|
|
|
|
{
|
|
|
|
var classIds = new List<string>();
|
|
|
|
var properties = await extension.GetExtensionPropertiesAsync();
|
|
|
|
|
|
|
|
if (properties is null)
|
|
|
|
{
|
|
|
|
return (null, classIds);
|
|
|
|
}
|
|
|
|
|
|
|
|
var cmdPalProvider = GetSubPropertySet(properties, "CmdPalProvider");
|
|
|
|
if (cmdPalProvider is null)
|
|
|
|
{
|
|
|
|
return (null, classIds);
|
|
|
|
}
|
|
|
|
|
|
|
|
var activation = GetSubPropertySet(cmdPalProvider, "Activation");
|
|
|
|
if (activation is null)
|
|
|
|
{
|
|
|
|
return (cmdPalProvider, classIds);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle case where extension creates multiple instances.
|
|
|
|
classIds.AddRange(GetCreateInstanceList(activation));
|
|
|
|
|
|
|
|
return (cmdPalProvider, classIds);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static async Task<IEnumerable<AppExtension>> GetInstalledAppExtensionsAsync() => await AppExtensionCatalog.Open("com.microsoft.commandpalette").FindAllAsync();
|
|
|
|
|
|
|
|
public async Task<IEnumerable<IExtensionWrapper>> GetInstalledExtensionsAsync(bool includeDisabledExtensions = false)
|
|
|
|
{
|
|
|
|
await _getInstalledExtensionsLock.WaitAsync();
|
|
|
|
try
|
|
|
|
{
|
|
|
|
if (_installedExtensions.Count == 0)
|
|
|
|
{
|
|
|
|
var extensions = await GetInstalledAppExtensionsAsync();
|
|
|
|
foreach (var extension in extensions)
|
|
|
|
{
|
|
|
|
var wrappers = await CreateWrappersForExtension(extension);
|
|
|
|
UpdateExtensionsListsFromWrappers(wrappers);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return includeDisabledExtensions ? _installedExtensions : _enabledExtensions;
|
|
|
|
}
|
|
|
|
finally
|
|
|
|
{
|
|
|
|
_getInstalledExtensionsLock.Release();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void UpdateExtensionsListsFromWrappers(List<ExtensionWrapper> wrappers)
|
|
|
|
{
|
|
|
|
foreach (var extensionWrapper in wrappers)
|
|
|
|
{
|
|
|
|
// var localSettingsService = Application.Current.GetService<ILocalSettingsService>();
|
|
|
|
var extensionUniqueId = extensionWrapper.ExtensionUniqueId;
|
|
|
|
var isExtensionDisabled = false; // await localSettingsService.ReadSettingAsync<bool>(extensionUniqueId + "-ExtensionDisabled");
|
|
|
|
|
|
|
|
_installedExtensions.Add(extensionWrapper);
|
|
|
|
if (!isExtensionDisabled)
|
|
|
|
{
|
|
|
|
_enabledExtensions.Add(extensionWrapper);
|
|
|
|
}
|
|
|
|
|
|
|
|
// TelemetryFactory.Get<ITelemetry>().Log(
|
|
|
|
// "Extension_ReportInstalled",
|
|
|
|
// LogLevel.Critical,
|
|
|
|
// new ReportInstalledExtensionEvent(extensionUniqueId, isEnabled: !isExtensionDisabled));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static async Task<List<ExtensionWrapper>> CreateWrappersForExtension(AppExtension extension)
|
|
|
|
{
|
|
|
|
var (cmdPalProvider, classIds) = await GetCmdPalExtensionPropertiesAsync(extension);
|
|
|
|
|
|
|
|
if (cmdPalProvider == null || classIds.Count == 0)
|
|
|
|
{
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
List<ExtensionWrapper> wrappers = [];
|
|
|
|
foreach (var classId in classIds)
|
|
|
|
{
|
|
|
|
var extensionWrapper = CreateExtensionWrapper(extension, cmdPalProvider, classId);
|
|
|
|
wrappers.Add(extensionWrapper);
|
|
|
|
}
|
|
|
|
|
|
|
|
return wrappers;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static ExtensionWrapper CreateExtensionWrapper(AppExtension extension, IPropertySet cmdPalProvider, string classId)
|
|
|
|
{
|
|
|
|
var extensionWrapper = new ExtensionWrapper(extension, classId);
|
|
|
|
|
|
|
|
var supportedInterfaces = GetSubPropertySet(cmdPalProvider, "SupportedInterfaces");
|
|
|
|
if (supportedInterfaces is not null)
|
|
|
|
{
|
|
|
|
foreach (var supportedInterface in supportedInterfaces)
|
|
|
|
{
|
|
|
|
ProviderType pt;
|
|
|
|
if (Enum.TryParse(supportedInterface.Key, out pt))
|
|
|
|
{
|
|
|
|
extensionWrapper.AddProviderType(pt);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// log warning that extension declared unsupported extension interface
|
|
|
|
CommandPaletteHost.Instance.DebugLog($"Extension {extension.DisplayName} declared an unsupported interface: {supportedInterface.Key}");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return extensionWrapper;
|
|
|
|
}
|
|
|
|
|
|
|
|
public IExtensionWrapper? GetInstalledExtension(string extensionUniqueId)
|
|
|
|
{
|
|
|
|
var extension = _installedExtensions.Where(extension => extension.ExtensionUniqueId.Equals(extensionUniqueId, StringComparison.Ordinal));
|
|
|
|
return extension.FirstOrDefault();
|
|
|
|
}
|
|
|
|
|
|
|
|
public async Task SignalStopExtensionsAsync()
|
|
|
|
{
|
|
|
|
var installedExtensions = await GetInstalledExtensionsAsync();
|
|
|
|
foreach (var installedExtension in installedExtensions)
|
|
|
|
{
|
|
|
|
if (installedExtension.IsRunning())
|
|
|
|
{
|
|
|
|
installedExtension.SignalDispose();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async Task<IEnumerable<IExtensionWrapper>> GetInstalledExtensionsAsync(ProviderType providerType, bool includeDisabledExtensions = false)
|
|
|
|
{
|
|
|
|
var installedExtensions = await GetInstalledExtensionsAsync(includeDisabledExtensions);
|
|
|
|
|
|
|
|
List<IExtensionWrapper> filteredExtensions = [];
|
|
|
|
foreach (var installedExtension in installedExtensions)
|
|
|
|
{
|
|
|
|
if (installedExtension.HasProviderType(providerType))
|
|
|
|
{
|
|
|
|
filteredExtensions.Add(installedExtension);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return filteredExtensions;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
{
|
|
|
|
Dispose(disposing: true);
|
|
|
|
GC.SuppressFinalize(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
|
|
{
|
|
|
|
if (!_disposedValue)
|
|
|
|
{
|
|
|
|
if (disposing)
|
|
|
|
{
|
|
|
|
_getInstalledExtensionsLock.Dispose();
|
|
|
|
_getInstalledWidgetsLock.Dispose();
|
|
|
|
}
|
|
|
|
|
|
|
|
_disposedValue = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static IPropertySet? GetSubPropertySet(IPropertySet propSet, string name) => propSet.TryGetValue(name, out var value) ? value as IPropertySet : null;
|
|
|
|
|
|
|
|
private static object[]? GetSubPropertySetArray(IPropertySet propSet, string name) => propSet.TryGetValue(name, out var value) ? value as object[] : null;
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// There are cases where the extension creates multiple COM instances.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="activationPropSet">Activation property set object</param>
|
|
|
|
/// <returns>List of ClassId strings associated with the activation property</returns>
|
|
|
|
private static List<string> GetCreateInstanceList(IPropertySet activationPropSet)
|
|
|
|
{
|
|
|
|
var propSetList = new List<string>();
|
|
|
|
var singlePropertySet = GetSubPropertySet(activationPropSet, CreateInstanceProperty);
|
|
|
|
if (singlePropertySet != null)
|
|
|
|
{
|
|
|
|
var classId = GetProperty(singlePropertySet, ClassIdProperty);
|
|
|
|
|
|
|
|
// If the instance has a classId as a single string, then it's only supporting a single instance.
|
|
|
|
if (classId != null)
|
|
|
|
{
|
|
|
|
propSetList.Add(classId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
var propertySetArray = GetSubPropertySetArray(activationPropSet, CreateInstanceProperty);
|
|
|
|
if (propertySetArray != null)
|
|
|
|
{
|
|
|
|
foreach (var prop in propertySetArray)
|
|
|
|
{
|
|
|
|
if (prop is not IPropertySet propertySet)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
var classId = GetProperty(propertySet, ClassIdProperty);
|
|
|
|
if (classId != null)
|
|
|
|
{
|
|
|
|
propSetList.Add(classId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return propSetList;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static string? GetProperty(IPropertySet propSet, string name) => propSet[name] as string;
|
|
|
|
|
|
|
|
public void EnableExtension(string extensionUniqueId)
|
|
|
|
{
|
|
|
|
var extension = _installedExtensions.Where(extension => extension.ExtensionUniqueId.Equals(extensionUniqueId, StringComparison.Ordinal));
|
|
|
|
_enabledExtensions.Add(extension.First());
|
|
|
|
}
|
|
|
|
|
|
|
|
public void DisableExtension(string extensionUniqueId)
|
|
|
|
{
|
|
|
|
var extension = _enabledExtensions.Where(extension => extension.ExtensionUniqueId.Equals(extensionUniqueId, StringComparison.Ordinal));
|
|
|
|
_enabledExtensions.Remove(extension.First());
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
///// <inheritdoc cref="IExtensionService.DisableExtensionIfWindowsFeatureNotAvailable(IExtensionWrapper)"/>
|
|
|
|
//public async Task<bool> DisableExtensionIfWindowsFeatureNotAvailable(IExtensionWrapper extension)
|
|
|
|
//{
|
|
|
|
// // Only attempt to disable feature if its available.
|
|
|
|
// if (IsWindowsOptionalFeatureAvailableForExtension(extension.ExtensionClassId))
|
|
|
|
// {
|
|
|
|
// return false;
|
|
|
|
// }
|
|
|
|
// _log.Warning($"Disabling extension: '{extension.ExtensionDisplayName}' because its feature is absent or unknown");
|
|
|
|
// // Remove extension from list of enabled extensions to prevent Dev Home from re-querying for this extension
|
|
|
|
// // for the rest of its process lifetime.
|
|
|
|
// DisableExtension(extension.ExtensionUniqueId);
|
|
|
|
// // Update the local settings so the next time the user launches Dev Home the extension will be disabled.
|
|
|
|
// await _localSettingsService.SaveSettingAsync(extension.ExtensionUniqueId + "-ExtensionDisabled", true);
|
|
|
|
// return true;
|
|
|
|
//} */
|
|
|
|
}
|
|
|
|
|
|
|
|
internal record struct IsExtensionResult(bool IsExtension, AppExtension? Extension)
|
|
|
|
{
|
|
|
|
}
|