diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 319a31be1f..8fa1d0ed37 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -848,11 +848,15 @@ hinstance hitinfo HIWORD hk -HKCR +HKCC HKCU +HKCR +HKE hkey HKL HKLM +HKPD +HKU HLOCAL HLSL hmenu @@ -1161,6 +1165,7 @@ Kybd LAlt lambson lamotile +langword Lastdevice LASTEXITCODE laute @@ -1197,6 +1202,7 @@ linkid Linkmenu linq LINQTo +Linux listbox listview llkhf @@ -1765,6 +1771,7 @@ REFCLSID refcount REFIID REGCLS +regedit regex REGISTERCLASSFAILED registrypath @@ -2092,6 +2099,7 @@ SYSCOLORCHANGE SYSCOMMAND SYSDEADCHAR SYSICONINDEX +sysinfo SYSKEY syskeydown syskeyup @@ -2516,4 +2524,4 @@ zoneset ZONESETCHANGE Zoneszonabletester Zoomusingmagnifier -zzz +zzz \ No newline at end of file diff --git a/.github/actions/spell-check/patterns.txt b/.github/actions/spell-check/patterns.txt index e5192cd0c3..893dd915cc 100644 --- a/.github/actions/spell-check/patterns.txt +++ b/.github/actions/spell-check/patterns.txt @@ -32,3 +32,16 @@ TestCase\("[^"]+" \\testapp \\tests \\tools + +# plugin.json +^ "ID": "[0-9A-F]{32}",$ + +# UnitTests +\[DataRow\(.*\)\] + +# Id info inside markdown file (registry.md) +^\|\s+ID\s+\|\s*\`[0-9A-F]{32}\` + +# marker for ignoring a comment to the end of the line +^.*/\* #no-spell-check-line \*/.*$ +// #no-spell-check.*$ \ No newline at end of file diff --git a/.pipelines/pipeline.user.windows.yml b/.pipelines/pipeline.user.windows.yml index 883ec2d7f3..1e00f2bb48 100644 --- a/.pipelines/pipeline.user.windows.yml +++ b/.pipelines/pipeline.user.windows.yml @@ -118,6 +118,8 @@ build: - 'modules\launcher\Plugins\Microsoft.Plugin.Program\Wox.Infrastructure.dll' - 'modules\launcher\Plugins\Microsoft.Plugin.Program\Wox.Plugin.dll' - 'modules\launcher\Plugins\Microsoft.Plugin.Program\Telemetry.dll' + - 'modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.Registry\Microsoft.PowerToys.Run.Plugin.Registry.dll' + - 'modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.Registry\ManagedTelemetry.dll' - 'modules\launcher\Plugins\Microsoft.Plugin.Shell\Microsoft.Plugin.Shell.dll' - 'modules\launcher\Plugins\Microsoft.Plugin.Shell\Wox.Infrastructure.dll' - 'modules\launcher\Plugins\Microsoft.Plugin.Shell\Wox.Plugin.dll' diff --git a/PowerToys.sln b/PowerToys.sln index cca19d731f..c31302780d 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -305,6 +305,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Telemetry", "Telemetry", "{ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Common.UI", "src\common\Microsoft.PowerToys.Common.UI\Microsoft.PowerToys.Common.UI.csproj", "{C3A17DCA-217B-462C-BB0C-BE086AF80081}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.Registry", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.Registry\Microsoft.PowerToys.Run.Plugin.Registry.csproj", "{4BABF3FE-3451-42FD-873F-3C332E18DCEF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.Registry.UnitTests", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.Registry.UnitTest\Microsoft.PowerToys.Run.Plugin.Registry.UnitTests.csproj", "{0648DF05-5DDA-4BE1-B5F2-584926EBDB65}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -619,6 +623,14 @@ Global {C3A17DCA-217B-462C-BB0C-BE086AF80081}.Debug|x64.Build.0 = Debug|x64 {C3A17DCA-217B-462C-BB0C-BE086AF80081}.Release|x64.ActiveCfg = Release|x64 {C3A17DCA-217B-462C-BB0C-BE086AF80081}.Release|x64.Build.0 = Release|x64 + {4BABF3FE-3451-42FD-873F-3C332E18DCEF}.Debug|x64.ActiveCfg = Debug|x64 + {4BABF3FE-3451-42FD-873F-3C332E18DCEF}.Debug|x64.Build.0 = Debug|x64 + {4BABF3FE-3451-42FD-873F-3C332E18DCEF}.Release|x64.ActiveCfg = Release|x64 + {4BABF3FE-3451-42FD-873F-3C332E18DCEF}.Release|x64.Build.0 = Release|x64 + {0648DF05-5DDA-4BE1-B5F2-584926EBDB65}.Debug|x64.ActiveCfg = Debug|x64 + {0648DF05-5DDA-4BE1-B5F2-584926EBDB65}.Debug|x64.Build.0 = Debug|x64 + {0648DF05-5DDA-4BE1-B5F2-584926EBDB65}.Release|x64.ActiveCfg = Release|x64 + {0648DF05-5DDA-4BE1-B5F2-584926EBDB65}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -711,6 +723,8 @@ Global {B39DC643-4663-475E-B329-03F0C9918D48} = {1AFB6476-670D-4E80-A464-657E01DFF482} {8F62026A-294B-41C6-8839-87463613F216} = {1AFB6476-670D-4E80-A464-657E01DFF482} {C3A17DCA-217B-462C-BB0C-BE086AF80081} = {1AFB6476-670D-4E80-A464-657E01DFF482} + {4BABF3FE-3451-42FD-873F-3C332E18DCEF} = {4AFC9975-2456-4C70-94A4-84073C1CED93} + {0648DF05-5DDA-4BE1-B5F2-584926EBDB65} = {4AFC9975-2456-4C70-94A4-84073C1CED93} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} diff --git a/doc/devdocs/modules/launcher/plugins/registry.md b/doc/devdocs/modules/launcher/plugins/registry.md new file mode 100644 index 0000000000..d74672fb99 --- /dev/null +++ b/doc/devdocs/modules/launcher/plugins/registry.md @@ -0,0 +1,101 @@ +# Registry Plugin + +The registry plugin allows users to search the Windows registry. + +## Special functions (differ from the regular functions) + +* Support full base keys and short base keys (e.g. `HKLM` for `HKEY_LOCALE_MACHINE`). +* Show count of subkeys and count of values in the second result line. +* Search for value names and value data inside a registry key (syntax: `[RegistryKey]\\[ValueName]` and `[RegistryKey]\\[ValueData]`) + +## The Windows Registry + +The registry contains all settings for the Windows operating system and many settings of the installed (Windows only) programs. + +*Note: Linux and macOS program ports typical store the settings in it's own configuration files and not in the Windows registry.* + +For more information about the Windows registry, see [the official documentation](https://docs.microsoft.com/en-us/windows/win32/sysinfo/registry). + +For advanced information about the Windows registry, see [Windows registry information for advanced users](https://docs.microsoft.com/en-us/troubleshoot/windows-server/performance/windows-registry-advanced-users). + +## Score + +The score is currently not set on the results. + +## Important for developers + +### General + +* The assembly name is cached into `_assemblyName` (to avoid to many calls of `Assembly.GetExecutingAssembly()`) + +### Results + +* All results override the visible search result via `QueryTextDisplay` to avoid problems with short registry base keys (e.g. `HKLM`). +* The length of a `Title` and `Subtitle` is automatic truncated, when it is to long. + +## Microsoft.Plugin.Registry project + +### Important plugin values (meta-data) + +| Name | Value | +| --------------- | --------------------------------------------- | +| ActionKeyword | `:` | +| ExecuteFileName | `Microsoft.PowerToys.Run.Plugin.Registry.dll` | +| ID | `303417D927BF4C97BCFFC78A123BE0C8` | + +### Interfaces used by this plugin + +The plugin use only these interfaces (all inside the `Main.cs`): + +* `Wox.Plugin.IPlugin` +* `Wox.Plugin.IContextMenu` +* `Wox.Plugin.IPluginI18n` +* `System.IDisposable` + +### Program files + +| File | Content | +| ------------------------------------ | ------------------------------------------------------------------------ | +| `Classes\RegistryEntry.cs` | Wrapper class for a registry key with a possible exception on access | +| `Constants\KeyName.cs` | Static used short registry key names (to avoid code and string doubling) | +| `Constants\MaxTextLength.cs` | Contain all maximum text lengths (for truncating) | +| `Enumeration\TruncateSide.cs` | Contain the possible truncate sides | +| `Helper\ContextMenuHelper.cs` | All functions to build the context menu (for each result entry) | +| `Helper\QueryHelper.cs` | All functions to analyze the search query | +| `Helper\RegistryHelper.cs` | All functions to search into the Windows registry (via `Win32.Registry`) | +| `Helper\ResultHelper.cs` | All functions to convert internal results into WOX results | +| `Helper\ValueHelper.cs` | All functions to convert values into human readable values | +| `Images\reg.dark.png` | Symbol for the results for the dark theme | +| `Images\reg.light.png` | Symbol for the results for the light theme | +| `Properties\Resources.Designer.resx` | File that contain all translatable keys | +| `Properties\Resources.resx` | File that contain all translatable strings in the neutral language | +| `GlobalSuppressions.cs` | Code suppressions (no real file, linked via *.csproj) | +| `Main.cs` | Main class, the only place that implement the WOX interfaces | +| `plugin.json` | All meta-data for this plugin | +| `StyleCop.json` | Code style (no real file, linked via *.csproj) | + +### Important project values (*.csproj) + +| Name | Value | +| --------------- | ------------------------------------------------------------------------------ | +| TargetFramework | `netcoreapp3.1` (means .NET Core 3.1) | +| LangVersion | `8.0` (mean C# 8.0) | +| Platforms | `x64` | +| Nullable | `true` | +| Output | `..\..\..\..\..\x64\Debug\modules\launcher\Plugins\Microsoft.Plugin.Registry\` | +| RootNamespace | `Microsoft.PowerToys.Run.Plugin.Registry` | +| AssemblyName | `Microsoft.PowerToys.Run.Plugin.Registry` | + +### Project dependencies + +#### Packages + +| Package | Version | +| ------------------------------------------------------------------------------------- | ------- | +| [`Microsoft.CodeAnalysis.FxCopAnalyzers`](https://github.com/dotnet/roslyn-analyzers) | 3.3.0 | +| [`StyleCop.Analyzers`](https://github.com/DotNetAnalyzers/StyleCopAnalyzers) | 1.1.118 | + +#### Projects + +* `Wox.Infrastructure` +* `Wox.Plugin` diff --git a/doc/devdocs/modules/launcher/readme.md b/doc/devdocs/modules/launcher/readme.md index da77c753e8..69af0edc46 100644 --- a/doc/devdocs/modules/launcher/readme.md +++ b/doc/devdocs/modules/launcher/readme.md @@ -9,7 +9,8 @@ - [Folder](/doc/devdocs/modules/launcher/plugins/folder.md) - [Indexer](/doc/devdocs/modules/launcher/plugins/indexer.md) - [Program](/doc/devdocs/modules/launcher/plugins/program.md) + - [Registry](/doc/devdocs/modules/launcher/plugins/registry.md) - [Shell](/doc/devdocs/modules/launcher/plugins/shell.md) - [Sys](/doc/devdocs/modules/launcher/plugins/sys.md) - [Uri](/doc/devdocs/modules/launcher/plugins/uri.md) - - [Window Walker](/doc/devdocs/modules/launcher/plugins/windowwalker.md) \ No newline at end of file + - [Window Walker](/doc/devdocs/modules/launcher/plugins/windowwalker.md) diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry.UnitTest/Constants/KeyNameTest.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry.UnitTest/Constants/KeyNameTest.cs new file mode 100644 index 0000000000..ce2bb3792b --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry.UnitTest/Constants/KeyNameTest.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.PowerToys.Run.Plugin.Registry.Constants; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.PowerToys.Run.Plugin.Registry.UnitTest.Constants +{ + [TestClass] + public sealed class KeyNameTest + { + [TestMethod] + [DataRow("HKEY", KeyName.FirstPart)] + [DataRow("HKEY_", KeyName.FirstPartUnderscore)] + [DataRow("HKCR", KeyName.ClassRootShort)] + [DataRow("HKCC", KeyName.CurrentConfigShort)] + [DataRow("HKCU", KeyName.CurrentUserShort)] + [DataRow("HKLM", KeyName.LocalMachineShort)] + [DataRow("HKPD", KeyName.PerformanceDataShort)] + [DataRow("HKU", KeyName.UsersShort)] + public void TestConstants(string shortName, string baseName) + { + Assert.AreEqual(shortName, baseName); + } + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry.UnitTest/Helper/QueryHelperTest.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry.UnitTest/Helper/QueryHelperTest.cs new file mode 100644 index 0000000000..9a1247907a --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry.UnitTest/Helper/QueryHelperTest.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.PowerToys.Run.Plugin.Registry.Helper; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.PowerToys.Run.Plugin.Registry.UnitTest.Helper +{ + [TestClass] + public sealed class QueryHelperTest + { + [TestMethod] + [DataRow(@"HKLM", false, @"HKLM", "")] + [DataRow(@"HKLM\", false, @"HKLM\", "")] + [DataRow(@"HKLM\\", true, @"HKLM", "")] + [DataRow(@"HKLM\\Test", true, @"HKLM", "Test")] + [DataRow(@"HKLM\Test\\TestTest", true, @"HKLM\Test", "TestTest")] + [DataRow(@"HKLM\Test\\\TestTest", true, @"HKLM\Test", @"\TestTest")] + public void GetQueryPartsTest(string query, bool expectedHasValueName, string expectedQueryKey, string expectedQueryValueName) + { + var hasValueName = QueryHelper.GetQueryParts(query, out var queryKey, out var queryValueName); + + Assert.AreEqual(expectedHasValueName, hasValueName); + Assert.AreEqual(expectedQueryKey, queryKey); + Assert.AreEqual(expectedQueryValueName, queryValueName); + } + + [TestMethod] + [DataRow(@"HKCR\*\OpenWithList", @"HKEY_CLASSES_ROOT\*\OpenWithList")] + [DataRow(@"HKCU\Control Panel\Accessibility", @"HKEY_CURRENT_USER\Control Panel\Accessibility")] + [DataRow(@"HKLM\HARDWARE\UEFI", @"HKEY_LOCAL_MACHINE\HARDWARE\UEFI")] + [DataRow(@"HKU\.DEFAULT\Environment", @"HKEY_USERS\.DEFAULT\Environment")] + [DataRow(@"HKCC\System\CurrentControlSet\Control", @"HKEY_CURRENT_CONFIG\System\CurrentControlSet\Control")] + [DataRow(@"HKPD\???", @"HKEY_PERFORMANCE_DATA\???")] + public void GetShortBaseKeyTest(string registryKeyShort, string registryKeyFull) + { + Assert.AreEqual(registryKeyShort, QueryHelper.GetKeyWithShortBaseKey(registryKeyFull)); + } + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry.UnitTest/Helper/RegistryHelperTest.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry.UnitTest/Helper/RegistryHelperTest.cs new file mode 100644 index 0000000000..8855d3e23e --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry.UnitTest/Helper/RegistryHelperTest.cs @@ -0,0 +1,73 @@ +// 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.Linq; +using Microsoft.PowerToys.Run.Plugin.Registry.Helper; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.PowerToys.Run.Plugin.Registry.UnitTest.Helper +{ + [TestClass] + public sealed class RegistryHelperTest + { + [TestMethod] + [DataRow(@"HKCC\System\CurrentControlSet\Control", "HKEY_CURRENT_CONFIG")] + [DataRow(@"HKCR\*\OpenWithList", "HKEY_CLASSES_ROOT")] + [DataRow(@"HKCU\Control Panel\Accessibility", "HKEY_CURRENT_USER")] + [DataRow(@"HKLM\HARDWARE\UEFI", "HKEY_LOCAL_MACHINE")] + [DataRow(@"HKPD\???", "HKEY_PERFORMANCE_DATA")] + [DataRow(@"HKU\.DEFAULT\Environment", "HKEY_USERS")] + public void GetRegistryBaseKeyTestOnlyOneBaseKey(string query, string expectedBaseKey) + { + var (baseKeyList, _) = RegistryHelper.GetRegistryBaseKey(query); + Assert.IsTrue(baseKeyList.Count() == 1); + Assert.AreEqual(expectedBaseKey, baseKeyList.FirstOrDefault().Name); + } + + [TestMethod] + public void GetRegistryBaseKeyTestMoreThanOneBaseKey() + { + var (baseKeyList, _) = RegistryHelper.GetRegistryBaseKey("HKC\\Control Panel\\Accessibility"); /* #no-spell-check-line */ + + Assert.IsTrue(baseKeyList.Count() > 1); + + var list = baseKeyList.Select(found => found.Name); + Assert.IsTrue(list.Contains("HKEY_CLASSES_ROOT")); + Assert.IsTrue(list.Contains("HKEY_CURRENT_CONFIG")); + Assert.IsTrue(list.Contains("HKEY_CURRENT_USER")); + } + + [TestMethod] + [DataRow(@"HKCR\*\OpenWithList", @"*\OpenWithList")] + [DataRow(@"HKCU\Control Panel\Accessibility", @"Control Panel\Accessibility")] + [DataRow(@"HKLM\HARDWARE\UEFI", @"HARDWARE\UEFI")] + [DataRow(@"HKU\.DEFAULT\Environment", @".DEFAULT\Environment")] + [DataRow(@"HKCC\System\CurrentControlSet\Control", @"System\CurrentControlSet\Control")] + [DataRow(@"HKPD\???", @"???")] + public void GetRegistryBaseKeyTestSubKey(string query, string expectedSubKey) + { + var (_, subKey) = RegistryHelper.GetRegistryBaseKey(query); + Assert.AreEqual(expectedSubKey, subKey); + } + + [TestMethod] + public void GetAllBaseKeysTest() + { + var list = RegistryHelper.GetAllBaseKeys(); + + CollectionAssert.AllItemsAreNotNull((ICollection)list); + CollectionAssert.AllItemsAreUnique((ICollection)list); + + var keys = list.Select(found => found.Key).ToList() as ICollection; + + CollectionAssert.Contains(keys, Win32.Registry.ClassesRoot); + CollectionAssert.Contains(keys, Win32.Registry.CurrentConfig); + CollectionAssert.Contains(keys, Win32.Registry.CurrentUser); + CollectionAssert.Contains(keys, Win32.Registry.LocalMachine); + CollectionAssert.Contains(keys, Win32.Registry.PerformanceData); + CollectionAssert.Contains(keys, Win32.Registry.Users); + } + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry.UnitTest/Helper/ResultHelperTest.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry.UnitTest/Helper/ResultHelperTest.cs new file mode 100644 index 0000000000..16b3c47a5e --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry.UnitTest/Helper/ResultHelperTest.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.PowerToys.Run.Plugin.Registry.Helper; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.PowerToys.Run.Plugin.Registry.UnitTest.Helper +{ + [TestClass] + public sealed class ResultHelperTest + { + [TestMethod] + [DataRow(@"HKEY_CLASSES_ROOT\*\OpenWithList", @"HKEY_CLASSES_ROOT\*\OpenWithList")] + [DataRow(@"HKEY_CURRENT_USER\Control Panel\Accessibility", @"HKEY_CURRENT_USER\Control Panel\Accessibility")] + [DataRow(@"HKEY_LOCAL_MACHINE\HARDWARE\UEFI", @"HKEY_LOCAL_MACHINE\HARDWARE\UEFI")] + [DataRow(@"HKEY_USERS\.DEFAULT\Environment", @"HKEY_USERS\.DEFAULT\Environment")] + [DataRow(@"HKCC\System\CurrentControlSet\Control", @"HKEY_CURRENT_CONFIG\System\CurrentControlSet\Control")] + [DataRow(@"HKEY_PERFORMANCE_DATA\???", @"HKEY_PERFORMANCE_DATA\???")] + [DataRow(@"HKCR\*\shell\Open with VS Code\command", @"HKEY_CLASSES_ROOT\*\shell\Open with VS Code\command")] + [DataRow(@"...ndows\CurrentVersion\Explorer\StartupApproved", @"HKEY_CURRENT_USER\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved")] + [DataRow(@"...p\Upgrade\NetworkDriverBackup\Control\Network", @"HKEY_LOCAL_MACHINE\SYSTEM\Setup\Upgrade\NetworkDriverBackup\Control\Network")] + [DataRow(@"...anel\International\User Profile System Backup", @"HKEY_USERS\.DEFAULT\Control Panel\International\User Profile System Backup")] + [DataRow(@"...stem\CurrentControlSet\Control\Print\Printers", @"HKEY_CURRENT_CONFIG\System\CurrentControlSet\Control\Print\Printers")] + public void GetTruncatedTextTest(string registryKeyShort, string registryKeyFull) + { + Assert.AreEqual(registryKeyShort, ResultHelper.GetTruncatedText(registryKeyFull, 45)); + } + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry.UnitTest/Microsoft.PowerToys.Run.Plugin.Registry.UnitTests.csproj b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry.UnitTest/Microsoft.PowerToys.Run.Plugin.Registry.UnitTests.csproj new file mode 100644 index 0000000000..19a76d6aa8 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry.UnitTest/Microsoft.PowerToys.Run.Plugin.Registry.UnitTests.csproj @@ -0,0 +1,65 @@ + + + + netcoreapp3.1 + x64 + en-US + 8.0 + enable + false + + + + true + DEBUG;TRACE + full + x64 + prompt + MinimumRecommendedRules.ruleset + 4 + false + true + + + + TRACE + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + 4 + true + + + + + + + + + + + GlobalSuppressions.cs + + + StyleCop.json + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Classes/RegistryEntry.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Classes/RegistryEntry.cs new file mode 100644 index 0000000000..691f1dd3b5 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Classes/RegistryEntry.cs @@ -0,0 +1,113 @@ +// 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 Microsoft.PowerToys.Run.Plugin.Registry.Helper; +using Microsoft.Win32; + +namespace Microsoft.PowerToys.Run.Plugin.Registry.Classes +{ + /// + /// A entry of the registry. + /// + internal class RegistryEntry + { + /// + /// Gets the full path to a registry key. + /// + internal string KeyPath { get; } + + /// + /// Gets the registry key for this entry. + /// + internal RegistryKey? Key { get; } + + /// + /// Gets a possible exception that was occurred when try to open this registry key (e.g. ). + /// + internal Exception? Exception { get; } + + /// + /// Gets the name of the current selected registry value. + /// + internal string? ValueName { get; } + + /// + /// Gets the value of the current selected registry value. + /// + internal object? ValueData { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The full path to the registry key for this entry. + /// A exception that was occurred when try to access this registry key. + internal RegistryEntry(string keyPath, Exception exception) + { + KeyPath = keyPath; + Exception = exception; + } + + /// + /// Initializes a new instance of the class. + /// + /// The for this entry. + internal RegistryEntry(RegistryKey key) + { + KeyPath = key.Name; + Key = key; + } + + /// + /// Initializes a new instance of the class. + /// + /// The for this entry. + /// The value name of the current selected registry value. + /// The value of the current selected registry value. + internal RegistryEntry(RegistryKey key, string valueName, object value) + { + KeyPath = key.Name; + Key = key; + ValueName = valueName; + ValueData = value; + } + + /// + /// Return the registry key. + /// + /// A registry key. + internal string GetRegistryKey() + { + return $"{Key?.Name ?? KeyPath}"; + } + + /// + /// Return the value name with the complete registry key. + /// + /// A value name with the complete registry key. + internal string GetValueNameWithKey() + { + return $"{Key?.Name ?? KeyPath}\\\\{ValueName?.ToString() ?? string.Empty}"; + } + + /// + /// Return the value data of a value name inside a registry key. + /// + /// A value data. + internal string GetValueData() + { + if (Key is null) + { + return KeyPath; + } + + if (string.IsNullOrEmpty(ValueName)) + { + return Key.Name; + } + + return ValueHelper.GetValue(Key, ValueName); + } + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Constants/KeyName.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Constants/KeyName.cs new file mode 100644 index 0000000000..1aea08bfc3 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Constants/KeyName.cs @@ -0,0 +1,52 @@ +// 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. + +namespace Microsoft.PowerToys.Run.Plugin.Registry.Constants +{ + /// + /// This class contains names for important registry keys + /// + internal static class KeyName + { + /// + /// The first name part of each base key without the underscore + /// + internal const string FirstPart = "HKEY"; + + /// + /// The first name part of each base key follow by a underscore + /// + internal const string FirstPartUnderscore = "HKEY_"; + + /// + /// The short name for the base key HKEY_CLASSES_ROOT (see ) + /// + internal const string ClassRootShort = "HKCR"; + + /// + /// The short name for the base key HKEY_CURRENT_CONFIG (see ) + /// + internal const string CurrentConfigShort = "HKCC"; + + /// + /// The short name for the base key HKEY_CURRENT_USER (see ) + /// + internal const string CurrentUserShort = "HKCU"; + + /// + /// The short name for the base key HKEY_LOCAL_MACHINE (see ) + /// + internal const string LocalMachineShort = "HKLM"; + + /// + /// The short name for the base key HKEY_PERFORMANCE_DATA (see ) + /// + internal const string PerformanceDataShort = "HKPD"; + + /// + /// The short name for the base key HKEY_USERS (see ) + /// + internal const string UsersShort = "HKU"; + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Constants/MaxTextLength.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Constants/MaxTextLength.cs new file mode 100644 index 0000000000..280460de9a --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Constants/MaxTextLength.cs @@ -0,0 +1,32 @@ +// 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. + +namespace Microsoft.PowerToys.Run.Plugin.Registry.Constants +{ + /// + /// This class contain all maximum text length. + /// + public static class MaxTextLength + { + /// + /// The maximum length for the title text length with two context menu symbols on the right. + /// + internal const int MaximumTitleLengthWithTwoSymbols = 44; + + /// + /// The maximum length for the title text length with three context menu symbols on the right. + /// + internal const int MaximumTitleLengthWithThreeSymbols = 40; + + /// + /// The maximum length for the sub-title text length with two context menu symbols on the right. + /// + internal const int MaximumSubTitleLengthWithTwoSymbols = 85; + + /// + /// The maximum length for the sub-title text length with three context menu symbols on the right. + /// + internal const int MaximumSubTitleLengthWithThreeSymbols = 78; + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Enumerations/TruncateSide.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Enumerations/TruncateSide.cs new file mode 100644 index 0000000000..10be1f2da6 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Enumerations/TruncateSide.cs @@ -0,0 +1,22 @@ +// 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. + +namespace Microsoft.PowerToys.Run.Plugin.Registry.Enumerations +{ + /// + /// The truncate side for a to long text + /// + internal enum TruncateSide + { + /// + /// Truncate a text only from the right side + /// + OnlyFromLeft, + + /// + /// Truncate a text only from the left side + /// + OnlyFromRight, + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Helper/ContextMenuHelper.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Helper/ContextMenuHelper.cs new file mode 100644 index 0000000000..9572d6b677 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Helper/ContextMenuHelper.cs @@ -0,0 +1,140 @@ +// 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.Collections.Generic; +using System.Windows; +using System.Windows.Input; +using Microsoft.PowerToys.Run.Plugin.Registry.Classes; +using Microsoft.PowerToys.Run.Plugin.Registry.Properties; +using Wox.Plugin; +using Wox.Plugin.Logger; + +namespace Microsoft.PowerToys.Run.Plugin.Registry.Helper +{ + /// + /// Helper class to easier work with context menu entries + /// + internal static class ContextMenuHelper + { + /// + /// Return a list with all context menu entries for the given + /// Symbols taken from + /// + /// The result for the context menu entires + /// The name of the this assembly + /// A list with context menu entries + internal static List GetContextMenu(Result result, string assemblyName) + { + if (!(result?.ContextData is RegistryEntry entry)) + { + return new List(0); + } + + var list = new List(); + + if (string.IsNullOrEmpty(entry.ValueName)) + { + list.Add(new ContextMenuResult + { + AcceleratorKey = Key.C, + AcceleratorModifiers = ModifierKeys.Control, + Action = _ => TryToCopyToClipBoard(entry.GetRegistryKey()), + FontFamily = "Segoe MDL2 Assets", + Glyph = "\xE8C8", // E8C8 => Symbol: Copy + PluginName = assemblyName, + Title = $"{Resources.CopyKeyNamePath} (Ctrl+C)", + }); + } + else + { + list.Add(new ContextMenuResult + { + AcceleratorKey = Key.C, + AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift, + Action = _ => TryToCopyToClipBoard(entry.GetValueData()), + FontFamily = "Segoe MDL2 Assets", + Glyph = "\xF413", // F413 => Symbol: CopyTo + PluginName = assemblyName, + Title = $"{Resources.CopyValueData} (Ctrl+Shift+C)", + }); + + list.Add(new ContextMenuResult + { + AcceleratorKey = Key.C, + AcceleratorModifiers = ModifierKeys.Control, + Action = _ => TryToCopyToClipBoard(entry.GetValueNameWithKey()), + FontFamily = "Segoe MDL2 Assets", + Glyph = "\xE8C8", // E8C8 => Symbol: Copy + PluginName = assemblyName, + Title = $"{Resources.CopyValueName} (Ctrl+C)", + }); + } + + list.Add(new ContextMenuResult + { + AcceleratorKey = Key.Enter, + Action = _ => TryToOpenInRegistryEditor(entry), + FontFamily = "Segoe MDL2 Assets", + Glyph = "\xE8A7", // E8A7 => Symbol: OpenInNewWindow + PluginName = assemblyName, + Title = $"{Resources.OpenKeyInRegistryEditor} (Enter)", + }); + + return list; + } + + #pragma warning disable CA1031 // Do not catch general exception types + + /// + /// Open the Windows registry editor and jump to registry key inside the given key (inside the + /// + /// The to jump in + /// if the registry editor was successful open, otherwise + internal static bool TryToOpenInRegistryEditor(in RegistryEntry entry) + { + try + { + RegistryHelper.OpenRegistryKey(entry.Key?.Name ?? entry.KeyPath); + return true; + } + catch (System.ComponentModel.Win32Exception) + { + MessageBox.Show( + Resources.OpenInRegistryEditorAccessExceptionText, + Resources.OpenInRegistryEditorAccessExceptionTitle, + MessageBoxButton.OK, + MessageBoxImage.Error); + return false; + } + catch (Exception exception) + { + Log.Exception("Error on opening Windows registry editor", exception, typeof(Main)); + return false; + } + } + + /// + /// Copy the given text to the clipboard + /// + /// The text to copy to the clipboard + /// The text successful copy to the clipboard, otherwise + private static bool TryToCopyToClipBoard(in string text) + { + try + { + Clipboard.Clear(); + Clipboard.SetText(text); + return true; + } + catch (Exception exception) + { + Log.Exception("Can't copy to clipboard", exception, typeof(Main)); + return false; + } + } + + #pragma warning restore CA1031 // Do not catch general exception types + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Helper/QueryHelper.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Helper/QueryHelper.cs new file mode 100644 index 0000000000..3e37f3b193 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Helper/QueryHelper.cs @@ -0,0 +1,98 @@ +// 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.Collections.Generic; +using System.Linq; +using Microsoft.PowerToys.Run.Plugin.Registry.Constants; + +namespace Microsoft.PowerToys.Run.Plugin.Registry.Helper +{ + /// + /// Helper class to easier work with queries + /// + internal static class QueryHelper + { + /// + /// The character to distinguish if the search query contain multiple parts (typically "\\") + /// + internal const string QuerySplitCharacter = "\\\\"; + + /// + /// A list that contain short names of all registry base keys + /// + private static readonly IReadOnlyDictionary _shortBaseKeys = new Dictionary(6) + { + { Win32.Registry.ClassesRoot.Name, KeyName.ClassRootShort }, + { Win32.Registry.CurrentConfig.Name, KeyName.CurrentConfigShort }, + { Win32.Registry.CurrentUser.Name, KeyName.CurrentUserShort }, + { Win32.Registry.LocalMachine.Name, KeyName.LocalMachineShort }, + { Win32.Registry.PerformanceData.Name, KeyName.PerformanceDataShort }, + { Win32.Registry.Users.Name, KeyName.UsersShort }, + }; + + /// + /// Return the parts of a given query + /// + /// The query that could contain parts + /// The key part of the query + /// The value name part of the query + /// when the query search for a key and a value name, otherwise + internal static bool GetQueryParts(in string query, out string queryKey, out string queryValueName) + { + if (!query.Contains(QuerySplitCharacter, StringComparison.InvariantCultureIgnoreCase)) + { + queryKey = query; + queryValueName = string.Empty; + return false; + } + + var querySplit = query.Split(QuerySplitCharacter); + + queryKey = querySplit.FirstOrDefault(); + queryValueName = querySplit.LastOrDefault(); + return true; + } + + /// + /// Return a registry key with a long base key + /// + /// A registry key with a short base key + /// A registry key with a long base key + internal static string GetKeyWithLongBaseKey(in string registryKey) + { + foreach (var shortName in _shortBaseKeys) + { + if (!registryKey.StartsWith(shortName.Value, StringComparison.InvariantCultureIgnoreCase)) + { + continue; + } + + return registryKey.Replace(shortName.Value, shortName.Key, StringComparison.InvariantCultureIgnoreCase); + } + + return registryKey; + } + + /// + /// Return a registry key with a short base key (useful to reduce the text length of a registry key) + /// + /// A registry key with a full base key + /// A registry key with a short base key + internal static string GetKeyWithShortBaseKey(in string registryKey) + { + foreach (var shortName in _shortBaseKeys) + { + if (!registryKey.StartsWith(shortName.Key, StringComparison.InvariantCultureIgnoreCase)) + { + continue; + } + + return registryKey.Replace(shortName.Key, shortName.Value, StringComparison.InvariantCultureIgnoreCase); + } + + return registryKey; + } + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Helper/RegistryHelper.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Helper/RegistryHelper.cs new file mode 100644 index 0000000000..5354ef835c --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Helper/RegistryHelper.cs @@ -0,0 +1,239 @@ +// 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.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Linq; +using Microsoft.PowerToys.Run.Plugin.Registry.Classes; +using Microsoft.PowerToys.Run.Plugin.Registry.Constants; +using Microsoft.PowerToys.Run.Plugin.Registry.Properties; +using Microsoft.Win32; + +namespace Microsoft.PowerToys.Run.Plugin.Registry.Helper +{ +#pragma warning disable CA1031 // Do not catch general exception types + + /// + /// Helper class to easier work with the registry + /// + internal static class RegistryHelper + { + /// + /// A list that contain all registry base keys in a long/full version and in a short version (e.g HKLM = HKEY_LOCAL_MACHINE) + /// + private static readonly IReadOnlyDictionary _baseKeys = new Dictionary(12) + { + { KeyName.ClassRootShort, Win32.Registry.ClassesRoot }, + { Win32.Registry.ClassesRoot.Name, Win32.Registry.ClassesRoot }, + { KeyName.CurrentConfigShort, Win32.Registry.CurrentConfig }, + { Win32.Registry.CurrentConfig.Name, Win32.Registry.CurrentConfig }, + { KeyName.CurrentUserShort, Win32.Registry.CurrentUser }, + { Win32.Registry.CurrentUser.Name, Win32.Registry.CurrentUser }, + { KeyName.LocalMachineShort, Win32.Registry.LocalMachine }, + { Win32.Registry.LocalMachine.Name, Win32.Registry.LocalMachine }, + { KeyName.PerformanceDataShort, Win32.Registry.PerformanceData }, + { Win32.Registry.PerformanceData.Name, Win32.Registry.PerformanceData }, + { KeyName.UsersShort, Win32.Registry.Users }, + { Win32.Registry.Users.Name, Win32.Registry.Users }, + }; + + /// + /// Try to find registry base keys based on the given query + /// + /// The query to search + /// A combination of a list of base and the sub keys + internal static (IEnumerable? baseKey, string subKey) GetRegistryBaseKey(in string query) + { + if (string.IsNullOrWhiteSpace(query)) + { + return (null, string.Empty); + } + + var baseKey = query.Split('\\').FirstOrDefault(); + var subKey = query.Replace(baseKey, string.Empty, StringComparison.InvariantCultureIgnoreCase).TrimStart('\\'); + + var baseKeyResult = _baseKeys + .Where(found => found.Key.StartsWith(baseKey, StringComparison.InvariantCultureIgnoreCase)) + .Select(found => found.Value) + .Distinct(); + + return (baseKeyResult, subKey); + } + + /// + /// Return a list of all registry base key + /// + /// A list with all registry base keys + internal static ICollection GetAllBaseKeys() + { + return new Collection + { + new RegistryEntry(Win32.Registry.ClassesRoot), + new RegistryEntry(Win32.Registry.CurrentConfig), + new RegistryEntry(Win32.Registry.CurrentUser), + new RegistryEntry(Win32.Registry.LocalMachine), + new RegistryEntry(Win32.Registry.PerformanceData), + new RegistryEntry(Win32.Registry.Users), + }; + } + + /// + /// Search for the given sub-key path in the given registry base key + /// + /// The base + /// The path of the registry sub-key + /// A list with all found registry keys + internal static ICollection SearchForSubKey(in RegistryKey baseKey, in string subKeyPath) + { + if (string.IsNullOrEmpty(subKeyPath)) + { + return FindSubKey(baseKey, string.Empty); + } + + var subKeysNames = subKeyPath.Split('\\'); + var index = 0; + var subKey = baseKey; + + ICollection result; + + do + { + result = FindSubKey(subKey, subKeysNames.ElementAtOrDefault(index)); + + if (result.Count == 0) + { + return FindSubKey(subKey, string.Empty); + } + + if (result.Count == 1 && index < subKeysNames.Length) + { + subKey = result.First().Key; + } + + if (result.Count > 1 || subKey == null) + { + break; + } + + index++; + } + while (index < subKeysNames.Length); + + return result; + } + + /// + /// Return a human readable summary of a given + /// + /// The for the summary + /// A human readable summary + internal static string GetSummary(in RegistryKey key) + { + return $"{Resources.SubKeys} {key.SubKeyCount} - {Resources.Values} {key.ValueCount}"; + } + + /// + /// Open a given registry key in the registry editor + /// + /// The registry key to open + internal static void OpenRegistryKey(in string fullKey) + { + // it's impossible to directly open a key via command-line option, so we must override the last remember key + Win32.Registry.SetValue(@"HKEY_Current_User\Software\Microsoft\Windows\CurrentVersion\Applets\Regedit", "LastKey", fullKey); + + var processStartInfo = new ProcessStartInfo + { + // -m => allow multi-instance (hidden start option) + Arguments = "-m", + + FileName = "regedit.exe", + + // Start as administrator + Verb = "runas", + + // Start as administrator will not work without this + UseShellExecute = true, + }; + + Process.Start(processStartInfo); + } + + /// + /// Try to find the given registry sub-key in the given registry parent-key + /// + /// The parent-key, also the root to start the search + /// The sub-key to find + /// A list with all found registry sub-keys + private static ICollection FindSubKey(in RegistryKey parentKey, in string searchSubKey) + { + var list = new Collection(); + + try + { + foreach (var subKey in parentKey.GetSubKeyNames().OrderBy(found => found)) + { + if (!subKey.StartsWith(searchSubKey, StringComparison.InvariantCultureIgnoreCase)) + { + continue; + } + + if (string.Equals(subKey, searchSubKey, StringComparison.InvariantCultureIgnoreCase)) + { + list.Add(new RegistryEntry(parentKey.OpenSubKey(subKey, RegistryKeyPermissionCheck.ReadSubTree))); + return list; + } + + try + { + list.Add(new RegistryEntry(parentKey.OpenSubKey(subKey, RegistryKeyPermissionCheck.ReadSubTree))); + } + catch (Exception exception) + { + list.Add(new RegistryEntry($"{parentKey.Name}\\{subKey}", exception)); + } + } + } + catch (Exception ex) + { + list.Add(new RegistryEntry(parentKey.Name, ex)); + } + + return list; + } + + /// + /// Return a list with a registry sub-keys of the given registry parent-key + /// + /// The registry parent-key + /// (optional) The maximum count of the results + /// A list with all found registry sub-keys + private static ICollection GetAllSubKeys(in RegistryKey parentKey, in int maxCount = 50) + { + var list = new Collection(); + + try + { + foreach (var subKey in parentKey.GetSubKeyNames()) + { + if (list.Count >= maxCount) + { + break; + } + + list.Add(new RegistryEntry(parentKey)); + } + } + catch (Exception exception) + { + list.Add(new RegistryEntry(parentKey.Name, exception)); + } + + return list; + } + } + + #pragma warning restore CA1031 // Do not catch general exception types +} diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Helper/ResultHelper.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Helper/ResultHelper.cs new file mode 100644 index 0000000000..b6f1b050f8 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Helper/ResultHelper.cs @@ -0,0 +1,219 @@ +// 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.Collections.Generic; +using System.Linq; +using Microsoft.PowerToys.Run.Plugin.Registry.Classes; +using Microsoft.PowerToys.Run.Plugin.Registry.Constants; +using Microsoft.PowerToys.Run.Plugin.Registry.Enumerations; +using Microsoft.PowerToys.Run.Plugin.Registry.Properties; +using Microsoft.Win32; +using Wox.Plugin; + +namespace Microsoft.PowerToys.Run.Plugin.Registry.Helper +{ + /// + /// Helper class to easier work with results + /// + internal static class ResultHelper + { + #pragma warning disable CA1031 // Do not catch general exception types + + /// + /// Return a list with s, based on the given list + /// + /// The original result list to convert + /// The path to the icon of each entry + /// A list with + internal static List GetResultList(in IEnumerable list, in string iconPath) + { + var resultList = new List(); + + foreach (var entry in list) + { + var result = new Result + { + IcoPath = iconPath, + }; + + if (entry.Exception is null && !(entry.Key is null)) + { + // when key contains keys or fields + result.QueryTextDisplay = entry.Key.Name; + result.SubTitle = RegistryHelper.GetSummary(entry.Key); + result.Title = GetTruncatedText(entry.Key.Name, MaxTextLength.MaximumTitleLengthWithTwoSymbols); + } + else if (entry.Key is null && !(entry.Exception is null)) + { + // on error (e.g access denied) + result.QueryTextDisplay = entry.KeyPath; + result.SubTitle = GetTruncatedText(entry.Exception.Message, MaxTextLength.MaximumSubTitleLengthWithTwoSymbols, TruncateSide.OnlyFromRight); + result.Title = GetTruncatedText(entry.KeyPath, MaxTextLength.MaximumTitleLengthWithTwoSymbols); + } + else + { + result.QueryTextDisplay = entry.KeyPath; + result.Title = GetTruncatedText(entry.KeyPath, MaxTextLength.MaximumTitleLengthWithTwoSymbols); + } + + result.Action = (_) => ContextMenuHelper.TryToOpenInRegistryEditor(entry); + result.ContextData = entry; + result.ToolTipData = new ToolTipData(Resources.RegistryKey, $"{Resources.KeyName}\t{result.Title}"); + + resultList.Add(result); + } + + return resultList; + } + + /// + /// Return a list with s, based on the given + /// + /// The that should contain entries for the list + /// The path to the icon of each entry + /// (optional) When not filter the list for the given value name and value + /// A list with + internal static List GetValuesFromKey(in RegistryKey? key, in string iconPath, string searchValue = "") + { + if (key is null) + { + return new List(0); + } + + ICollection> valueList = new List>(key.ValueCount); + + var resultList = new List(); + + try + { + var valueNames = key.GetValueNames(); + + try + { + foreach (var valueName in valueNames) + { + valueList.Add(KeyValuePair.Create(valueName, key.GetValue(valueName))); + } + } + catch (Exception valueException) + { + var registryEntry = new RegistryEntry(key.Name, valueException); + + resultList.Add(new Result + { + ContextData = registryEntry, + IcoPath = iconPath, + SubTitle = GetTruncatedText(valueException.Message, MaxTextLength.MaximumSubTitleLengthWithThreeSymbols, TruncateSide.OnlyFromRight), + Title = GetTruncatedText(key.Name, MaxTextLength.MaximumTitleLengthWithThreeSymbols), + ToolTipData = new ToolTipData(valueException.Message, valueException.ToString()), + Action = (_) => ContextMenuHelper.TryToOpenInRegistryEditor(registryEntry), + QueryTextDisplay = key.Name, + }); + } + + if (!string.IsNullOrEmpty(searchValue)) + { + var filteredValueName = valueList.Where(found => found.Key.Contains(searchValue, StringComparison.InvariantCultureIgnoreCase)); + var filteredValueList = valueList.Where(found => found.Value.ToString()?.Contains(searchValue, StringComparison.InvariantCultureIgnoreCase) ?? false); + + valueList = filteredValueName.Concat(filteredValueList).Distinct().ToList(); + } + + foreach (var valueEntry in valueList.OrderBy(found => found.Key)) + { + var valueName = valueEntry.Key; + if (string.IsNullOrEmpty(valueName)) + { + valueName = "(Default)"; + } + + var registryEntry = new RegistryEntry(key, valueEntry.Key, valueEntry.Value); + + resultList.Add(new Result + { + ContextData = registryEntry, + IcoPath = iconPath, + SubTitle = GetTruncatedText(GetSubTileForRegistryValue(key, valueEntry), MaxTextLength.MaximumSubTitleLengthWithThreeSymbols, TruncateSide.OnlyFromRight), + Title = GetTruncatedText(valueName, MaxTextLength.MaximumTitleLengthWithThreeSymbols), + ToolTipData = new ToolTipData(Resources.RegistryValue, GetToolTipTextForRegistryValue(key, valueEntry)), + Action = (_) => ContextMenuHelper.TryToOpenInRegistryEditor(registryEntry), + + // Avoid user handling interrupt when move up/down inside the results of a registry key + QueryTextDisplay = $"{key.Name}{QueryHelper.QuerySplitCharacter}", + }); + } + } + catch (Exception exception) + { + var registryEntry = new RegistryEntry(key.Name, exception); + + resultList.Add(new Result + { + ContextData = registryEntry, + IcoPath = iconPath, + SubTitle = GetTruncatedText(exception.Message, MaxTextLength.MaximumSubTitleLengthWithThreeSymbols, TruncateSide.OnlyFromRight), + Title = GetTruncatedText(key.Name, MaxTextLength.MaximumTitleLengthWithThreeSymbols), + ToolTipData = new ToolTipData(exception.Message, exception.ToString()), + Action = (_) => ContextMenuHelper.TryToOpenInRegistryEditor(registryEntry), + QueryTextDisplay = key.Name, + }); + } + + return resultList; + } + +#pragma warning restore CA1031 // Do not catch general exception types + + /// + /// Return a truncated name + /// + /// The text to truncate + /// The maximum length of the text + /// (optional) The side of the truncate + /// A truncated text with a maximum length + internal static string GetTruncatedText(string text, in int maxLength, TruncateSide truncateSide = TruncateSide.OnlyFromLeft) + { + if (truncateSide == TruncateSide.OnlyFromLeft) + { + if (text.Length > maxLength) + { + text = QueryHelper.GetKeyWithShortBaseKey(text); + } + + return text.Length > maxLength ? $"...{text[^maxLength..]}" : text; + } + else + { + return text.Length > maxLength ? $"{text[0..maxLength]}..." : text; + } + } + + /// + /// Return the tool-tip text for a registry value + /// + /// The registry key for the tool-tip + /// The value name and value of the registry value + /// A tool-tip text + private static string GetToolTipTextForRegistryValue(RegistryKey key, KeyValuePair valueEntry) + { + return $"{Resources.KeyName}\t{key.Name}{Environment.NewLine}" + + $"{Resources.Name}\t{valueEntry.Key}{Environment.NewLine}" + + $"{Resources.Type}\t{ValueHelper.GetType(key, valueEntry.Key)}{Environment.NewLine}" + + $"{Resources.Value}\t{ValueHelper.GetValue(key, valueEntry.Key)}"; + } + + /// + /// Return the sub-title text for a registry value + /// + /// The registry key for the sub-title + /// The value name and value of the registry value + /// A sub-title text + private static string GetSubTileForRegistryValue(RegistryKey key, KeyValuePair valueEntry) + { + return $"{Resources.Type} {ValueHelper.GetType(key, valueEntry.Key)}" + + $" - {Resources.Value} {ValueHelper.GetValue(key, valueEntry.Key, 50)}"; + } + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Helper/ValueHelper.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Helper/ValueHelper.cs new file mode 100644 index 0000000000..d3bf3a9790 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Helper/ValueHelper.cs @@ -0,0 +1,63 @@ +// 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.Linq; +using Microsoft.PowerToys.Run.Plugin.Registry.Properties; +using Microsoft.Win32; + +namespace Microsoft.PowerToys.Run.Plugin.Registry.Helper +{ + /// + /// Helper class to easier work with values of a + /// + internal static class ValueHelper + { + /// + /// Return a human readable value data, of the given value name inside the given + /// + /// The that should contain the value name. + /// The name of the value. + /// The maximum length for the human readable value. + /// A human readable value data. + internal static string GetValue(in RegistryKey key, in string valueName, int maxLength = int.MaxValue) + { + var unformattedValue = key.GetValue(valueName); + + var valueData = key.GetValueKind(valueName) switch + { + RegistryValueKind.DWord => $"0x{unformattedValue:X8} ({(uint)(int)unformattedValue})", + RegistryValueKind.QWord => $"0x{unformattedValue:X16} ({(ulong)(long)unformattedValue})", + RegistryValueKind.Binary => (unformattedValue as byte[]).Aggregate(string.Empty, (current, singleByte) => $"{current} {singleByte:X2}"), + _ => $"{unformattedValue}", + }; + + return valueData.Length > maxLength + ? $"{valueData.Substring(0, maxLength)}..." + : valueData; + } + + /// + /// Return the registry type name of a given value name inside a given + /// + /// The that should contain the value name + /// The name of the value + /// A registry type name + internal static object GetType(RegistryKey key, string valueName) + { + return key.GetValueKind(valueName) switch + { + RegistryValueKind.None => Resources.RegistryValueKindNone, + RegistryValueKind.Unknown => Resources.RegistryValueKindUnknown, + RegistryValueKind.String => "REG_SZ", + RegistryValueKind.ExpandString => "REG_EXPAND_SZ", + RegistryValueKind.MultiString => "REG_MULTI_SZ", + RegistryValueKind.Binary => "REG_BINARY", + RegistryValueKind.DWord => "REG_DWORD", + RegistryValueKind.QWord => "REG_QWORD", + _ => throw new ArgumentOutOfRangeException(nameof(valueName)), + }; + } + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Images/reg.dark.png b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Images/reg.dark.png new file mode 100644 index 0000000000..562b3c1887 Binary files /dev/null and b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Images/reg.dark.png differ diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Images/reg.light.png b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Images/reg.light.png new file mode 100644 index 0000000000..1a59d7e23e Binary files /dev/null and b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Images/reg.light.png differ diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Main.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Main.cs new file mode 100644 index 0000000000..7a1310d55d --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Main.cs @@ -0,0 +1,173 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using ManagedCommon; +using Microsoft.PowerToys.Run.Plugin.Registry.Classes; +using Microsoft.PowerToys.Run.Plugin.Registry.Helper; +using Microsoft.PowerToys.Run.Plugin.Registry.Properties; +using Wox.Plugin; + +[assembly: InternalsVisibleTo("Microsoft.PowerToys.Run.Plugin.Registry.UnitTests")] + +namespace Microsoft.PowerToys.Run.Plugin.Registry +{ + /// + /// Main class of this plugin that implement all used interfaces + /// + public class Main : IPlugin, IContextMenu, IPluginI18n, IDisposable + { + /// + /// The name of this assembly + /// + private readonly string _assemblyName; + + /// + /// The initial context for this plugin (contains API and meta-data) + /// + private PluginInitContext? _context; + + /// + /// The path to the icon for each result + /// + private string _defaultIconPath; + + /// + /// Indicate that the plugin is disposed + /// + private bool _disposed; + + /// + /// Initializes a new instance of the class. + /// + public Main() + { + _assemblyName = Assembly.GetExecutingAssembly().GetName().Name ?? GetTranslatedPluginTitle(); + _defaultIconPath = "Images/reg.light.png"; + } + + /// + /// Initialize the plugin with the given + /// + /// The for this plugin + public void Init(PluginInitContext context) + { + _context = context ?? throw new ArgumentNullException(nameof(context)); + _context.API.ThemeChanged += OnThemeChanged; + UpdateIconPath(_context.API.GetCurrentTheme()); + } + + /// + /// Return a filtered list, based on the given query + /// + /// The query to filter the list + /// A filtered list, can be empty when nothing was found + public List Query(Query query) + { + if (query?.Search is null) + { + return new List(0); + } + + var searchForValueName = QueryHelper.GetQueryParts(query.Search, out var queryKey, out var queryValueName); + + var (baseKeyList, subKey) = RegistryHelper.GetRegistryBaseKey(queryKey); + if (baseKeyList is null) + { + // no base key found + return ResultHelper.GetResultList(RegistryHelper.GetAllBaseKeys(), _defaultIconPath); + } + else if (baseKeyList.Count() > 1) + { + // more than one base key was found -> show results + return ResultHelper.GetResultList(baseKeyList.Select(found => new RegistryEntry(found)), _defaultIconPath); + } + + // only one base key was found -> start search for the sub-key + var list = RegistryHelper.SearchForSubKey(baseKeyList.First(), subKey); + + // when only one sub-key was found and a user search for values ("\\") + // show the filtered list of values of one sub-key + if (searchForValueName && list.Count == 1) + { + return ResultHelper.GetValuesFromKey(list.First().Key, _defaultIconPath, queryValueName); + } + + return ResultHelper.GetResultList(list, _defaultIconPath); + } + + /// + /// Return a list context menu entries for a given (shown at the right side of the result) + /// + /// The for the list with context menu entries + /// A list context menu entries + public List LoadContextMenus(Result selectedResult) + { + return ContextMenuHelper.GetContextMenu(selectedResult, _assemblyName); + } + + /// + /// Change all theme-based elements (typical called when the plugin theme has changed) + /// + /// The old + /// The new + private void OnThemeChanged(Theme oldtheme, Theme newTheme) + { + UpdateIconPath(newTheme); + } + + /// + /// Update all icons (typical called when the plugin theme has changed) + /// + /// The new for the icons + private void UpdateIconPath(Theme theme) + { + _defaultIconPath = theme == Theme.Light || theme == Theme.HighContrastWhite + ? "Images/reg.light.png" + : "Images/reg.dark.png"; + } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Wrapper method for that dispose additional objects and events form the plugin itself + /// + /// Indicate that the plugin is disposed + protected virtual void Dispose(bool disposing) + { + if (_disposed || !disposing) + { + return; + } + + if (!(_context is null)) + { + _context.API.ThemeChanged -= OnThemeChanged; + } + + _disposed = true; + } + + /// + public string GetTranslatedPluginTitle() + { + return Resources.PluginTitle; + } + + /// + public string GetTranslatedPluginDescription() + { + return Resources.PluginDescription; + } + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Microsoft.PowerToys.Run.Plugin.Registry.csproj b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Microsoft.PowerToys.Run.Plugin.Registry.csproj new file mode 100644 index 0000000000..cddfe836c6 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Microsoft.PowerToys.Run.Plugin.Registry.csproj @@ -0,0 +1,105 @@ + + + + netcoreapp3.1 + Microsoft.PowerToys.Run.Plugin.Registry + Microsoft.PowerToys.Run.Plugin.Registry + false + false + x64 + en-US + 8.0 + enable + + + + true + ..\..\..\..\..\x64\Debug\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.Registry\ + DEBUG;TRACE + full + x64 + prompt + MinimumRecommendedRules.ruleset + 4 + false + true + + + + ..\..\..\..\..\x64\Release\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.Registry\ + TRACE + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + 4 + true + + + + + + + + + + + GlobalSuppressions.cs + + + StyleCop.json + + + + + + false + + + false + + + + + + PreserveNewest + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Properties/Resources.Designer.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..60f6e5aa33 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Properties/Resources.Designer.cs @@ -0,0 +1,225 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.PowerToys.Run.Plugin.Registry.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.PowerToys.Run.Plugin.Registry.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Copy key name (path). + /// + internal static string CopyKeyNamePath { + get { + return ResourceManager.GetString("CopyKeyNamePath", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Copy value data. + /// + internal static string CopyValueData { + get { + return ResourceManager.GetString("CopyValueData", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Copy value name. + /// + internal static string CopyValueName { + get { + return ResourceManager.GetString("CopyValueName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Key name:. + /// + internal static string KeyName { + get { + return ResourceManager.GetString("KeyName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Name:. + /// + internal static string Name { + get { + return ResourceManager.GetString("Name", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You do not have enough rights to open the Windows registry editor. + /// + internal static string OpenInRegistryEditorAccessExceptionText { + get { + return ResourceManager.GetString("OpenInRegistryEditorAccessExceptionText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error on open registry editor. + /// + internal static string OpenInRegistryEditorAccessExceptionTitle { + get { + return ResourceManager.GetString("OpenInRegistryEditorAccessExceptionTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Open key in registry editor. + /// + internal static string OpenKeyInRegistryEditor { + get { + return ResourceManager.GetString("OpenKeyInRegistryEditor", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Search inside the Windows Registry. + /// + internal static string PluginDescription { + get { + return ResourceManager.GetString("PluginDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Registry Plugin. + /// + internal static string PluginTitle { + get { + return ResourceManager.GetString("PluginTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Registry key. + /// + internal static string RegistryKey { + get { + return ResourceManager.GetString("RegistryKey", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Registry value. + /// + internal static string RegistryValue { + get { + return ResourceManager.GetString("RegistryValue", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No data type. + /// + internal static string RegistryValueKindNone { + get { + return ResourceManager.GetString("RegistryValueKindNone", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unsupported data type. + /// + internal static string RegistryValueKindUnknown { + get { + return ResourceManager.GetString("RegistryValueKindUnknown", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Subkeys:. + /// + internal static string SubKeys { + get { + return ResourceManager.GetString("SubKeys", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Type:. + /// + internal static string Type { + get { + return ResourceManager.GetString("Type", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Value:. + /// + internal static string Value { + get { + return ResourceManager.GetString("Value", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Values:. + /// + internal static string Values { + get { + return ResourceManager.GetString("Values", resourceCulture); + } + } + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Properties/Resources.resx b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Properties/Resources.resx new file mode 100644 index 0000000000..7a97ac5ec7 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Properties/Resources.resx @@ -0,0 +1,182 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Registry Plugin + + + Search inside the Windows Registry + "this built into Windows the OS. translate accordingly, https://docs.microsoft.com/de-de/troubleshoot/windows-server/performance/windows-registry-advanced-users is an example of it translated in German" + + + Copy key name (path) + 'The maximum size of a key name is 255 characters.' + + + Key name: + 'The maximum size of a key name is 255 characters.' + + + Name: + + + Copy value name + + + Open key in registry editor + "registry editor" is the name of the OS built-in application + + + You do not have enough rights to open the Windows registry editor + "registry editor" is the name of the OS built-in application + + + Error on open registry editor + "registry editor" is the name of the OS built-in application + + + Subkeys: + + + Values: + + + Registry key + + + Registry value + + + Type: + See https://docs.microsoft.com/en-us/windows/win32/sysinfo/registry-value-types for proper context of how to translate 'type' + + + Value: + See https://docs.microsoft.com/en-us/windows/win32/sysinfo/registry-value-types for proper context of how to translate 'value' + + + No data type + + + Unsupported data type + + + Copy value data + + \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/plugin.json b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/plugin.json new file mode 100644 index 0000000000..0ad383e35d --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/plugin.json @@ -0,0 +1,12 @@ +{ + "ID": "303417D927BF4C97BCFFC78A123BE0C8", + "ActionKeyword": ":", + "Name": "Registry", + "Description": "Search inside the Windows Registry", + "Author": "TobiasSekan", + "Version": "1.0.0", + "Language": "csharp", + "Website": "https://aka.ms/powertoys", + "ExecuteFileName": "Microsoft.PowerToys.Run.Plugin.Registry.dll", + "IcoPath": "Images\\reg.dark.png" +}