diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index a36b157274..2091f78ed3 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -161,7 +161,6 @@ CHANGECBCHAIN changecursor CHILDACTIVATE CHILDWINDOW -CHT cidl cim CImage @@ -182,6 +181,7 @@ clrcall CLSCTX Clusion cmder +CMDNOTFOUNDMODULEINTERFACE Cmds CMIC CMINVOKECOMMANDINFO @@ -219,7 +219,6 @@ CONFIGW CONFLICTINGMODIFIERKEY CONFLICTINGMODIFIERSHORTCUT CONOUT -constexpr consts contentdialog contentfiles @@ -319,7 +318,6 @@ DESKTOPABSOLUTEEDITING DESKTOPABSOLUTEPARSING desktopshorcutinstalled desktopwindowxamlsource -DEU devblogs devdocs devenum @@ -508,6 +506,7 @@ GETCLIENTAREAANIMATION GETDESKWALLPAPER GETDLGCODE GETDPISCALEDSIZE +getfilesiginforedist GETICON GETMINMAXINFO GETPROPERTYSTOREFLAGS @@ -538,7 +537,6 @@ Hanzi Hardlines hardlinks HARDWAREINPUT -hashcode Hashset hashtag HASHVAL @@ -557,7 +555,6 @@ hcwhite hdc hdrop hdwwiz -HEB Helpline helptext HGFE @@ -720,7 +717,6 @@ jif jjw jobject jpe -JPN jpnime Jsons jsonval @@ -746,10 +742,7 @@ killrunner Knownfolders KSPROPERTY Kybd -LAlt -Lambson languagesjson -langword lastcodeanalysissucceeded Lastdevice LAYOUTRTL @@ -907,6 +900,8 @@ MOUSEHWHEEL MOUSEINPUT MOVESIZEEND MOVESIZESTART +MOZILLAPL +MOZPL mpmc MRM MRT @@ -972,6 +967,7 @@ newdev newitem newpath newrow +newsgroups NIF NLD NLog @@ -984,7 +980,6 @@ NOCLOSEPROCESS NOCOALESCE NOCOPYBITS nodeca -nodiscard nodoc NODRAWCAPTION NODRAWICON @@ -1093,7 +1088,6 @@ pcch pcelt pch PCIDLIST -pcs PCWSTR pdisp pdo @@ -1304,7 +1298,6 @@ RKey RNumber roadmap rop -roundf ROUNDSMALL rpcrt RRF @@ -1320,12 +1313,11 @@ rundll rungameid RUNLEVEL runsettings +runspace runtimeclass runtimeobject runtimepack runtimes -RUS -RValue rvm rwin rwl @@ -1506,7 +1498,6 @@ subquery Superbar sut svchost -SVE SVGIn SVGIO svgz @@ -1579,7 +1570,6 @@ tlbimp TMPVAR TNP toggleswitch -tonos toolkitcontrols toolkitconverters Toolset @@ -1715,7 +1705,6 @@ wcsnicmp WDA wdp wdupenv -weakme webbrowsers webcam webpage @@ -1724,7 +1713,6 @@ wekyb Wevtapi wgpocpl WIC -wifi wil winapi winappdriver @@ -1852,9 +1840,6 @@ zonable zoneset Zoneszonabletester zzz -newsgroups -MOZILLAPL -MOZPL # FALSE POSITIVES diff --git a/.github/actions/spell-check/patterns.txt b/.github/actions/spell-check/patterns.txt index daf0220efe..d48cec9abd 100644 --- a/.github/actions/spell-check/patterns.txt +++ b/.github/actions/spell-check/patterns.txt @@ -118,6 +118,9 @@ aka\.ms/[a-zA-Z0-9]+ # YouTube url \b(?:(?:www\.|)youtube\.com|youtu.be)/(?:channel/|embed/|user/|playlist\?list=|watch\?v=|v/|)[-a-zA-Z0-9?&=_%]* +# power shell gallery website +\bpowershellgallery.com/[-_a-zA-Z0-9()=./%]* + # uuid: (or CompGUIDPrefix) L?(["']|[-<({>]|\b)[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{10,12}(?:\g{-1}|[<})>]) diff --git a/.pipelines/ESRPSigning_core.json b/.pipelines/ESRPSigning_core.json index 73bc45ed03..a6350ec764 100644 --- a/.pipelines/ESRPSigning_core.json +++ b/.pipelines/ESRPSigning_core.json @@ -28,7 +28,10 @@ "PowerToys.AlwaysOnTop.exe", "PowerToys.AlwaysOnTopModuleInterface.dll", - + + "PowerToys.CmdNotFoundModuleInterface.dll", + "PowerToys.CmdNotFound.dll", + "PowerToys.ColorPicker.dll", "PowerToys.ColorPickerUI.dll", "PowerToys.ColorPickerUI.exe", @@ -251,6 +254,7 @@ "Mages.Core.dll", "JetBrains.Annotations.dll", "NLog.Extensions.Logging.dll", + "getfilesiginforedist.dll", "concrt140_app.dll", "msvcp140_1_app.dll", "msvcp140_2_app.dll", diff --git a/.pipelines/versionAndSignCheck.ps1 b/.pipelines/versionAndSignCheck.ps1 index 42b66ed8bc..d5960efb64 100644 --- a/.pipelines/versionAndSignCheck.ps1 +++ b/.pipelines/versionAndSignCheck.ps1 @@ -24,6 +24,7 @@ $versionExceptions = @( $nullVersionExceptions = @( "codicon.ttf", "e_sqlite3.dll", + "getfilesiginforedist.dll", "vcamp140_app.dll", "vcruntime140_app.dll", "vcruntime140_1_app.dll", diff --git a/Directory.Packages.props b/Directory.Packages.props index cb36c3beaf..61ca429ea1 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -33,6 +33,7 @@ + @@ -66,6 +67,7 @@ + diff --git a/NOTICE.md b/NOTICE.md index 59adbebaed..47d5e68545 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -1321,6 +1321,7 @@ 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 5.0.17 - Microsoft.NET.Test.Sdk 17.6.3 - Microsoft.Toolkit.Uwp.Notifications 7.1.2 - Microsoft.Web.WebView2 1.0.2088.41 @@ -1350,6 +1351,7 @@ 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.ServiceProcess.ServiceController 8.0.0 diff --git a/PowerToys.sln b/PowerToys.sln index c3a17c1804..6c37839b19 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -538,6 +538,10 @@ 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}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "EnvironmentVariables", "EnvironmentVariables", "{538ED0BB-B863-4B20-98CC-BCDF7FA0B68A}" @@ -558,6 +562,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests-QoiPreviewHandler EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests-QoiThumbnailProvider", "src\modules\previewpane\UnitTests-QoiThumbnailProvider\UnitTests-QoiThumbnailProvider.csproj", "{F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CmdNotFoundModuleInterface", "src\modules\cmdNotFound\CmdNotFoundModuleInterface\CmdNotFoundModuleInterface.vcxproj", "{0014D652-901F-4456-8D65-06FC5F997FB0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -2328,6 +2334,18 @@ 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 @@ -2436,6 +2454,18 @@ Global {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Release|x64.Build.0 = Release|x64 {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Release|x86.ActiveCfg = Release|x64 {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Release|x86.Build.0 = Release|x64 + {0014D652-901F-4456-8D65-06FC5F997FB0}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {0014D652-901F-4456-8D65-06FC5F997FB0}.Debug|ARM64.Build.0 = Debug|ARM64 + {0014D652-901F-4456-8D65-06FC5F997FB0}.Debug|x64.ActiveCfg = Debug|x64 + {0014D652-901F-4456-8D65-06FC5F997FB0}.Debug|x64.Build.0 = Debug|x64 + {0014D652-901F-4456-8D65-06FC5F997FB0}.Debug|x86.ActiveCfg = Debug|Win32 + {0014D652-901F-4456-8D65-06FC5F997FB0}.Debug|x86.Build.0 = Debug|Win32 + {0014D652-901F-4456-8D65-06FC5F997FB0}.Release|ARM64.ActiveCfg = Release|ARM64 + {0014D652-901F-4456-8D65-06FC5F997FB0}.Release|ARM64.Build.0 = Release|ARM64 + {0014D652-901F-4456-8D65-06FC5F997FB0}.Release|x64.ActiveCfg = Release|x64 + {0014D652-901F-4456-8D65-06FC5F997FB0}.Release|x64.Build.0 = Release|x64 + {0014D652-901F-4456-8D65-06FC5F997FB0}.Release|x86.ActiveCfg = Release|Win32 + {0014D652-901F-4456-8D65-06FC5F997FB0}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2630,6 +2660,8 @@ 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} {51465DA1-C18B-4B99-93E1-ECF8E0FA0CBA} = {538ED0BB-B863-4B20-98CC-BCDF7FA0B68A} @@ -2640,6 +2672,7 @@ Global {6B04803D-B418-4833-A67E-B0FC966636A5} = {2F305555-C296-497E-AC20-5FA1B237996A} {3940AD4D-F748-4BE4-9083-85769CD553EF} = {2F305555-C296-497E-AC20-5FA1B237996A} {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38} = {2F305555-C296-497E-AC20-5FA1B237996A} + {0014D652-901F-4456-8D65-06FC5F997FB0} = {4C0D0746-BE5B-49EE-BD5D-A7811628AE8B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} diff --git a/README.md b/README.md index 575abe4c54..110865b35c 100644 --- a/README.md +++ b/README.md @@ -17,14 +17,14 @@ Microsoft PowerToys is a set of utilities for power users to tune and streamline | | Current utilities: | | |--------------|--------------------|--------------| -| [Always on Top](https://aka.ms/PowerToysOverview_AoT) | [PowerToys Awake](https://aka.ms/PowerToysOverview_Awake) | [Color Picker](https://aka.ms/PowerToysOverview_ColorPicker) | -| [Crop And Lock](https://aka.ms/PowerToysOverview_CropAndLock) | [Environment Variables](https://aka.ms/PowerToysOverview_EnvironmentVariables) | [FancyZones](https://aka.ms/PowerToysOverview_FancyZones) | -| [File Explorer Add-ons](https://aka.ms/PowerToysOverview_FileExplorerAddOns) | [File Locksmith](https://aka.ms/PowerToysOverview_FileLocksmith) | [Hosts File Editor](https://aka.ms/PowerToysOverview_HostsFileEditor) | -| [Image Resizer](https://aka.ms/PowerToysOverview_ImageResizer) | [Keyboard Manager](https://aka.ms/PowerToysOverview_KeyboardManager) | [Mouse utilities](https://aka.ms/PowerToysOverview_MouseUtilities) | -| [Mouse Without Borders](https://aka.ms/PowerToysOverview_MouseWithoutBorders) | [Peek](https://aka.ms/PowerToysOverview_Peek) | [Paste as Plain Text](https://aka.ms/PowerToysOverview_PastePlain) | -| [PowerRename](https://aka.ms/PowerToysOverview_PowerRename) | [PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) | [Quick Accent](https://aka.ms/PowerToysOverview_QuickAccent) | -| [Registry Preview](https://aka.ms/PowerToysOverview_RegistryPreview) | [Screen Ruler](https://aka.ms/PowerToysOverview_ScreenRuler) | [Shortcut Guide](https://aka.ms/PowerToysOverview_ShortcutGuide) | -| [Text Extractor](https://aka.ms/PowerToysOverview_TextExtractor) | [Video Conference Mute](https://aka.ms/PowerToysOverview_VideoConference) | +| [Always on Top](https://aka.ms/PowerToysOverview_AoT) | [PowerToys Awake](https://aka.ms/PowerToysOverview_Awake) | [Command Not Found](https://aka.ms/PowerToysOverview_CmdNotFound) | +| [Color Picker](https://aka.ms/PowerToysOverview_ColorPicker) | [Crop And Lock](https://aka.ms/PowerToysOverview_CropAndLock) | [Environment Variables](https://aka.ms/PowerToysOverview_EnvironmentVariables) | +| [FancyZones](https://aka.ms/PowerToysOverview_FancyZones) | [File Explorer Add-ons](https://aka.ms/PowerToysOverview_FileExplorerAddOns) | [File Locksmith](https://aka.ms/PowerToysOverview_FileLocksmith) | +| [Hosts File Editor](https://aka.ms/PowerToysOverview_HostsFileEditor) | [Image Resizer](https://aka.ms/PowerToysOverview_ImageResizer) | [Keyboard Manager](https://aka.ms/PowerToysOverview_KeyboardManager) | +| [Mouse utilities](https://aka.ms/PowerToysOverview_MouseUtilities) | [Mouse Without Borders](https://aka.ms/PowerToysOverview_MouseWithoutBorders) | [Peek](https://aka.ms/PowerToysOverview_Peek) | +| [Paste as Plain Text](https://aka.ms/PowerToysOverview_PastePlain) | [PowerRename](https://aka.ms/PowerToysOverview_PowerRename) | [PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) | +| [Quick Accent](https://aka.ms/PowerToysOverview_QuickAccent) | [Registry Preview](https://aka.ms/PowerToysOverview_RegistryPreview) | [Screen Ruler](https://aka.ms/PowerToysOverview_ScreenRuler) | +| [Shortcut Guide](https://aka.ms/PowerToysOverview_ShortcutGuide) | [Text Extractor](https://aka.ms/PowerToysOverview_TextExtractor) | [Video Conference Mute](https://aka.ms/PowerToysOverview_VideoConference) | ## Installing and running Microsoft PowerToys diff --git a/doc/images/icons/Command Not Found.png b/doc/images/icons/Command Not Found.png new file mode 100644 index 0000000000..8243b9f8c8 Binary files /dev/null and b/doc/images/icons/Command Not Found.png differ diff --git a/installer/PowerToysSetup/CmdNotFound.wxs b/installer/PowerToysSetup/CmdNotFound.wxs new file mode 100644 index 0000000000..3ac8445d88 --- /dev/null +++ b/installer/PowerToysSetup/CmdNotFound.wxs @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetup/Common.wxi b/installer/PowerToysSetup/Common.wxi index 4a1c93fc8e..6e68ce1986 100644 --- a/installer/PowerToysSetup/Common.wxi +++ b/installer/PowerToysSetup/Common.wxi @@ -18,6 +18,7 @@ + diff --git a/installer/PowerToysSetup/PowerToysInstaller.wixproj b/installer/PowerToysSetup/PowerToysInstaller.wixproj index 6a4f3b80cb..8fb186ebb6 100644 --- a/installer/PowerToysSetup/PowerToysInstaller.wixproj +++ b/installer/PowerToysSetup/PowerToysInstaller.wixproj @@ -1,5 +1,6 @@ - + @@ -14,7 +15,7 @@ SET PTRoot=$(SolutionDir)\.. call "..\..\..\publish.cmd" x64 ) call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuildThisFileDirectory)\generateMonacoWxs.ps1 -monacoWxsFile "$(MSBuildThisFileDirectory)\MonacoSRC.wxs" - + @@ -25,7 +26,7 @@ SET PTRoot=$(SolutionDir)\.. call "..\..\..\publish.cmd" arm64 ) call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuildThisFileDirectory)\generateMonacoWxs.ps1 -monacoWxsFile "$(MSBuildThisFileDirectory)\MonacoSRC.wxs" - + Always @@ -67,7 +68,7 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil - Release + Release $(Platform) 3.10 022a9d30-7c4f-416d-a9df-5ff2661cc0ad @@ -103,6 +104,7 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil + @@ -180,17 +182,6 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil --> - + \ No newline at end of file diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs index 2dea1fab55..ee15ec090a 100644 --- a/installer/PowerToysSetup/Product.wxs +++ b/installer/PowerToysSetup/Product.wxs @@ -72,6 +72,7 @@ + @@ -133,6 +134,7 @@ + @@ -159,6 +161,9 @@ Installed AND (REMOVE="ALL") + + Installed AND (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL") + Installed AND (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL") @@ -200,6 +205,10 @@ Property="UnApplyModulesRegistryChangeSets" Value="[INSTALLFOLDER]" /> + + @@ -252,7 +261,15 @@ BinaryKey="PTCustomActions" DllEntry="UninstallServicesCA" /> - + + + + + @@ -45,6 +47,16 @@ + + + + + + + + + + @@ -55,7 +67,9 @@ + + diff --git a/installer/PowerToysSetupCustomActions/CustomAction.cpp b/installer/PowerToysSetupCustomActions/CustomAction.cpp index c76e2203f8..290df09898 100644 --- a/installer/PowerToysSetupCustomActions/CustomAction.cpp +++ b/installer/PowerToysSetupCustomActions/CustomAction.cpp @@ -433,7 +433,29 @@ UINT __stdcall RemoveWindowsServiceByName(std::wstring serviceName) return ERROR_SUCCESS; } +UINT __stdcall UninstallCommandNotFoundModuleCA(MSIHANDLE hInstall) +{ + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + std::wstring installationFolder; + std::string command; + hr = WcaInitialize(hInstall, "UninstallCommandNotFoundModule"); + 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\\DisableModule.ps1" + "\""; + + system(command.c_str()); + +LExit: + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} UINT __stdcall UninstallServicesCA(MSIHANDLE hInstall) { diff --git a/installer/PowerToysSetupCustomActions/CustomAction.def b/installer/PowerToysSetupCustomActions/CustomAction.def index 7c3c058d9c..6a503da797 100644 --- a/installer/PowerToysSetupCustomActions/CustomAction.def +++ b/installer/PowerToysSetupCustomActions/CustomAction.def @@ -22,4 +22,5 @@ EXPORTS UninstallVirtualCameraDriverCA UnRegisterContextMenuPackagesCA UninstallEmbeddedMSIXCA - UninstallServicesCA \ No newline at end of file + UninstallServicesCA + UninstallCommandNotFoundModuleCA \ No newline at end of file diff --git a/src/common/GPOWrapper/GPOWrapper.cpp b/src/common/GPOWrapper/GPOWrapper.cpp index 77d81e8516..c3177d60db 100644 --- a/src/common/GPOWrapper/GPOWrapper.cpp +++ b/src/common/GPOWrapper/GPOWrapper.cpp @@ -12,6 +12,10 @@ namespace winrt::PowerToys::GPOWrapper::implementation { return static_cast(powertoys_gpo::getConfiguredAwakeEnabledValue()); } + GpoRuleConfigured GPOWrapper::GetConfiguredCmdNotFoundEnabledValue() + { + return static_cast(powertoys_gpo::getConfiguredCmdNotFoundEnabledValue()); + } GpoRuleConfigured GPOWrapper::GetConfiguredColorPickerEnabledValue() { return static_cast(powertoys_gpo::getConfiguredColorPickerEnabledValue()); diff --git a/src/common/GPOWrapper/GPOWrapper.h b/src/common/GPOWrapper/GPOWrapper.h index c29fac95b6..bd153ed258 100644 --- a/src/common/GPOWrapper/GPOWrapper.h +++ b/src/common/GPOWrapper/GPOWrapper.h @@ -9,6 +9,7 @@ namespace winrt::PowerToys::GPOWrapper::implementation GPOWrapper() = default; static GpoRuleConfigured GetConfiguredAlwaysOnTopEnabledValue(); static GpoRuleConfigured GetConfiguredAwakeEnabledValue(); + static GpoRuleConfigured GetConfiguredCmdNotFoundEnabledValue(); static GpoRuleConfigured GetConfiguredColorPickerEnabledValue(); static GpoRuleConfigured GetConfiguredCropAndLockEnabledValue(); static GpoRuleConfigured GetConfiguredFancyZonesEnabledValue(); diff --git a/src/common/GPOWrapper/GPOWrapper.idl b/src/common/GPOWrapper/GPOWrapper.idl index d0e3ebbd4f..3b4c0eca28 100644 --- a/src/common/GPOWrapper/GPOWrapper.idl +++ b/src/common/GPOWrapper/GPOWrapper.idl @@ -13,6 +13,7 @@ namespace PowerToys [default_interface] static runtimeclass GPOWrapper { static GpoRuleConfigured GetConfiguredAlwaysOnTopEnabledValue(); static GpoRuleConfigured GetConfiguredAwakeEnabledValue(); + static GpoRuleConfigured GetConfiguredCmdNotFoundEnabledValue(); static GpoRuleConfigured GetConfiguredColorPickerEnabledValue(); static GpoRuleConfigured GetConfiguredCropAndLockEnabledValue(); static GpoRuleConfigured GetConfiguredFancyZonesEnabledValue(); diff --git a/src/common/GPOWrapperProjection/GPOWrapper.cs b/src/common/GPOWrapperProjection/GPOWrapper.cs index 92196323e5..5e1ef14748 100644 --- a/src/common/GPOWrapperProjection/GPOWrapper.cs +++ b/src/common/GPOWrapperProjection/GPOWrapper.cs @@ -27,6 +27,11 @@ namespace PowerToys.GPOWrapperProjection return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredFancyZonesEnabledValue(); } + public static GpoRuleConfigured GetConfiguredCmdNotFoundEnabledValue() + { + return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredCmdNotFoundEnabledValue(); + } + public static GpoRuleConfigured GetConfiguredColorPickerEnabledValue() { return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredColorPickerEnabledValue(); diff --git a/src/common/ManagedCommon/ManagedCommon.csproj b/src/common/ManagedCommon/ManagedCommon.csproj index 53ca704109..dbe6eca3f7 100644 --- a/src/common/ManagedCommon/ManagedCommon.csproj +++ b/src/common/ManagedCommon/ManagedCommon.csproj @@ -4,7 +4,7 @@ net8.0-windows - win-x64;win-arm64 + win-x64;win-arm64 $(Version).0 Microsoft Corporation PowerToys diff --git a/src/common/ManagedCommon/ModuleType.cs b/src/common/ManagedCommon/ModuleType.cs index 3a489711b0..0bbfaeeda3 100644 --- a/src/common/ManagedCommon/ModuleType.cs +++ b/src/common/ManagedCommon/ModuleType.cs @@ -9,6 +9,7 @@ namespace ManagedCommon AlwaysOnTop, Awake, ColorPicker, + CmdNotFound, CropAndLock, EnvironmentVariables, FancyZones, diff --git a/src/common/logger/logger_settings.h b/src/common/logger/logger_settings.h index 91dd5faae8..cc1b3825ce 100644 --- a/src/common/logger/logger_settings.h +++ b/src/common/logger/logger_settings.h @@ -67,6 +67,8 @@ struct LogSettings inline const static std::string cropAndLockLoggerName = "crop-and-lock"; inline const static std::wstring registryPreviewLogPath = L"Logs\\registryPreview-log.txt"; inline const static std::string environmentVariablesLoggerName = "environment-variables"; + inline const static std::wstring cmdNotFoundLogPath = L"Logs\\cmd-not-found-log.txt"; + inline const static std::string cmdNotFoundLoggerName = "cmd-not-found"; inline const static int retention = 30; std::wstring logLevel; LogSettings(); diff --git a/src/common/utils/gpo.h b/src/common/utils/gpo.h index be88e0d22d..016bd744cc 100644 --- a/src/common/utils/gpo.h +++ b/src/common/utils/gpo.h @@ -24,6 +24,7 @@ namespace powertoys_gpo { const std::wstring POLICY_CONFIGURE_ENABLED_GLOBAL_ALL_UTILITIES = L"ConfigureGlobalUtilityEnabledState"; const std::wstring POLICY_CONFIGURE_ENABLED_ALWAYS_ON_TOP = L"ConfigureEnabledUtilityAlwaysOnTop"; const std::wstring POLICY_CONFIGURE_ENABLED_AWAKE = L"ConfigureEnabledUtilityAwake"; + const std::wstring POLICY_CONFIGURE_ENABLED_CMD_NOT_FOUND = L"ConfigureEnabledUtilityCmdNotFound"; const std::wstring POLICY_CONFIGURE_ENABLED_COLOR_PICKER = L"ConfigureEnabledUtilityColorPicker"; const std::wstring POLICY_CONFIGURE_ENABLED_CROP_AND_LOCK = L"ConfigureEnabledUtilityCropAndLock"; const std::wstring POLICY_CONFIGURE_ENABLED_FANCYZONES = L"ConfigureEnabledUtilityFancyZones"; @@ -224,6 +225,11 @@ namespace powertoys_gpo { return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_AWAKE); } + inline gpo_rule_configured_t getConfiguredCmdNotFoundEnabledValue() + { + return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_CMD_NOT_FOUND); + } + inline gpo_rule_configured_t getConfiguredColorPickerEnabledValue() { return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_COLOR_PICKER); diff --git a/src/gpo/assets/PowerToys.admx b/src/gpo/assets/PowerToys.admx index 0b135f665e..59a0973dcf 100644 --- a/src/gpo/assets/PowerToys.admx +++ b/src/gpo/assets/PowerToys.admx @@ -15,6 +15,7 @@ + @@ -59,6 +60,16 @@ + + + + + + + + + + diff --git a/src/gpo/assets/en-US/PowerToys.adml b/src/gpo/assets/en-US/PowerToys.adml index de04d77c8b..2f69d63986 100644 --- a/src/gpo/assets/en-US/PowerToys.adml +++ b/src/gpo/assets/en-US/PowerToys.adml @@ -17,6 +17,7 @@ PowerToys version 0.73.0 or later PowerToys version 0.75.0 or later PowerToys version 0.76.0 or later + PowerToys version 0.77.0 or later This policy configures the enabled state for all PowerToys utilities. @@ -112,6 +113,7 @@ Note: Changes require a restart of PowerToys Run. Always On Top: Configure enabled state Awake: Configure enabled state Color Picker: Configure enabled state + Command Not Found: Configure enabled state Crop And Lock: Configure enabled state Environment Variables: Configure enabled state FancyZones: Configure enabled state diff --git a/src/modules/cmdNotFound/CmdNotFound/CmdNotFound.csproj b/src/modules/cmdNotFound/CmdNotFound/CmdNotFound.csproj new file mode 100644 index 0000000000..0d4f12d5b4 --- /dev/null +++ b/src/modules/cmdNotFound/CmdNotFound/CmdNotFound.csproj @@ -0,0 +1,67 @@ + + + + + net8.0-windows10.0.22621.0 + 10.0.19041.0 + 10.0.19041.0 + win-x64;win-arm64 + enable + Microsoft Corporation + PowerToys + enable + PowerToys CommandNotFound + PowerToys.CmdNotFound + false + true + ..\..\..\..\$(Platform)\$(Configuration) + false + false + true + true + + + + + win-x64 + + + win-arm64 + + + + DEBUG;TRACE + full + prompt + 4 + false + + + + TRACE;RELEASE + true + pdbonly + prompt + 4 + + + + + contentFiles + all + + + contentFiles + all + + + PreserveNewest + PreserveNewest + + + + + + + + diff --git a/src/modules/cmdNotFound/CmdNotFound/Init.cs b/src/modules/cmdNotFound/CmdNotFound/Init.cs new file mode 100644 index 0000000000..4d4ecb6d60 --- /dev/null +++ b/src/modules/cmdNotFound/CmdNotFound/Init.cs @@ -0,0 +1,57 @@ +// 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(new Guid(Id)); + SubsystemManager.UnregisterSubsystem(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; + } + } +} diff --git a/src/modules/cmdNotFound/CmdNotFound/PooledPowerShellObjectPolicy.cs b/src/modules/cmdNotFound/CmdNotFound/PooledPowerShellObjectPolicy.cs new file mode 100644 index 0000000000..75d2ba8922 --- /dev/null +++ b/src/modules/cmdNotFound/CmdNotFound/PooledPowerShellObjectPolicy.cs @@ -0,0 +1,34 @@ +// 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 + { + 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; + } + } +} diff --git a/src/modules/cmdNotFound/CmdNotFound/Telemetry/CmdNotFoundFeedbackProvidedEvent.cs b/src/modules/cmdNotFound/CmdNotFound/Telemetry/CmdNotFoundFeedbackProvidedEvent.cs new file mode 100644 index 0000000000..83563bbe35 --- /dev/null +++ b/src/modules/cmdNotFound/CmdNotFound/Telemetry/CmdNotFoundFeedbackProvidedEvent.cs @@ -0,0 +1,16 @@ +// 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; + } +} diff --git a/src/modules/cmdNotFound/CmdNotFound/Telemetry/CmdNotFoundSuggestionProvidedEvent.cs b/src/modules/cmdNotFound/CmdNotFound/Telemetry/CmdNotFoundSuggestionProvidedEvent.cs new file mode 100644 index 0000000000..2f55dbbb39 --- /dev/null +++ b/src/modules/cmdNotFound/CmdNotFound/Telemetry/CmdNotFoundSuggestionProvidedEvent.cs @@ -0,0 +1,16 @@ +// 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; + } +} diff --git a/src/modules/cmdNotFound/CmdNotFound/WinGetCommandNotFound.psd1 b/src/modules/cmdNotFound/CmdNotFound/WinGetCommandNotFound.psd1 new file mode 100644 index 0000000000..c7a1118eac --- /dev/null +++ b/src/modules/cmdNotFound/CmdNotFound/WinGetCommandNotFound.psd1 @@ -0,0 +1,11 @@ +@{ + 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"; }) +} diff --git a/src/modules/cmdNotFound/CmdNotFound/WinGetCommandNotFoundFeedbackPredictor.cs b/src/modules/cmdNotFound/CmdNotFound/WinGetCommandNotFoundFeedbackPredictor.cs new file mode 100644 index 0000000000..681b073bad --- /dev/null +++ b/src/modules/cmdNotFound/CmdNotFound/WinGetCommandNotFoundFeedbackPredictor.cs @@ -0,0 +1,206 @@ +// 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 Microsoft.Extensions.ObjectPool; +using Microsoft.PowerToys.Telemetry; + +namespace WinGetCommandNotFound +{ + public sealed class WinGetCommandNotFoundFeedbackPredictor : IFeedbackProvider, ICommandPredictor + { + private readonly Guid _guid; + + private readonly ObjectPool _pool; + + private const int _maxSuggestions = 20; + + private List? _candidates; + + private bool _warmedUp; + + public static WinGetCommandNotFoundFeedbackPredictor Singleton { get; } = new WinGetCommandNotFoundFeedbackPredictor(Init.Id); + + private WinGetCommandNotFoundFeedbackPredictor(string guid) + { + _guid = new Guid(guid); + + var provider = new DefaultObjectPoolProvider(); + _pool = provider.Create(new PooledPowerShellObjectPolicy()); + _pool.Return(_pool.Get()); + Task.Run(() => WarmUp()); + } + + 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? FunctionsToDefine => null; + + private void WarmUp() + { + var ps = _pool.Get(); + try + { + ps.AddCommand("Find-WinGetPackage") + .AddParameter("Count", 1) + .Invoke(); + } + finally + { + _pool.Return(ps); + _warmedUp = true; + } + } + + /// + /// Gets feedback based on the given commandline and error record. + /// + public FeedbackItem? GetFeedback(FeedbackContext context, CancellationToken token) + { + var target = (string)context.LastError!.TargetObject; + if (target is not null) + { + 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(); + 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); + } + + return null; + } + + private Collection FindPackages(string query, ref bool tooManySuggestions, ref string packageMatchFilterField) + { + if (!_warmedUp) + { + return new Collection(); + } + + 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? result = null; + + foreach (string c in _candidates) + { + if (c.StartsWith(input, StringComparison.OrdinalIgnoreCase)) + { + result ??= new List(_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 history) + { + // Reset the candidate state. + _candidates = null; + } + } +} diff --git a/src/modules/cmdNotFound/CmdNotFoundModuleInterface/CmdNotFoundModuleInterface.rc b/src/modules/cmdNotFound/CmdNotFoundModuleInterface/CmdNotFoundModuleInterface.rc new file mode 100644 index 0000000000..e9ac022fc5 --- /dev/null +++ b/src/modules/cmdNotFound/CmdNotFoundModuleInterface/CmdNotFoundModuleInterface.rc @@ -0,0 +1,108 @@ +// Microsoft Visual C++ generated resource script. +// +#include +#include "resource.h" +#include "../../../common/version/version.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +1 VERSIONINFO +FILEVERSION FILE_VERSION +PRODUCTVERSION PRODUCT_VERSION +FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG +FILEFLAGS VS_FF_DEBUG +#else +FILEFLAGS 0x0L +#endif +FILEOS VOS_NT_WINDOWS32 +FILETYPE VFT_DLL +FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset + BEGIN + VALUE "CompanyName", COMPANY_NAME + VALUE "FileDescription", FILE_DESCRIPTION + VALUE "FileVersion", FILE_VERSION_STRING + VALUE "InternalName", INTERNAL_NAME + VALUE "LegalCopyright", COPYRIGHT_NOTE + VALUE "OriginalFilename", ORIGINAL_FILENAME + VALUE "ProductName", PRODUCT_NAME + VALUE "ProductVersion", PRODUCT_VERSION_STRING + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset + END +END + + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_CMD_NOT_FOUND_NAME "Command Not Found" +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/src/modules/cmdNotFound/CmdNotFoundModuleInterface/CmdNotFoundModuleInterface.vcxproj b/src/modules/cmdNotFound/CmdNotFoundModuleInterface/CmdNotFoundModuleInterface.vcxproj new file mode 100644 index 0000000000..a7fd427c2a --- /dev/null +++ b/src/modules/cmdNotFound/CmdNotFoundModuleInterface/CmdNotFoundModuleInterface.vcxproj @@ -0,0 +1,107 @@ + + + + 17.0 + Win32Proj + {0014d652-901f-4456-8d65-06fc5f997fb0} + CmdNotFoundModuleInterface + PowerToys.CmdNotFoundModuleInterface + v143 + CmdNotFoundModuleInterface + + + + DynamicLibrary + true + Unicode + + + DynamicLibrary + false + true + Unicode + + + + + + + + + + + + ..\..\..\..\$(Platform)\$(Configuration)\ + + + + Level3 + true + WIN32;_DEBUG;CMDNOTFOUNDMODULEINTERFACE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + + + Windows + true + false + Shlwapi.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) + + + + + Level3 + true + true + true + WIN32;NDEBUG;CMDNOTFOUNDMODULEINTERFACE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + + + Windows + true + true + true + false + Shlwapi.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) + + + + + ..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories) + + + + + + Header Files + + + + + + Source Files + + + + Create + + + + + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + + + {6955446d-23f7-4023-9bb3-8657f904af99} + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/cmdNotFound/CmdNotFoundModuleInterface/CmdNotFoundModuleInterface.vcxproj.filters b/src/modules/cmdNotFound/CmdNotFoundModuleInterface/CmdNotFoundModuleInterface.vcxproj.filters new file mode 100644 index 0000000000..1834d3ae88 --- /dev/null +++ b/src/modules/cmdNotFound/CmdNotFoundModuleInterface/CmdNotFoundModuleInterface.vcxproj.filters @@ -0,0 +1,45 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/src/modules/cmdNotFound/CmdNotFoundModuleInterface/dllmain.cpp b/src/modules/cmdNotFound/CmdNotFoundModuleInterface/dllmain.cpp new file mode 100644 index 0000000000..fed395d45d --- /dev/null +++ b/src/modules/cmdNotFound/CmdNotFoundModuleInterface/dllmain.cpp @@ -0,0 +1,161 @@ +// dllmain.cpp : Defines the entry point for the DLL application. +#include "pch.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "resource.h" +#include "trace.h" + +BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + Trace::RegisterProvider(); + break; + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + case DLL_PROCESS_DETACH: + Trace::UnregisterProvider(); + break; + } + return TRUE; +} + +const static wchar_t* MODULE_NAME = L"Command Not Found"; +const static wchar_t* MODULE_DESC = L"A module that detects an error thrown by a command in PowerShell and suggests a relevant WinGet package to install, if available."; + +inline const std::wstring ModuleKey = L"CmdNotFound"; + +class CmdNotFound : public PowertoyModuleIface +{ + std::wstring app_name; + std::wstring app_key; + +private: + bool m_enabled = false; + + void install_module() + { + auto module_path = get_module_folderpath(); + + std::string command = "pwsh.exe"; + command += " "; + command += "-NoProfile -NonInteractive -NoLogo -WindowStyle Hidden -ExecutionPolicy Unrestricted -File \"" + winrt::to_string(module_path) + "\\WinUI3Apps\\Assets\\Settings\\Scripts\\EnableModule.ps1" + "\"" + " -scriptPath \"" + winrt::to_string(module_path) + "\""; + + int ret = system(command.c_str()); + + if (ret != 0) + { + Logger::error("Running EnableModule.ps1 script failed."); + } + else + { + Logger::info("Module installed successfully."); + Trace::EnableCmdNotFoundGpo(true); + } + } + + void uninstall_module() + { + auto module_path = get_module_folderpath(); + + std::string command = "pwsh.exe"; + command += " "; + command += "-NoProfile -NonInteractive -NoLogo -WindowStyle Hidden -ExecutionPolicy Unrestricted -File \"" + winrt::to_string(module_path) + "\\WinUI3Apps\\Assets\\Settings\\Scripts\\DisableModule.ps1" + "\""; + + int ret = system(command.c_str()); + + if (ret != 0) + { + Logger::error("Running EnableModule.ps1 script failed."); + } + else + { + Logger::info("Module uninstalled successfully."); + Trace::EnableCmdNotFoundGpo(false); + } + } + +public: + CmdNotFound() + { + app_name = GET_RESOURCE_STRING(IDS_CMD_NOT_FOUND_NAME); + app_key = ModuleKey; + + std::filesystem::path logFilePath(PTSettingsHelper::get_module_save_folder_location(this->app_key)); + logFilePath.append(LogSettings::cmdNotFoundLogPath); + Logger::init(LogSettings::cmdNotFoundLoggerName, logFilePath.wstring(), PTSettingsHelper::get_log_settings_file_location()); + Logger::info("CmdNotFound object is constructing"); + + powertoys_gpo::gpo_rule_configured_t gpo_rule_configured_value = gpo_policy_enabled_configuration(); + if (gpo_rule_configured_value == powertoys_gpo::gpo_rule_configured_t::gpo_rule_configured_enabled) + { + install_module(); + m_enabled = true; + } + else if (gpo_rule_configured_value == powertoys_gpo::gpo_rule_configured_t::gpo_rule_configured_disabled) + { + uninstall_module(); + m_enabled = false; + } + } + + virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override + { + return powertoys_gpo::getConfiguredCmdNotFoundEnabledValue(); + } + + virtual void destroy() override + { + delete this; + } + + virtual const wchar_t* get_name() override + { + return MODULE_NAME; + } + + virtual const wchar_t* get_key() override + { + return app_key.c_str(); + } + + virtual bool get_config(wchar_t* /*buffer*/, int* /*buffer_size*/) override + { + return false; + } + + virtual void set_config(const wchar_t* config) override + { + } + + virtual void enable() + { + } + + virtual void disable() + { + } + + virtual bool is_enabled() override + { + return m_enabled; + } +}; + +extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create() +{ + return new CmdNotFound(); +} diff --git a/src/modules/cmdNotFound/CmdNotFoundModuleInterface/pch.cpp b/src/modules/cmdNotFound/CmdNotFoundModuleInterface/pch.cpp new file mode 100644 index 0000000000..64b7eef6d6 --- /dev/null +++ b/src/modules/cmdNotFound/CmdNotFoundModuleInterface/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: source file corresponding to the pre-compiled header + +#include "pch.h" + +// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. diff --git a/src/modules/cmdNotFound/CmdNotFoundModuleInterface/pch.h b/src/modules/cmdNotFound/CmdNotFoundModuleInterface/pch.h new file mode 100644 index 0000000000..96a774ab2a --- /dev/null +++ b/src/modules/cmdNotFound/CmdNotFoundModuleInterface/pch.h @@ -0,0 +1,16 @@ +// pch.h: This is a precompiled header file. +// Files listed below are compiled only once, improving build performance for future builds. +// This also affects IntelliSense performance, including code completion and many code browsing features. +// However, files listed here are ALL re-compiled if any one of them is updated between builds. +// Do not add files here that you will be updating frequently as this negates the performance advantage. + +#ifndef PCH_H +#define PCH_H + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files +#include + +#include + +#endif //PCH_H diff --git a/src/modules/cmdNotFound/CmdNotFoundModuleInterface/resource.h b/src/modules/cmdNotFound/CmdNotFoundModuleInterface/resource.h new file mode 100644 index 0000000000..9b16533a8a --- /dev/null +++ b/src/modules/cmdNotFound/CmdNotFoundModuleInterface/resource.h @@ -0,0 +1,21 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Awake.rc +// +#define IDS_CMD_NOT_FOUND_NAME 101 + + +#define FILE_DESCRIPTION "PowerToys Command Not Found" +#define INTERNAL_NAME "PowerToys.CmdNotFoundModuleInterface" +#define ORIGINAL_FILENAME "PowerToys.CmdNotFoundModuleInterface.dll" + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/modules/cmdNotFound/CmdNotFoundModuleInterface/trace.cpp b/src/modules/cmdNotFound/CmdNotFoundModuleInterface/trace.cpp new file mode 100644 index 0000000000..255c46ea99 --- /dev/null +++ b/src/modules/cmdNotFound/CmdNotFoundModuleInterface/trace.cpp @@ -0,0 +1,30 @@ +#include "pch.h" +#include "trace.h" + +TRACELOGGING_DEFINE_PROVIDER( + g_hProvider, + "Microsoft.PowerToys", + // {38e8889b-9731-53f5-e901-e8a7c1753074} + (0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74), + TraceLoggingOptionProjectTelemetry()); + +void Trace::RegisterProvider() +{ + TraceLoggingRegister(g_hProvider); +} + +void Trace::UnregisterProvider() +{ + TraceLoggingUnregister(g_hProvider); +} + +// Log if the user has CmdNotFound enabled or disabled +void Trace::EnableCmdNotFoundGpo(const bool enabled) noexcept +{ + TraceLoggingWrite( + g_hProvider, + "CmdNotFound_EnableCmdNotFound", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), + TraceLoggingBoolean(enabled, "Enabled")); +} diff --git a/src/modules/cmdNotFound/CmdNotFoundModuleInterface/trace.h b/src/modules/cmdNotFound/CmdNotFoundModuleInterface/trace.h new file mode 100644 index 0000000000..4294c510a6 --- /dev/null +++ b/src/modules/cmdNotFound/CmdNotFoundModuleInterface/trace.h @@ -0,0 +1,11 @@ +#pragma once + +class Trace +{ +public: + static void RegisterProvider(); + static void UnregisterProvider(); + + // Log if the user has CmdNotFound enabled or disabled + static void EnableCmdNotFoundGpo(const bool enabled) noexcept; +}; diff --git a/src/runner/main.cpp b/src/runner/main.cpp index 59a455b379..a16197655e 100644 --- a/src/runner/main.cpp +++ b/src/runner/main.cpp @@ -158,6 +158,7 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow L"WinUI3Apps/PowerToys.EnvironmentVariablesModuleInterface.dll", L"PowerToys.MouseWithoutBordersModuleInterface.dll", L"PowerToys.CropAndLockModuleInterface.dll", + L"PowerToys.CmdNotFoundModuleInterface.dll", }; const auto VCM_PATH = L"PowerToys.VideoConferenceModule.dll"; if (const auto mf = LoadLibraryA("mf.dll")) diff --git a/src/settings-ui/Settings.UI.Library/CmdNotFoundSettings.cs b/src/settings-ui/Settings.UI.Library/CmdNotFoundSettings.cs new file mode 100644 index 0000000000..4353e5a188 --- /dev/null +++ b/src/settings-ui/Settings.UI.Library/CmdNotFoundSettings.cs @@ -0,0 +1,42 @@ +// 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.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.PowerToys.Settings.UI.Library.Interfaces; + +namespace Microsoft.PowerToys.Settings.UI.Library +{ + public class CmdNotFoundSettings : BasePTModuleSettings, ISettingsConfig + { + private static readonly JsonSerializerOptions SerializerOptions = new() + { + WriteIndented = true, + }; + + public const string ModuleName = "CmdNotFound"; + + public CmdNotFoundSettings() + { + Version = "1"; + Name = ModuleName; + } + + public virtual void Save(ISettingsUtils settingsUtils) + { + // Save settings to file + ArgumentNullException.ThrowIfNull(settingsUtils); + + settingsUtils.SaveSettings(JsonSerializer.Serialize(this, SerializerOptions), ModuleName); + } + + public string GetModuleName() + => Name; + + // This can be utilized in the future if the settings.json file is to be modified/deleted. + public bool UpgradeSettingsConfiguration() + => false; + } +} diff --git a/src/settings-ui/Settings.UI.Library/EnabledModules.cs b/src/settings-ui/Settings.UI.Library/EnabledModules.cs index 27af49c4eb..a9ef81c1a8 100644 --- a/src/settings-ui/Settings.UI.Library/EnabledModules.cs +++ b/src/settings-ui/Settings.UI.Library/EnabledModules.cs @@ -427,6 +427,23 @@ namespace Microsoft.PowerToys.Settings.UI.Library } } + private bool cmdNotFound = true; + + [JsonPropertyName("CmdNotFound")] + public bool CmdNotFound + { + get => cmdNotFound; + set + { + if (cmdNotFound != value) + { + LogTelemetryEvent(value); + cmdNotFound = value; + NotifyChange(); + } + } + } + private bool environmentVariables = true; [JsonPropertyName("EnvironmentVariables")] diff --git a/src/settings-ui/Settings.UI.Library/Telemetry/Events/CmdNotFoundInstallEvent.cs b/src/settings-ui/Settings.UI.Library/Telemetry/Events/CmdNotFoundInstallEvent.cs new file mode 100644 index 0000000000..97d700a8e5 --- /dev/null +++ b/src/settings-ui/Settings.UI.Library/Telemetry/Events/CmdNotFoundInstallEvent.cs @@ -0,0 +1,16 @@ +// 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 Microsoft.PowerToys.Settings.UI.Library.Telemetry.Events +{ + [EventData] + public class CmdNotFoundInstallEvent : EventBase, IEvent + { + public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage; + } +} diff --git a/src/settings-ui/Settings.UI.Library/Telemetry/Events/CmdNotFoundUninstallEvent.cs b/src/settings-ui/Settings.UI.Library/Telemetry/Events/CmdNotFoundUninstallEvent.cs new file mode 100644 index 0000000000..ae469903ee --- /dev/null +++ b/src/settings-ui/Settings.UI.Library/Telemetry/Events/CmdNotFoundUninstallEvent.cs @@ -0,0 +1,16 @@ +// 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 Microsoft.PowerToys.Settings.UI.Library.Telemetry.Events +{ + [EventData] + public class CmdNotFoundUninstallEvent : EventBase, IEvent + { + public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage; + } +} diff --git a/src/settings-ui/Settings.UI/Assets/Settings/FluentIcons/FluentIconsCmdNotFound.png b/src/settings-ui/Settings.UI/Assets/Settings/FluentIcons/FluentIconsCmdNotFound.png new file mode 100644 index 0000000000..c4de6573a5 Binary files /dev/null and b/src/settings-ui/Settings.UI/Assets/Settings/FluentIcons/FluentIconsCmdNotFound.png differ diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Modules/CmdNotFound.png b/src/settings-ui/Settings.UI/Assets/Settings/Modules/CmdNotFound.png new file mode 100644 index 0000000000..4b06c4de36 Binary files /dev/null and b/src/settings-ui/Settings.UI/Assets/Settings/Modules/CmdNotFound.png differ diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Modules/OOBE/CmdNotFound.png b/src/settings-ui/Settings.UI/Assets/Settings/Modules/OOBE/CmdNotFound.png new file mode 100644 index 0000000000..f40d8ae08c Binary files /dev/null and b/src/settings-ui/Settings.UI/Assets/Settings/Modules/OOBE/CmdNotFound.png differ diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Scripts/DisableModule.ps1 b/src/settings-ui/Settings.UI/Assets/Settings/Scripts/DisableModule.ps1 new file mode 100644 index 0000000000..fef86f4935 --- /dev/null +++ b/src/settings-ui/Settings.UI/Assets/Settings/Scripts/DisableModule.ps1 @@ -0,0 +1,35 @@ +$profileContent = Get-Content $PROFILE + +$newContent = "" +$linesToDeleteFound = $False +$atLeastOneInstanceFound = $False + +$profileContent | ForEach-Object { + if ($_.Contains("34de4b3d-13a8-4540-b76d-b9e8d3851756") -and !$linesToDeleteFound) + { + $linesToDeleteFound = $True + $atLeastOneInstanceFound = $True + return + } + + if ($_.Contains("34de4b3d-13a8-4540-b76d-b9e8d3851756") -and $linesToDeleteFound) + { + $linesToDeleteFound = $False + return + } + + if($linesToDeleteFound) + { + return + } + + $newContent += $_ + "`r`n" +} + +if($atLeastOneInstanceFound) +{ + Set-Content -Path $PROFILE -Value $newContent + Write-Host "Removed the Command Not Found reference from the profile file." +} else { + Write-Host "No instance of Command Not Found was found in the profile file." +} diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Scripts/EnableModule.ps1 b/src/settings-ui/Settings.UI/Assets/Settings/Scripts/EnableModule.ps1 new file mode 100644 index 0000000000..844613a2be --- /dev/null +++ b/src/settings-ui/Settings.UI/Assets/Settings/Scripts/EnableModule.ps1 @@ -0,0 +1,38 @@ +[CmdletBinding()] +Param( + [Parameter(Mandatory=$True,Position=1)] + [string]$scriptPath +) + +Write-Host "Enabling experimental feature: PSFeedbackProvider" +Enable-ExperimentalFeature PSFeedbackProvider +Write-Host "Enabling experimental feature: PSCommandNotFoundSuggestion" +Enable-ExperimentalFeature PSCommandNotFoundSuggestion + +if (Get-Module -ListAvailable -Name Microsoft.WinGet.Client) { + Write-Host "WinGet Client module detected" +} +else { + Write-Host "WinGet module was not found. Installation instructions can be found on https://www.powershellgallery.com/packages/Microsoft.WinGet.Client `r`n" +} + +if (!(Test-Path $PROFILE)) +{ + Write-Host "Profile file $PROFILE not found". + New-Item -Path $PROFILE -ItemType File + Write-Host "Created profile file $PROFILE". +} + +$profileContent = Get-Content -Path $PROFILE -Raw + +if ((-not [string]::IsNullOrEmpty($profileContent)) -and ($profileContent.Contains("34de4b3d-13a8-4540-b76d-b9e8d3851756"))) +{ + Write-Host "Module is already registered in the profile file." +} +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" + Write-Host "Module was successfully registered in the profile file." +} diff --git a/src/settings-ui/Settings.UI/Helpers/ModuleHelper.cs b/src/settings-ui/Settings.UI/Helpers/ModuleHelper.cs index 25d5fb4f59..7daa62ae0d 100644 --- a/src/settings-ui/Settings.UI/Helpers/ModuleHelper.cs +++ b/src/settings-ui/Settings.UI/Helpers/ModuleHelper.cs @@ -44,6 +44,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers case ModuleType.AlwaysOnTop: return generalSettingsConfig.Enabled.AlwaysOnTop; case ModuleType.Awake: return generalSettingsConfig.Enabled.Awake; case ModuleType.ColorPicker: return generalSettingsConfig.Enabled.ColorPicker; + case ModuleType.CmdNotFound: return generalSettingsConfig.Enabled.CmdNotFound; case ModuleType.CropAndLock: return generalSettingsConfig.Enabled.CropAndLock; case ModuleType.EnvironmentVariables: return generalSettingsConfig.Enabled.EnvironmentVariables; case ModuleType.FancyZones: return generalSettingsConfig.Enabled.FancyZones; @@ -76,6 +77,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers case ModuleType.AlwaysOnTop: generalSettingsConfig.Enabled.AlwaysOnTop = isEnabled; break; case ModuleType.Awake: generalSettingsConfig.Enabled.Awake = isEnabled; break; case ModuleType.ColorPicker: generalSettingsConfig.Enabled.ColorPicker = isEnabled; break; + case ModuleType.CmdNotFound: generalSettingsConfig.Enabled.CmdNotFound = isEnabled; break; case ModuleType.CropAndLock: generalSettingsConfig.Enabled.CropAndLock = isEnabled; break; case ModuleType.EnvironmentVariables: generalSettingsConfig.Enabled.EnvironmentVariables = isEnabled; break; case ModuleType.FancyZones: generalSettingsConfig.Enabled.FancyZones = isEnabled; break; @@ -107,6 +109,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers case ModuleType.AlwaysOnTop: return GPOWrapper.GetConfiguredAlwaysOnTopEnabledValue(); case ModuleType.Awake: return GPOWrapper.GetConfiguredAwakeEnabledValue(); case ModuleType.ColorPicker: return GPOWrapper.GetConfiguredColorPickerEnabledValue(); + case ModuleType.CmdNotFound: return GPOWrapper.GetConfiguredCmdNotFoundEnabledValue(); case ModuleType.CropAndLock: return GPOWrapper.GetConfiguredCropAndLockEnabledValue(); case ModuleType.EnvironmentVariables: return GPOWrapper.GetConfiguredEnvironmentVariablesEnabledValue(); case ModuleType.FancyZones: return GPOWrapper.GetConfiguredFancyZonesEnabledValue(); @@ -139,6 +142,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers ModuleType.AlwaysOnTop => Color.FromArgb(255, 74, 196, 242), // #4ac4f2 ModuleType.Awake => Color.FromArgb(255, 40, 177, 233), // #28b1e9 ModuleType.ColorPicker => Color.FromArgb(255, 7, 129, 211), // #0781d3 + ModuleType.CmdNotFound => Color.FromArgb(255, 31, 164, 227), // #1fa4e3 ModuleType.CropAndLock => Color.FromArgb(255, 32, 166, 228), // #20a6e4 ModuleType.EnvironmentVariables => Color.FromArgb(255, 16, 132, 208), // #1084d0 ModuleType.FancyZones => Color.FromArgb(255, 65, 209, 247), // #41d1f7 @@ -171,6 +175,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers ModuleType.AlwaysOnTop => typeof(AlwaysOnTopPage), ModuleType.Awake => typeof(AwakePage), ModuleType.ColorPicker => typeof(ColorPickerPage), + ModuleType.CmdNotFound => typeof(CmdNotFoundPage), ModuleType.CropAndLock => typeof(CropAndLockPage), ModuleType.EnvironmentVariables => typeof(EnvironmentVariablesPage), ModuleType.FancyZones => typeof(FancyZonesPage), diff --git a/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs b/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs index 1a5091f5f4..111b0f669a 100644 --- a/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs +++ b/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs @@ -9,6 +9,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Enums Overview = 0, AlwaysOnTop, Awake, + CmdNotFound, ColorPicker, CropAndLock, EnvironmentVariables, diff --git a/src/settings-ui/Settings.UI/PowerToys.Settings.csproj b/src/settings-ui/Settings.UI/PowerToys.Settings.csproj index bffa7cc353..16c118872a 100644 --- a/src/settings-ui/Settings.UI/PowerToys.Settings.csproj +++ b/src/settings-ui/Settings.UI/PowerToys.Settings.csproj @@ -53,7 +53,7 @@ CA1720 true - + PowerToys.GPOWrapper @@ -61,10 +61,10 @@ false - - - https://pkgs.dev.azure.com/dotnet/CommunityToolkit/_packaging/CommunityToolkit-Labs/nuget/v3/index.json - + + + https://pkgs.dev.azure.com/dotnet/CommunityToolkit/_packaging/CommunityToolkit-Labs/nuget/v3/index.json + @@ -96,11 +96,11 @@ - + - + @@ -111,7 +111,7 @@ - + @@ -119,10 +119,19 @@ VSTHRD002;VSTHRD110;VSTHRD100;VSTHRD200;VSTHRD101 - - - Always - - - + + + Always + + + + + + Always + + + Always + + + diff --git a/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs index 46cd4d7e0e..9d3b030ef0 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs @@ -395,6 +395,7 @@ namespace Microsoft.PowerToys.Settings.UI case "Overview": return typeof(GeneralPage); case "AlwaysOnTop": return typeof(AlwaysOnTopPage); case "Awake": return typeof(AwakePage); + case "CmdNotFound": return typeof(CmdNotFoundPage); case "ColorPicker": return typeof(ColorPickerPage); case "FancyZones": return typeof(FancyZonesPage); case "FileLocksmith": return typeof(FileLocksmithPage); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCmdNotFound.xaml b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCmdNotFound.xaml new file mode 100644 index 0000000000..d08cea71aa --- /dev/null +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCmdNotFound.xaml @@ -0,0 +1,32 @@ + + + + + + + + + + + +