mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-08 04:07:40 +02:00
[PT Run] Registry plugin (#7951)
This commit is contained in:
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class to easier work with context menu entries
|
||||
/// </summary>
|
||||
internal static class ContextMenuHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Return a list with all context menu entries for the given <see cref="Result"/>
|
||||
/// <para>Symbols taken from <see href="https://docs.microsoft.com/en-us/windows/uwp/design/style/segoe-ui-symbol-font"/></para>
|
||||
/// </summary>
|
||||
/// <param name="result">The result for the context menu entires</param>
|
||||
/// <param name="assemblyName">The name of the this assembly</param>
|
||||
/// <returns>A list with context menu entries</returns>
|
||||
internal static List<ContextMenuResult> GetContextMenu(Result result, string assemblyName)
|
||||
{
|
||||
if (!(result?.ContextData is RegistryEntry entry))
|
||||
{
|
||||
return new List<ContextMenuResult>(0);
|
||||
}
|
||||
|
||||
var list = new List<ContextMenuResult>();
|
||||
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Open the Windows registry editor and jump to registry key inside the given key (inside the <see cref="RegistryEntry"/>
|
||||
/// </summary>
|
||||
/// <param name="entry">The <see cref="RegistryEntry"/> to jump in</param>
|
||||
/// <returns><see langword="true"/> if the registry editor was successful open, otherwise <see langword="false"/></returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy the given text to the clipboard
|
||||
/// </summary>
|
||||
/// <param name="text">The text to copy to the clipboard</param>
|
||||
/// <returns><see langword="true"/>The text successful copy to the clipboard, otherwise <see langword="false"/></returns>
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class to easier work with queries
|
||||
/// </summary>
|
||||
internal static class QueryHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// The character to distinguish if the search query contain multiple parts (typically "\\")
|
||||
/// </summary>
|
||||
internal const string QuerySplitCharacter = "\\\\";
|
||||
|
||||
/// <summary>
|
||||
/// A list that contain short names of all registry base keys
|
||||
/// </summary>
|
||||
private static readonly IReadOnlyDictionary<string, string> _shortBaseKeys = new Dictionary<string, string>(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 },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Return the parts of a given query
|
||||
/// </summary>
|
||||
/// <param name="query">The query that could contain parts</param>
|
||||
/// <param name="queryKey">The key part of the query</param>
|
||||
/// <param name="queryValueName">The value name part of the query</param>
|
||||
/// <returns><see langword="true"/> when the query search for a key and a value name, otherwise <see langword="false"/></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a registry key with a long base key
|
||||
/// </summary>
|
||||
/// <param name="registryKey">A registry key with a short base key</param>
|
||||
/// <returns>A registry key with a long base key</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a registry key with a short base key (useful to reduce the text length of a registry key)
|
||||
/// </summary>
|
||||
/// <param name="registryKey">A registry key with a full base key</param>
|
||||
/// <returns>A registry key with a short base key</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
/// <summary>
|
||||
/// Helper class to easier work with the registry
|
||||
/// </summary>
|
||||
internal static class RegistryHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// A list that contain all registry base keys in a long/full version and in a short version (e.g HKLM = HKEY_LOCAL_MACHINE)
|
||||
/// </summary>
|
||||
private static readonly IReadOnlyDictionary<string, RegistryKey> _baseKeys = new Dictionary<string, RegistryKey>(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 },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Try to find registry base keys based on the given query
|
||||
/// </summary>
|
||||
/// <param name="query">The query to search</param>
|
||||
/// <returns>A combination of a list of base <see cref="RegistryKey"/> and the sub keys</returns>
|
||||
internal static (IEnumerable<RegistryKey>? 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a list of all registry base key
|
||||
/// </summary>
|
||||
/// <returns>A list with all registry base keys</returns>
|
||||
internal static ICollection<RegistryEntry> GetAllBaseKeys()
|
||||
{
|
||||
return new Collection<RegistryEntry>
|
||||
{
|
||||
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),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Search for the given sub-key path in the given registry base key
|
||||
/// </summary>
|
||||
/// <param name="baseKey">The base <see cref="RegistryKey"/></param>
|
||||
/// <param name="subKeyPath">The path of the registry sub-key</param>
|
||||
/// <returns>A list with all found registry keys</returns>
|
||||
internal static ICollection<RegistryEntry> 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<RegistryEntry> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a human readable summary of a given <see cref="RegistryKey"/>
|
||||
/// </summary>
|
||||
/// <param name="key">The <see cref="RegistryKey"/> for the summary</param>
|
||||
/// <returns>A human readable summary</returns>
|
||||
internal static string GetSummary(in RegistryKey key)
|
||||
{
|
||||
return $"{Resources.SubKeys} {key.SubKeyCount} - {Resources.Values} {key.ValueCount}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Open a given registry key in the registry editor
|
||||
/// </summary>
|
||||
/// <param name="fullKey">The registry key to open</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to find the given registry sub-key in the given registry parent-key
|
||||
/// </summary>
|
||||
/// <param name="parentKey">The parent-key, also the root to start the search</param>
|
||||
/// <param name="searchSubKey">The sub-key to find</param>
|
||||
/// <returns>A list with all found registry sub-keys</returns>
|
||||
private static ICollection<RegistryEntry> FindSubKey(in RegistryKey parentKey, in string searchSubKey)
|
||||
{
|
||||
var list = new Collection<RegistryEntry>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a list with a registry sub-keys of the given registry parent-key
|
||||
/// </summary>
|
||||
/// <param name="parentKey">The registry parent-key</param>
|
||||
/// <param name="maxCount">(optional) The maximum count of the results</param>
|
||||
/// <returns>A list with all found registry sub-keys</returns>
|
||||
private static ICollection<RegistryEntry> GetAllSubKeys(in RegistryKey parentKey, in int maxCount = 50)
|
||||
{
|
||||
var list = new Collection<RegistryEntry>();
|
||||
|
||||
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
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class to easier work with results
|
||||
/// </summary>
|
||||
internal static class ResultHelper
|
||||
{
|
||||
#pragma warning disable CA1031 // Do not catch general exception types
|
||||
|
||||
/// <summary>
|
||||
/// Return a list with <see cref="Result"/>s, based on the given list
|
||||
/// </summary>
|
||||
/// <param name="list">The original result list to convert</param>
|
||||
/// <param name="iconPath">The path to the icon of each entry</param>
|
||||
/// <returns>A list with <see cref="Result"/></returns>
|
||||
internal static List<Result> GetResultList(in IEnumerable<RegistryEntry> list, in string iconPath)
|
||||
{
|
||||
var resultList = new List<Result>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a list with <see cref="Result"/>s, based on the given <see cref="RegistryKey"/>
|
||||
/// </summary>
|
||||
/// <param name="key">The <see cref="RegistryKey"/> that should contain entries for the list</param>
|
||||
/// <param name="iconPath">The path to the icon of each entry</param>
|
||||
/// <param name="searchValue">(optional) When not <see cref="string.Empty"/> filter the list for the given value name and value</param>
|
||||
/// <returns>A list with <see cref="Result"/></returns>
|
||||
internal static List<Result> GetValuesFromKey(in RegistryKey? key, in string iconPath, string searchValue = "")
|
||||
{
|
||||
if (key is null)
|
||||
{
|
||||
return new List<Result>(0);
|
||||
}
|
||||
|
||||
ICollection<KeyValuePair<string, object>> valueList = new List<KeyValuePair<string, object>>(key.ValueCount);
|
||||
|
||||
var resultList = new List<Result>();
|
||||
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Return a truncated name
|
||||
/// </summary>
|
||||
/// <param name="text">The text to truncate</param>
|
||||
/// <param name="maxLength">The maximum length of the text</param>
|
||||
/// <param name="truncateSide">(optional) The side of the truncate</param>
|
||||
/// <returns>A truncated text with a maximum length</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the tool-tip text for a registry value
|
||||
/// </summary>
|
||||
/// <param name="key">The registry key for the tool-tip</param>
|
||||
/// <param name="valueEntry">The value name and value of the registry value</param>
|
||||
/// <returns>A tool-tip text</returns>
|
||||
private static string GetToolTipTextForRegistryValue(RegistryKey key, KeyValuePair<string, object> 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)}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the sub-title text for a registry value
|
||||
/// </summary>
|
||||
/// <param name="key">The registry key for the sub-title</param>
|
||||
/// <param name="valueEntry">The value name and value of the registry value</param>
|
||||
/// <returns>A sub-title text</returns>
|
||||
private static string GetSubTileForRegistryValue(RegistryKey key, KeyValuePair<string, object> valueEntry)
|
||||
{
|
||||
return $"{Resources.Type} {ValueHelper.GetType(key, valueEntry.Key)}"
|
||||
+ $" - {Resources.Value} {ValueHelper.GetValue(key, valueEntry.Key, 50)}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class to easier work with values of a <see cref="RegistryKey"/>
|
||||
/// </summary>
|
||||
internal static class ValueHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Return a human readable value data, of the given value name inside the given <see cref="RegistryKey"/>
|
||||
/// </summary>
|
||||
/// <param name="key">The <see cref="RegistryKey"/> that should contain the value name.</param>
|
||||
/// <param name="valueName">The name of the value.</param>
|
||||
/// <param name="maxLength">The maximum length for the human readable value.</param>
|
||||
/// <returns>A human readable value data.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the registry type name of a given value name inside a given <see cref="RegistryKey"/>
|
||||
/// </summary>
|
||||
/// <param name="key">The <see cref="RegistryKey"/> that should contain the value name</param>
|
||||
/// <param name="valueName">The name of the value</param>
|
||||
/// <returns>A registry type name</returns>
|
||||
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)),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user