Files
PowerToys/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/ExtensionWrapper.cs
Yu Leng 5d9a1ad404 Don't throw exception in the extension loading flow (#40094)
<!-- 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
Remove throw exception and add some logs here.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] **Closes:** #xxx
- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [x] **Localization:** All end-user-facing strings can be localized
- [x] **Dev docs:** Added/updated
- [x] **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)
- [x] **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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

Co-authored-by: Yu Leng <yuleng@microsoft.com>
2025-06-18 13:10:39 +08:00

202 lines
6.4 KiB
C#

// 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 ManagedCommon;
using Microsoft.CmdPal.Common.Services;
using Microsoft.CommandPalette.Extensions;
using Windows.ApplicationModel;
using Windows.ApplicationModel.AppExtensions;
using Windows.Win32;
using Windows.Win32.System.Com;
using WinRT;
// [assembly: System.Runtime.CompilerServices.DisableRuntimeMarshalling]
namespace Microsoft.CmdPal.UI.ViewModels.Models;
public class ExtensionWrapper : IExtensionWrapper
{
private const int HResultRpcServerNotRunning = -2147023174;
private readonly string _appUserModelId;
private readonly string _extensionId;
private readonly Lock _lock = new();
private readonly List<ProviderType> _providerTypes = [];
private readonly Dictionary<Type, ProviderType> _providerTypeMap = new()
{
[typeof(ICommandProvider)] = ProviderType.Commands,
};
private IExtension? _extensionObject;
public ExtensionWrapper(AppExtension appExtension, string classId)
{
PackageDisplayName = appExtension.Package.DisplayName;
ExtensionDisplayName = appExtension.DisplayName;
PackageFullName = appExtension.Package.Id.FullName;
PackageFamilyName = appExtension.Package.Id.FamilyName;
ExtensionClassId = classId ?? throw new ArgumentNullException(nameof(classId));
Publisher = appExtension.Package.PublisherDisplayName;
InstalledDate = appExtension.Package.InstalledDate;
Version = appExtension.Package.Id.Version;
_appUserModelId = appExtension.AppInfo.AppUserModelId;
_extensionId = appExtension.Id;
}
public string PackageDisplayName { get; }
public string ExtensionDisplayName { get; }
public string PackageFullName { get; }
public string PackageFamilyName { get; }
public string ExtensionClassId { get; }
public string Publisher { get; }
public DateTimeOffset InstalledDate { get; }
public PackageVersion Version { get; }
/// <summary>
/// Gets the unique id for this Dev Home extension. The unique id is a concatenation of:
/// <list type="number">
/// <item>The AppUserModelId (AUMID) of the extension's application. The AUMID is the concatenation of the package
/// family name and the application id and uniquely identifies the application containing the extension within
/// the package.</item>
/// <item>The Extension Id. This is the unique identifier of the extension within the application.</item>
/// </list>
/// </summary>
public string ExtensionUniqueId => _appUserModelId + "!" + _extensionId;
public bool IsRunning()
{
if (_extensionObject is null)
{
return false;
}
try
{
_extensionObject.As<IInspectable>().GetRuntimeClassName();
}
catch (COMException e)
{
if (e.ErrorCode == HResultRpcServerNotRunning)
{
return false;
}
throw;
}
return true;
}
public async Task StartExtensionAsync()
{
await Task.Run(() =>
{
lock (_lock)
{
if (!IsRunning())
{
Logger.LogDebug($"Starting {ExtensionDisplayName} ({ExtensionClassId})");
unsafe
{
var extensionPtr = (void*)nint.Zero;
try
{
// -2147024809: E_INVALIDARG
// -2147467262: E_NOINTERFACE
// -2147024893: E_PATH_NOT_FOUND
var guid = typeof(IExtension).GUID;
var hr = PInvoke.CoCreateInstance(Guid.Parse(ExtensionClassId), null, CLSCTX.CLSCTX_LOCAL_SERVER, guid, out extensionPtr);
if (hr.Value == -2147024893)
{
Logger.LogDebug($"Failed to find {ExtensionDisplayName}: {hr}. It may have been uninstalled or deleted.");
// We don't really need to throw this exception.
// We'll just return out nothing.
return;
}
// Marshal.ThrowExceptionForHR(hr);
_extensionObject = MarshalInterface<IExtension>.FromAbi((nint)extensionPtr);
}
catch (Exception e)
{
Logger.LogDebug($"Failed to start {ExtensionDisplayName}. ex: {e.Message}");
}
finally
{
if ((nint)extensionPtr != nint.Zero)
{
Marshal.Release((nint)extensionPtr);
}
}
}
}
}
});
}
public void SignalDispose()
{
lock (_lock)
{
if (IsRunning())
{
_extensionObject?.Dispose();
}
_extensionObject = null;
}
}
public IExtension? GetExtensionObject()
{
lock (_lock)
{
return IsRunning() ? _extensionObject : null;
}
}
public async Task<T?> GetProviderAsync<T>()
where T : class
{
await StartExtensionAsync();
return GetExtensionObject()?.GetProvider(_providerTypeMap[typeof(T)]) as T;
}
public async Task<IEnumerable<T>> GetListOfProvidersAsync<T>()
where T : class
{
await StartExtensionAsync();
var supportedProviders = GetExtensionObject()?.GetProvider(_providerTypeMap[typeof(T)]);
if (supportedProviders is IEnumerable<T> multipleProvidersSupported)
{
return multipleProvidersSupported;
}
else if (supportedProviders is T singleProviderSupported)
{
return [singleProviderSupported];
}
return Enumerable.Empty<T>();
}
public void AddProviderType(ProviderType providerType) => _providerTypes.Add(providerType);
public bool HasProviderType(ProviderType providerType) => _providerTypes.Contains(providerType);
}