[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
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 188 additions and 540 deletions

View File

@ -197,6 +197,7 @@ CMock
CMONITORS
cmpgt
cne
CNF
coclass
codeofconduct
codereview

View File

@ -34,7 +34,6 @@
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageVersion Include="Microsoft.Extensions.ObjectPool" Version="8.0.0" />
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.2" />
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.2365.46" />
<!-- Package Microsoft.Win32.SystemEvents added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Drawing.Common but the 8.0.1 version wasn't published to nuget. -->
@ -77,7 +76,6 @@
<PackageVersion Include="System.IO.Abstractions" Version="17.2.3" />
<PackageVersion Include="System.IO.Abstractions.TestingHelpers" Version="17.2.3" />
<PackageVersion Include="System.Management" Version="8.0.0" />
<PackageVersion Include="System.Management.Automation" Version="7.4.0" />
<PackageVersion Include="System.Reactive" Version="6.0.0-preview.9" />
<PackageVersion Include="System.Runtime.Caching" Version="8.0.0" />
<!-- Package System.Security.Cryptography.ProtectedData added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Data.OleDb but the 8.0.1 version wasn't published to nuget. -->

View File

@ -1323,7 +1323,6 @@ EXHIBIT A -Mozilla Public License.
- Microsoft.Extensions.Hosting.WindowsServices 8.0.0
- Microsoft.Extensions.Logging 8.0.0
- Microsoft.Extensions.Logging.Abstractions 8.0.0
- Microsoft.Extensions.ObjectPool 8.0.0
- Microsoft.NET.Test.Sdk 17.8.0
- Microsoft.Toolkit.Uwp.Notifications 7.1.2
- Microsoft.Web.WebView2 1.0.2365.46
@ -1358,7 +1357,6 @@ EXHIBIT A -Mozilla Public License.
- System.IO.Abstractions 17.2.3
- System.IO.Abstractions.TestingHelpers 17.2.3
- System.Management 8.0.0
- System.Management.Automation 7.4.0
- System.Reactive 6.0.0-preview.9
- System.Runtime.Caching 8.0.0
- System.Security.Cryptography.ProtectedData 8.0.0

View File

@ -538,8 +538,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CropAndLock", "src\modules\
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CropAndLockModuleInterface", "src\modules\CropAndLock\CropAndLockModuleInterface\CropAndLockModuleInterface.vcxproj", "{3157FA75-86CF-4EE2-8F62-C43F776493C6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CmdNotFound", "src\modules\cmdNotFound\CmdNotFound\CmdNotFound.csproj", "{A37865FE-2881-449F-8ADB-B8CD373D6D79}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cmdNotFound", "cmdNotFound", "{4C0D0746-BE5B-49EE-BD5D-A7811628AE8B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests-FancyZonesEditor", "src\modules\fancyzones\UnitTests-FancyZonesEditor\UnitTests-FancyZonesEditor.csproj", "{FC8EB78F-F061-4BD9-A3F6-507BEA965E2B}"
@ -2357,18 +2355,6 @@ Global
{3157FA75-86CF-4EE2-8F62-C43F776493C6}.Release|x64.Build.0 = Release|x64
{3157FA75-86CF-4EE2-8F62-C43F776493C6}.Release|x86.ActiveCfg = Release|x64
{3157FA75-86CF-4EE2-8F62-C43F776493C6}.Release|x86.Build.0 = Release|x64
{A37865FE-2881-449F-8ADB-B8CD373D6D79}.Debug|ARM64.ActiveCfg = Debug|ARM64
{A37865FE-2881-449F-8ADB-B8CD373D6D79}.Debug|ARM64.Build.0 = Debug|ARM64
{A37865FE-2881-449F-8ADB-B8CD373D6D79}.Debug|x64.ActiveCfg = Debug|x64
{A37865FE-2881-449F-8ADB-B8CD373D6D79}.Debug|x64.Build.0 = Debug|x64
{A37865FE-2881-449F-8ADB-B8CD373D6D79}.Debug|x86.ActiveCfg = Debug|x64
{A37865FE-2881-449F-8ADB-B8CD373D6D79}.Debug|x86.Build.0 = Debug|x64
{A37865FE-2881-449F-8ADB-B8CD373D6D79}.Release|ARM64.ActiveCfg = Release|ARM64
{A37865FE-2881-449F-8ADB-B8CD373D6D79}.Release|ARM64.Build.0 = Release|ARM64
{A37865FE-2881-449F-8ADB-B8CD373D6D79}.Release|x64.ActiveCfg = Release|x64
{A37865FE-2881-449F-8ADB-B8CD373D6D79}.Release|x64.Build.0 = Release|x64
{A37865FE-2881-449F-8ADB-B8CD373D6D79}.Release|x86.ActiveCfg = Release|x64
{A37865FE-2881-449F-8ADB-B8CD373D6D79}.Release|x86.Build.0 = Release|x64
{FC8EB78F-F061-4BD9-A3F6-507BEA965E2B}.Debug|ARM64.ActiveCfg = Debug|ARM64
{FC8EB78F-F061-4BD9-A3F6-507BEA965E2B}.Debug|ARM64.Build.0 = Debug|ARM64
{FC8EB78F-F061-4BD9-A3F6-507BEA965E2B}.Debug|x64.ActiveCfg = Debug|x64
@ -2791,7 +2777,6 @@ Global
{3B227528-4BA6-4CAF-B44A-A10C78A64849} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{F5E1146E-B7B3-4E11-85FD-270A500BD78C} = {3B227528-4BA6-4CAF-B44A-A10C78A64849}
{3157FA75-86CF-4EE2-8F62-C43F776493C6} = {3B227528-4BA6-4CAF-B44A-A10C78A64849}
{A37865FE-2881-449F-8ADB-B8CD373D6D79} = {4C0D0746-BE5B-49EE-BD5D-A7811628AE8B}
{4C0D0746-BE5B-49EE-BD5D-A7811628AE8B} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{FC8EB78F-F061-4BD9-A3F6-507BEA965E2B} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}
{538ED0BB-B863-4B20-98CC-BCDF7FA0B68A} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}

View File

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<?include $(sys.CURRENTDIR)\Common.wxi?>
<Fragment>
<DirectoryRef Id="INSTALLFOLDER" FileSource="$(var.BinDir)">
<!-- !Warning! Make sure to change Component Guid if you update the file list -->
<Component Id="Module_CmdNotFound" Win64="yes" Guid="80F648F2-29F6-4685-AED4-04DC1B6EE176">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="Module_CmdNotFound" Value="" KeyPath="yes"/>
</RegistryKey>
<File Source="$(var.BinDir)WinGetCommandNotFound.psd1" />
<!-- The dll files will be picked up by BaseApplications generateAllFileComponents.ps1-->
</Component>
</DirectoryRef>
<ComponentGroup Id="CmdNotFoundComponentGroup">
<ComponentRef Id="Module_CmdNotFound" />
</ComponentGroup>
</Fragment>
</Wix>

View File

@ -18,7 +18,6 @@
<?define PastePlainProjectName="PastePlain"?>
<?define RegistryPreviewProjectName="RegistryPreview"?>
<?define PeekProjectName="Peek"?>
<?define CmdNotFoundProjectName="CmdNotFound"?>
<?define RepoDir="$(var.ProjectDir)..\..\" ?>
<?if $(var.Platform) = x64?>

View File

@ -104,7 +104,6 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil
<Compile Include="Awake.wxs" />
<Compile Include="BaseApplications.wxs" />
<Compile Include="CmdNotFound.wxs" />
<Compile Include="ColorPicker.wxs" />
<Compile Include="EnvironmentVariables.wxs" />
<Compile Include="FileExplorerPreview.wxs" />

View File

@ -72,7 +72,6 @@
<ComponentGroupRef Id="VideoConferenceComponentGroup" />
<ComponentGroupRef Id="MouseWithoutBordersComponentGroup" />
<ComponentGroupRef Id="EnvironmentVariablesComponentGroup" />
<ComponentGroupRef Id="CmdNotFoundComponentGroup" />
<ComponentGroupRef Id="ResourcesComponentGroup" />
<ComponentGroupRef Id="WindowsAppSDKComponentGroup" />
@ -135,6 +134,7 @@
<Custom Action="DetectPrevInstallPath" After="AppSearch" />
<Custom Action="SetLaunchPowerToysParam" Before="LaunchPowerToys" />
<Custom Action="SetUninstallCommandNotFoundParam" Before="UninstallCommandNotFound" />
<Custom Action="SetUpgradeCommandNotFoundParam" Before="UpgradeCommandNotFound" />
<Custom Action="SetApplyModulesRegistryChangeSetsParam" Before="ApplyModulesRegistryChangeSets" />
<Custom Action="SetUnApplyModulesRegistryChangeSetsParam" Before="UnApplyModulesRegistryChangeSets" />
<Custom Action="CheckGPO" After="InstallInitialize">
@ -164,6 +164,9 @@
<Custom Action="UninstallCommandNotFound" Before="RemoveFiles">
Installed AND (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")
</Custom>
<Custom Action="UpgradeCommandNotFound" After="InstallFiles">
WIX_UPGRADE_DETECTED
</Custom>
<Custom Action="UninstallServicesTask" After="InstallFinalize">
Installed AND (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")
</Custom>
@ -209,6 +212,10 @@
Property="UninstallCommandNotFound"
Value="[INSTALLFOLDER]" />
<CustomAction Id="SetUpgradeCommandNotFoundParam"
Property="UpgradeCommandNotFound"
Value="[INSTALLFOLDER]" />
<CustomAction Id="SetCreateWinAppSDKHardlinksParam"
Property="CreateWinAppSDKHardlinks"
Value="[INSTALLFOLDER]" />
@ -269,6 +276,14 @@
BinaryKey="PTCustomActions"
DllEntry="UninstallCommandNotFoundModuleCA"
/>
<CustomAction Id="UpgradeCommandNotFound"
Return="ignore"
Impersonate="yes"
Execute="deferred"
BinaryKey="PTCustomActions"
DllEntry="UpgradeCommandNotFoundModuleCA"
/>
<CustomAction Id="TelemetryLogInstallSuccess"
Return="ignore"

View File

@ -53,6 +53,7 @@
<RegistryValue Type="string" Name="CommandNotFound_Scripts" Value="" KeyPath="yes"/>
</RegistryKey>
<File Id="CommandNotFound_Scripts_EnableModule.ps1" Source="$(var.SettingsV2AssetsFilesPath)\Scripts\EnableModule.ps1" />
<File Id="CommandNotFound_Scripts_UpgradeModule.ps1" Source="$(var.SettingsV2AssetsFilesPath)\Scripts\UpgradeModule.ps1" />
<File Id="CommandNotFound_Scripts_DisableModule.ps1" Source="$(var.SettingsV2AssetsFilesPath)\Scripts\DisableModule.ps1" />
<File Id="CommandNotFound_Scripts_CheckCmdNotFoundRequirements.ps1" Source="$(var.SettingsV2AssetsFilesPath)\Scripts\CheckCmdNotFoundRequirements.ps1" />
<File Id="CommandNotFound_Scripts_InstallWinGetClientModule.ps1" Source="$(var.SettingsV2AssetsFilesPath)\Scripts\InstallWinGetClientModule.ps1" />

View File

@ -457,6 +457,30 @@ LExit:
return WcaFinalize(er);
}
UINT __stdcall UpgradeCommandNotFoundModuleCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
std::wstring installationFolder;
std::string command;
hr = WcaInitialize(hInstall, "UpgradeCommandNotFoundModule");
ExitOnFailure(hr, "Failed to initialize");
hr = getInstallFolder(hInstall, installationFolder);
ExitOnFailure(hr, "Failed to get installFolder.");
command = "pwsh.exe";
command += " ";
command += "-NoProfile -NonInteractive -NoLogo -WindowStyle Hidden -ExecutionPolicy Unrestricted -File \"" + winrt::to_string(installationFolder) + "\\WinUI3Apps\\Assets\\Settings\\Scripts\\UpgradeModule.ps1" + "\"";
system(command.c_str());
LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall UninstallServicesCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;

View File

@ -23,4 +23,5 @@ EXPORTS
UnRegisterContextMenuPackagesCA
UninstallEmbeddedMSIXCA
UninstallServicesCA
UninstallCommandNotFoundModuleCA
UninstallCommandNotFoundModuleCA
UpgradeCommandNotFoundModuleCA

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;
}
}
}

View File

@ -10,10 +10,25 @@ else
# This message will be compared against in Command Not Found Settings page code behind. Take care when changing it.
}
if (Get-Module -ListAvailable -Name Microsoft.WinGet.Client)
if ($mods = Get-Module -ListAvailable -Name Microsoft.WinGet.Client)
{
Write-Host "WinGet Client module detected."
# This message will be compared against in Command Not Found Settings page code behind. Take care when changing it.
$needsUpdate = $true;
foreach ($mod in $mods)
{
if ($mod.Version -ge "1.8.1133")
{
$needsUpdate = $false;
break;
}
}
if ($needsUpdate)
{
Write-Host "WinGet Client module needs to be updated."
# This message will be compared against in Command Not Found Settings page code behind. Take care when changing it.
}
}
else {
Write-Host "WinGet Client module not detected. Installation instructions can be found on https://www.powershellgallery.com/packages/Microsoft.WinGet.Client `r`n"
@ -30,6 +45,11 @@ if (!(Test-Path $PROFILE))
$profileContent = Get-Content -Path $PROFILE -Raw
if ((-not [string]::IsNullOrEmpty($profileContent)) -and ($profileContent.Contains("34de4b3d-13a8-4540-b76d-b9e8d3851756")))
{
Write-Host "Outdated version of Command Not Found module found in the profile file."
# This message will be compared against in Command Not Found Settings page code behind. Take care when changing it.
}
elseif ((-not [string]::IsNullOrEmpty($profileContent)) -and ($profileContent.Contains("f45873b3-b655-43a6-b217-97c00aa0db58")))
{
Write-Host "Command Not Found module is registered in the profile file."
# This message will be compared against in Command Not Found Settings page code behind. Take care when changing it.

View File

@ -5,14 +5,14 @@ $linesToDeleteFound = $False
$atLeastOneInstanceFound = $False
$profileContent | ForEach-Object {
if ($_.Contains("34de4b3d-13a8-4540-b76d-b9e8d3851756") -and !$linesToDeleteFound)
if (($_.Contains("34de4b3d-13a8-4540-b76d-b9e8d3851756") -or $_.Contains("f45873b3-b655-43a6-b217-97c00aa0db58")) -and !$linesToDeleteFound)
{
$linesToDeleteFound = $True
$atLeastOneInstanceFound = $True
return
}
if ($_.Contains("34de4b3d-13a8-4540-b76d-b9e8d3851756") -and $linesToDeleteFound)
if (($_.Contains("34de4b3d-13a8-4540-b76d-b9e8d3851756") -or $_.Contains("f45873b3-b655-43a6-b217-97c00aa0db58")) -and $linesToDeleteFound)
{
$linesToDeleteFound = $False
return

View File

@ -9,13 +9,36 @@ Enable-ExperimentalFeature PSFeedbackProvider
Write-Host "Enabling experimental feature: PSCommandNotFoundSuggestion"
Enable-ExperimentalFeature PSCommandNotFoundSuggestion
if (Get-Module -ListAvailable -Name Microsoft.WinGet.Client) {
$wingetModules = Get-Module -ListAvailable -Name Microsoft.WinGet.Client
if ($wingetModules) {
$moduleUpToDate = $false;
foreach ($mod in $wingetModules) {
if ($mod.Version -ge "1.8.1133") {
$moduleUpToDate = $true;
break;
}
}
if ($moduleUpToDate) {
Write-Host "WinGet Client module detected"
} else {
Write-Host "WinGet module needs to be updated. Run `"Update-Module -Name Microsoft.WinGet.Client`" to update `r`n"
}
}
else {
Write-Host "WinGet module was not found. Installation instructions can be found on https://www.powershellgallery.com/packages/Microsoft.WinGet.Client `r`n"
}
$CNFModule = Get-Module -ListAvailable -Name Microsoft.WinGet.CommandNotFound
if ($CNFModule) {
Write-Host "Microsoft.WinGet.CommandNotFound module detected"
} else {
Write-Host "Microsoft.WinGet.CommandNotFound was not found. Installing...`r`n"
Install-Module -Name Microsoft.WinGet.CommandNotFound -Force
Write-Host "Microsoft.WinGet.CommandNotFound module installed`r`n"
}
if (!(Test-Path $PROFILE))
{
Write-Host "Profile file $PROFILE not found".
@ -26,15 +49,28 @@ if (!(Test-Path $PROFILE))
$profileContent = Get-Content -Path $PROFILE -Raw
if ((-not [string]::IsNullOrEmpty($profileContent)) -and ($profileContent.Contains("34de4b3d-13a8-4540-b76d-b9e8d3851756")))
{
if ($profileContent.Contains("Import-Module `"$scriptPath\WinGetCommandNotFound.psd1`""))
{
$profileContent = $profileContent.Replace("Import-Module `"$scriptPath\WinGetCommandNotFound.psd1`"",
"Import-Module -Name Microsoft.WinGet.CommandNotFound")
$profileContent = $profileContent.Replace("34de4b3d-13a8-4540-b76d-b9e8d3851756",
"f45873b3-b655-43a6-b217-97c00aa0db58")
Set-Content -Path $PROFILE -Value $profileContent
Write-Host "Module was successfully upgraded in the profile file."
# This message will be compared against in Command Not Found Settings page code behind. Take care when changing it.
}
}
elseif ((-not [string]::IsNullOrEmpty($profileContent)) -and ($profileContent.Contains("f45873b3-b655-43a6-b217-97c00aa0db58")))
{
Write-Host "Module is already registered in the profile file."
# This message will be compared against in Command Not Found Settings page code behind. Take care when changing it.
}
else
{
Add-Content -Path $PROFILE -Value "`r`n#34de4b3d-13a8-4540-b76d-b9e8d3851756 PowerToys CommandNotFound module"
Add-Content -Path $PROFILE -Value "`r`nImport-Module `"$scriptPath\WinGetCommandNotFound.psd1`""
Add-Content -Path $PROFILE -Value "#34de4b3d-13a8-4540-b76d-b9e8d3851756"
Add-Content -Path $PROFILE -Value "`r`n#f45873b3-b655-43a6-b217-97c00aa0db58 PowerToys CommandNotFound module"
Add-Content -Path $PROFILE -Value "`r`nImport-Module -Name Microsoft.WinGet.CommandNotFound"
Add-Content -Path $PROFILE -Value "#f45873b3-b655-43a6-b217-97c00aa0db58"
Write-Host "Module was successfully registered in the profile file."
# This message will be compared against in Command Not Found Settings page code behind. Take care when changing it.
}

View File

@ -1,7 +1,46 @@
if (Get-Module -ListAvailable -Name Microsoft.WinGet.Client)
$wingetModules = Get-Module -ListAvailable -Name Microsoft.WinGet.Client
if ($wingetModules)
{
Write-Host "WinGet Client module detected."
# This message will be compared against in Command Not Found Settings page code behind. Take care when changing it.
$needsUpdate = $true;
foreach ($mod in $wingetModules)
{
if ($mod.Version -ge "1.8.1133")
{
$needsUpdate = $false;
break;
}
}
if ($needsUpdate)
{
Update-Module -Name Microsoft.WinGet.Client -Force
$wingetModules = Get-Module -ListAvailable -Name Microsoft.WinGet.Client
$updated = $false;
foreach ($mod in $wingetModules)
{
if ($mod.Version -ge "1.8.1133")
{
$updated = $true;
break;
}
}
if ($updated)
{
Write-Host "WinGet Client module updated."
# This message will be compared against in Command Not Found Settings page code behind. Take care when changing it.
}
else
{
Write-Host "WinGet Client module detected."
# This message will be compared against in Command Not Found Settings page code behind. Take care when changing it.
}
}
else
{
Write-Host "WinGet Client module detected."
# This message will be compared against in Command Not Found Settings page code behind. Take care when changing it.
}
}
else {
Install-Module -Name Microsoft.WinGet.Client

View File

@ -0,0 +1,29 @@
if (!(Test-Path $PROFILE))
{
return;
}
$profileContent = Get-Content -Path $PROFILE -Raw
$legacyGuid = "34de4b3d-13a8-4540-b76d-b9e8d3851756"
if ((-not [string]::IsNullOrEmpty($profileContent)) -and ($profileContent.Contains($legacyGuid)))
{
# Upgrade Microsoft.WinGet.Client module
$wingetModule = Get-Module -ListAvailable -Name Microsoft.WinGet.Client
if ($wingetModule -and $wingetModule.Version -lt "1.8.1133") {
Update-Module -Name Microsoft.WinGet.Client
}
# Install Microsoft.WinGet.CommandNotFound module
if (-Not (Get-Module -ListAvailable -Name Microsoft.WinGet.CommandNotFound)) {
Install-Module -Name Microsoft.WinGet.CommandNotFound -Force
}
# Replace old module with new one (and new GUID comment)
$regex = "Import-Module .*WinGetCommandNotFound.psd1`""
if ($profileContent -match $regex)
{
$profileContent = $profileContent -replace $regex, "Import-Module -Name Microsoft.WinGet.CommandNotFound"
$profileContent = $profileContent -replace $legacyGuid, "f45873b3-b655-43a6-b217-97c00aa0db58"
Set-Content -Path $PROFILE -Value $profileContent
}
}

View File

@ -17,10 +17,7 @@
TrueValue="Collapsed" />
<tkconverters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
</Page.Resources>
<controls:SettingsPageControl
x:Uid="CmdNotFound"
IsEnabled="{x:Bind ViewModel.IsArm64Arch, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}"
ModuleImageSource="ms-appx:///Assets/Settings/Modules/CmdNotFound.png">
<controls:SettingsPageControl x:Uid="CmdNotFound" ModuleImageSource="ms-appx:///Assets/Settings/Modules/CmdNotFound.png">
<controls:SettingsPageControl.ModuleContent>
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
<InfoBar
@ -65,7 +62,7 @@
BorderThickness="0"
CornerRadius="0"
IsClosable="False"
IsOpen="{x:Bind Mode=OneWay, Path=ViewModel.IsArm64Arch, Converter={StaticResource BoolNegationConverter}}">
IsOpen="True">
<InfoBar.ActionButton>
<HyperlinkButton x:Uid="CmdNotFound_CheckCompatibility" Command="{x:Bind ViewModel.CheckRequirementsEventHandler}">
<ToolTipService.ToolTip>
@ -74,16 +71,10 @@
</HyperlinkButton>
</InfoBar.ActionButton>
</InfoBar>
<InfoBar
x:Uid="CmdNotFound_Arm64ArchBar"
BorderThickness="0"
CornerRadius="0"
IsClosable="False"
IsOpen="{x:Bind Mode=OneWay, Path=ViewModel.IsArm64Arch}" />
</StackPanel>
</tkcontrols:SettingsExpander.ItemsHeader>
<tkcontrols:SettingsExpander.Items>
<tkcontrols:SettingsCard x:Uid="CmdNotFound_PowerShellDetection" Visibility="{x:Bind ViewModel.IsArm64Arch, Mode=OneWay, Converter={StaticResource BoolToInvertedVisibilityConverter}}">
<tkcontrols:SettingsCard x:Uid="CmdNotFound_PowerShellDetection">
<tkcontrols:SwitchPresenter TargetType="x:Boolean" Value="{x:Bind ViewModel.IsPowerShell7Detected, Mode=OneWay}">
<tkcontrols:Case Value="True">
<StackPanel Orientation="Horizontal" Spacing="8">
@ -113,7 +104,7 @@
</tkcontrols:SwitchPresenter>
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard x:Uid="CmdNotFound_WinGetClientDetection" Visibility="{x:Bind ViewModel.IsArm64Arch, Mode=OneWay, Converter={StaticResource BoolToInvertedVisibilityConverter}}">
<tkcontrols:SettingsCard x:Uid="CmdNotFound_WinGetClientDetection">
<tkcontrols:SwitchPresenter TargetType="x:Boolean" Value="{x:Bind ViewModel.IsWinGetClientModuleDetected, Mode=OneWay}">
<tkcontrols:Case Value="True">
<StackPanel Orientation="Horizontal" Spacing="8">

View File

@ -4046,9 +4046,6 @@ Activate by holding the key for the character you want to add an accent to, then
<data name="GeneralPage_ShowWhatsNewAfterUpdates.Content" xml:space="preserve">
<value>Show the release notes after an update</value>
</data>
<data name="CmdNotFound_Arm64ArchBar.Title" xml:space="preserve">
<value>Command Not Found is not supported on the ARM64 architecture currently. We are actively working on a solution.</value>
</data>
<data name="QuickAccent_Prevent_Activation_On_Game_Mode.Content" xml:space="preserve">
<value>Do not activate when Game Mode is on</value>
<comment>"Game mode" is the Windows feature to prevent notification when playing a game.</comment>

View File

@ -129,11 +129,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
get => _enabledStateIsGPOConfigured;
}
public bool IsArm64Arch
{
get => RuntimeInformation.OSArchitecture == System.Runtime.InteropServices.Architecture.Arm64;
}
public string RunPowerShellOrPreviewScript(string powershellExecutable, string powershellArguments, bool hidePowerShellWindow = false)
{
if (isPowerShellPreviewDetected)
@ -228,7 +223,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
IsWinGetClientModuleDetected = true;
}
else if (result.Contains("WinGet Client module not detected."))
else if (result.Contains("WinGet Client module not detected.") || result.Contains("WinGet Client module needs to be updated."))
{
IsWinGetClientModuleDetected = false;
}
@ -237,7 +232,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
IsCommandNotFoundModuleInstalled = true;
}
else if (result.Contains("Command Not Found module is not registered in the profile file."))
else if (result.Contains("Command Not Found module is not registered in the profile file.") || result.Contains("Outdated version of Command Not Found module found in the profile file."))
{
IsCommandNotFoundModuleInstalled = false;
}
@ -266,7 +261,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
var ps1File = AssemblyDirectory + "\\Assets\\Settings\\Scripts\\InstallWinGetClientModule.ps1";
var arguments = $"-NoProfile -ExecutionPolicy Unrestricted -File \"{ps1File}\"";
var result = RunPowerShellOrPreviewScript("pwsh.exe", arguments);
if (result.Contains("WinGet Client module detected."))
if (result.Contains("WinGet Client module detected.") || result.Contains("WinGet Client module updated."))
{
IsWinGetClientModuleDetected = true;
}
@ -284,7 +279,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
var arguments = $"-NoProfile -ExecutionPolicy Unrestricted -File \"{ps1File}\" -scriptPath \"{AssemblyDirectory}\\..\"";
var result = RunPowerShellOrPreviewScript("pwsh.exe", arguments);
if (result.Contains("Module is already registered in the profile file.") || result.Contains("Module was successfully registered in the profile file."))
if (result.Contains("Module is already registered in the profile file.")
|| result.Contains("Module was successfully registered in the profile file.")
|| result.Contains("Module was successfully upgraded in the profile file."))
{
IsCommandNotFoundModuleInstalled = true;
PowerToysTelemetry.Log.WriteEvent(new CmdNotFoundInstallEvent());