[CommandNotFound]Upgrade to use PSGallery release and support arm64 (#32766)

* [CommandNotFound] Add support for upgrading the module

* upgrade module as a part of upgrade installation; actually set content in EnableModule.ps1

* Fix XAML style to pass CI

* Remove CmdNotFound project from sln as well

* Remove CmdNotFound psd1 file from installer

* More installer fixes

* UpgradeCommandNotFound runs after InstallFiles

* Fix NOTICE.md

* Fix custom action condition

* Pass install folder to the custom action

* Upgrade-Module --> Update-Module

* actually install the module

* spell

* verify updated scripts work; make necessary changes

---------

Co-authored-by: Jaime Bernardo <jaime@janeasystems.com>
This commit is contained in:
Carlos Zamora
2024-05-16 05:45:06 -07:00
committed by GitHub
parent 07ca6c8e62
commit e1832a0a4a
28 changed files with 188 additions and 540 deletions

View File

@@ -1,69 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\Version.props" />
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
<RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>
<ImplicitUsings>enable</ImplicitUsings>
<Authors>Microsoft Corporation</Authors>
<Product>PowerToys</Product>
<Nullable>enable</Nullable>
<Description>PowerToys CommandNotFound</Description>
<AssemblyName>PowerToys.CmdNotFound</AssemblyName>
<GenerateDependencyFile>false</GenerateDependencyFile>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<SelfContained>true</SelfContained>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
</PropertyGroup>
<!-- SelfContained=true requires RuntimeIdentifier to be set -->
<PropertyGroup Condition="'$(Platform)'=='x64'">
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
</PropertyGroup>
<PropertyGroup Condition="'$(Platform)'=='ARM64'">
<RuntimeIdentifier>win-arm64</RuntimeIdentifier>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Optimize>false</Optimize>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<DefineConstants>TRACE;RELEASE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.ObjectPool">
<ExcludeAssets>contentFiles</ExcludeAssets>
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; compile; build; native; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Management.Automation">
<ExcludeAssets>contentFiles</ExcludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<Content Include="WinGetCommandNotFound.psd1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,57 +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.Management.Automation;
using System.Management.Automation.Subsystem;
using System.Management.Automation.Subsystem.Feedback;
using System.Management.Automation.Subsystem.Prediction;
namespace WinGetCommandNotFound
{
public sealed class Init : IModuleAssemblyInitializer, IModuleAssemblyCleanup
{
internal const string Id = "e5351aa4-dfde-4d4d-bf0f-1a2f5a37d8d6";
public void OnImport()
{
if (!Platform.IsWindows || !IsWinGetInstalled())
{
return;
}
SubsystemManager.RegisterSubsystem(SubsystemKind.FeedbackProvider, WinGetCommandNotFoundFeedbackPredictor.Singleton);
SubsystemManager.RegisterSubsystem(SubsystemKind.CommandPredictor, WinGetCommandNotFoundFeedbackPredictor.Singleton);
}
public void OnRemove(PSModuleInfo psModuleInfo)
{
if (!IsWinGetInstalled())
{
return;
}
SubsystemManager.UnregisterSubsystem<IFeedbackProvider>(new Guid(Id));
SubsystemManager.UnregisterSubsystem<ICommandPredictor>(new Guid(Id));
}
private bool IsWinGetInstalled()
{
// Ensure WinGet is installed
using (var pwsh = PowerShell.Create(RunspaceMode.CurrentRunspace))
{
var results = pwsh.AddCommand("Get-Command")
.AddParameter("Name", "winget")
.AddParameter("CommandType", "Application")
.Invoke();
if (results.Count is 0)
{
return false;
}
}
return true;
}
}
}

View File

@@ -1,34 +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.Management.Automation;
using System.Management.Automation.Runspaces;
using Microsoft.Extensions.ObjectPool;
namespace WinGetCommandNotFound
{
public sealed class PooledPowerShellObjectPolicy : IPooledObjectPolicy<PowerShell>
{
private static readonly string[] WingetClientModuleName = new[] { "Microsoft.WinGet.Client" };
public PowerShell Create()
{
var iss = InitialSessionState.CreateDefault2();
iss.ImportPSModule(WingetClientModuleName);
return PowerShell.Create(iss);
}
public bool Return(PowerShell obj)
{
if (obj != null)
{
obj.Commands.Clear();
obj.Streams.ClearStreams();
return true;
}
return false;
}
}
}

View File

@@ -1,16 +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.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace WinGetCommandNotFound.Telemetry
{
[EventData]
public class CmdNotFoundFeedbackProvidedEvent : EventBase, IEvent
{
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}
}

View File

@@ -1,18 +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.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace WinGetCommandNotFound.Telemetry
{
[EventData]
public class CmdNotFoundFeedbackProvidedFailureEvent : EventBase, IEvent
{
public string Message { get; set; } = string.Empty;
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}
}

View File

@@ -1,16 +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.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace WinGetCommandNotFound.Telemetry
{
[EventData]
public class CmdNotFoundInstanceCreatedEvent : EventBase, IEvent
{
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}
}

View File

@@ -1,16 +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.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace WinGetCommandNotFound.Telemetry
{
[EventData]
public class CmdNotFoundSuggestionProvidedEvent : EventBase, IEvent
{
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}
}

View File

@@ -1,11 +0,0 @@
@{
ModuleVersion = '0.1.0'
GUID = '28c9afa2-92e5-413e-8e53-44b2d7a83ac6'
Author = 'Carlos Zamora'
CompanyName = "Microsoft Corporation"
Copyright = "Copyright (c) Microsoft Corporation."
Description = 'Enable suggestions on how to install missing commands via winget'
PowerShellVersion = '7.4'
NestedModules = @('PowerToys.CmdNotFound.dll')
RequiredModules = @(@{ModuleName = 'Microsoft.WinGet.Client'; ModuleVersion = "0.2.1"; })
}

View File

@@ -1,221 +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.Collections;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Management.Automation;
using System.Management.Automation.Subsystem.Feedback;
using System.Management.Automation.Subsystem.Prediction;
using ManagedCommon;
using Microsoft.Extensions.ObjectPool;
using Microsoft.PowerToys.Telemetry;
namespace WinGetCommandNotFound
{
public sealed class WinGetCommandNotFoundFeedbackPredictor : IFeedbackProvider, ICommandPredictor
{
private readonly Guid _guid;
private readonly ObjectPool<PowerShell> _pool;
private const int _maxSuggestions = 20;
private List<string>? _candidates;
private bool _warmedUp;
public static WinGetCommandNotFoundFeedbackPredictor Singleton { get; } = new WinGetCommandNotFoundFeedbackPredictor(Init.Id);
private WinGetCommandNotFoundFeedbackPredictor(string guid)
{
Logger.InitializeLogger("\\CmdNotFound\\Logs");
_guid = new Guid(guid);
var provider = new DefaultObjectPoolProvider();
_pool = provider.Create(new PooledPowerShellObjectPolicy());
_pool.Return(_pool.Get());
Task.Run(() => WarmUp());
// Telemetry that a shell is creating an instance of CommandNotFound.
PowerToysTelemetry.Log.WriteEvent(new Telemetry.CmdNotFoundInstanceCreatedEvent());
}
public Guid Id => _guid;
public string Name => "Windows Package Manager - WinGet";
public string Description => "Finds missing commands that can be installed via WinGet.";
public Dictionary<string, string>? FunctionsToDefine => null;
private void WarmUp()
{
var ps = _pool.Get();
try
{
ps.AddCommand("Find-WinGetPackage")
.AddParameter("Count", 1)
.Invoke();
}
finally
{
_pool.Return(ps);
_warmedUp = true;
}
}
/// <summary>
/// Gets feedback based on the given commandline and error record.
/// </summary>
public FeedbackItem? GetFeedback(FeedbackContext context, CancellationToken token)
{
var target = (string)context.LastError!.TargetObject;
if (target is not null)
{
try
{
bool tooManySuggestions = false;
string packageMatchFilterField = "command";
var pkgList = FindPackages(target, ref tooManySuggestions, ref packageMatchFilterField);
if (pkgList.Count == 0)
{
return null;
}
// Build list of suggestions
_candidates = new List<string>();
foreach (var pkg in pkgList)
{
_candidates.Add(string.Format(CultureInfo.InvariantCulture, "winget install --id {0}", pkg.Members["Id"].Value.ToString()));
}
// Build footer message
var footerMessage = tooManySuggestions ?
string.Format(CultureInfo.InvariantCulture, "Additional results can be found using \"winget search --{0} {1}\"", packageMatchFilterField, target) :
null;
PowerToysTelemetry.Log.WriteEvent(new Telemetry.CmdNotFoundFeedbackProvidedEvent());
return new FeedbackItem(
"Try installing this package using winget:",
_candidates,
footerMessage,
FeedbackDisplayLayout.Portrait);
}
catch (Exception ex)
{
Logger.LogError("GetFeedback failed to execute", ex);
PowerToysTelemetry.Log.WriteEvent(new Telemetry.CmdNotFoundFeedbackProvidedFailureEvent { Message = ex.Message });
return new FeedbackItem($"Failed to execute PowerToys Command Not Found.{Environment.NewLine}This is a known issue if PowerShell 7 is installed from the Store or MSIX. If that isn't your case, please report an issue.", new List<string>(), FeedbackDisplayLayout.Portrait);
}
}
return null;
}
private Collection<PSObject> FindPackages(string query, ref bool tooManySuggestions, ref string packageMatchFilterField)
{
if (!_warmedUp)
{
return new Collection<PSObject>();
}
var ps = _pool.Get();
try
{
var common = new Hashtable()
{
["Source"] = "winget",
};
// 1) Search by command
var pkgList = ps.AddCommand("Find-WinGetPackage")
.AddParameter("Command", query)
.AddParameter("MatchOption", "StartsWithCaseInsensitive")
.AddParameters(common)
.Invoke();
if (pkgList.Count > 0)
{
tooManySuggestions = pkgList.Count > _maxSuggestions;
packageMatchFilterField = "command";
return pkgList;
}
// 2) No matches found,
// search by name
ps.Commands.Clear();
pkgList = ps.AddCommand("Find-WinGetPackage")
.AddParameter("Name", query)
.AddParameter("MatchOption", "ContainsCaseInsensitive")
.AddParameters(common)
.Invoke();
if (pkgList.Count > 0)
{
tooManySuggestions = pkgList.Count > _maxSuggestions;
packageMatchFilterField = "name";
return pkgList;
}
// 3) No matches found,
// search by moniker
ps.Commands.Clear();
pkgList = ps.AddCommand("Find-WinGetPackage")
.AddParameter("Moniker", query)
.AddParameter("MatchOption", "ContainsCaseInsensitive")
.AddParameters(common)
.Invoke();
tooManySuggestions = pkgList.Count > _maxSuggestions;
packageMatchFilterField = "moniker";
return pkgList;
}
finally
{
_pool.Return(ps);
}
}
public bool CanAcceptFeedback(PredictionClient client, PredictorFeedbackKind feedback)
{
return feedback switch
{
PredictorFeedbackKind.CommandLineAccepted => true,
_ => false,
};
}
public SuggestionPackage GetSuggestion(PredictionClient client, PredictionContext context, CancellationToken cancellationToken)
{
if (_candidates is not null)
{
string input = context.InputAst.Extent.Text;
List<PredictiveSuggestion>? result = null;
foreach (string c in _candidates)
{
if (c.StartsWith(input, StringComparison.OrdinalIgnoreCase))
{
result ??= new List<PredictiveSuggestion>(_candidates.Count);
result.Add(new PredictiveSuggestion(c));
}
}
if (result is not null)
{
PowerToysTelemetry.Log.WriteEvent(new Telemetry.CmdNotFoundSuggestionProvidedEvent());
return new SuggestionPackage(result);
}
}
return default;
}
public void OnCommandLineAccepted(PredictionClient client, IReadOnlyList<string> history)
{
// Reset the candidate state.
_candidates = null;
}
}
}