mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-07 19:57:07 +02:00
[Run-Plugin] Settings plugin (#11663)
* Current settings plugin state on fresh master * typo fixes * Add to YML * Add to WXS * Address feedback - highlight the note in the tool-tip a little bit * Address feedback add extra note for "Manage known networks" * Address feedback - Show type of settings in sub-line and remove extra ControlPanel prefix in json * Add "WiFi" as alternative name for each Wi-Fi setting * Add a few more alternative names * Make RESX happy * exclude WindowsSettings.json from spell checker because all entries are placeholders for the translation * Translate all alternative names * Translate all notes * fix for not find "wifi" * fix typo * typo fixes and remove debug * Address feedback - correct author * Address feedback - settings entries * Address feedback - code changes * Address feedback - RESX changes and tool-tip * fix typo * Address feedback - remove superfluous interface * Address feedback - Update RESX * Address feedback - simplification * Address feedback - remove enumeration * Address feedback - move big function in extra helper classes * Address feedback - move big function in extra helper class * Address feedback - correct namespace * Address feedback - move translation to translation helper and make translation more robust * fix typo * Update src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/Main.cs Co-authored-by: Heiko <61519853+htcfreek@users.noreply.github.com> * Update src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/Helper/ResultHelper.cs Co-authored-by: Heiko <61519853+htcfreek@users.noreply.github.com> * Update src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/Main.cs Co-authored-by: Heiko <61519853+htcfreek@users.noreply.github.com> * fix build * Address feedback * ups * Address feedback - Correct windows update settings name * Update src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/WindowsSettings.json Co-authored-by: Heiko <61519853+htcfreek@users.noreply.github.com> * adding in dependencies so when you build Launcher, all plugins are included * Address feedback - add optional updates * Address feedback - use build numebr instaed of Windows version * Address feedback - Log difference between registry values + fix wrong ValueType (ushort -> uint) * Address feebdback - improved warning message on different registry values * fix typo * removed not need using * Update src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/Helper/UnsupportedSettingsHelper.cs Co-authored-by: Heiko <61519853+htcfreek@users.noreply.github.com> * Update src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/Helper/UnsupportedSettingsHelper.cs Co-authored-by: Heiko <61519853+htcfreek@users.noreply.github.com> * Update src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/Helper/UnsupportedSettingsHelper.cs Co-authored-by: Heiko <61519853+htcfreek@users.noreply.github.com> * Addrress feedback, don't copy embed file * Address feedback - Remove duplicated or not available settings * Address feedback - Improve scoring * Address feedback - Add extra filter * Address feedback - replace the are filter sign with a better one * Address feedback - fix unwanted behavior * Address feedback - Change class name * Address feedback - Rename settings type * typo fix * Fix installer * Comment out localization support Co-authored-by: Sekan, Tobias <tobias.sekan@startmail.com> Co-authored-by: Heiko <61519853+htcfreek@users.noreply.github.com> Co-authored-by: crutkas <crutkas@microsoft.com>
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
// 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.WindowsSettings.Properties;
|
||||
using Wox.Plugin;
|
||||
using Wox.Plugin.Logger;
|
||||
|
||||
namespace Microsoft.PowerToys.Run.Plugin.WindowsSettings.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(in Result result, in string assemblyName)
|
||||
{
|
||||
if (!(result?.ContextData is WindowsSetting entry))
|
||||
{
|
||||
return new List<ContextMenuResult>(0);
|
||||
}
|
||||
|
||||
var list = new List<ContextMenuResult>(1)
|
||||
{
|
||||
new ContextMenuResult
|
||||
{
|
||||
AcceleratorKey = Key.C,
|
||||
AcceleratorModifiers = ModifierKeys.Control,
|
||||
Action = _ => TryToCopyToClipBoard(entry.Command),
|
||||
FontFamily = "Segoe MDL2 Assets",
|
||||
Glyph = "\xE8C8", // E8C8 => Symbol: Copy
|
||||
PluginName = assemblyName,
|
||||
Title = $"{Resources.CopyCommand} (Ctrl+C)",
|
||||
},
|
||||
};
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// 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.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Wox.Plugin.Logger;
|
||||
|
||||
namespace Microsoft.PowerToys.Run.Plugin.WindowsSettings.Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class to easier work with the JSON file that contains all Windows settings
|
||||
/// </summary>
|
||||
internal static class JsonSettingsListHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the file that contains all settings for the query
|
||||
/// </summary>
|
||||
private const string _settingsFile = "WindowsSettings.json";
|
||||
|
||||
/// <summary>
|
||||
/// Read all possible Windows settings.
|
||||
/// </summary>
|
||||
/// <returns>A list with all possible windows settings.</returns>
|
||||
internal static IEnumerable<WindowsSetting> ReadAllPossibleSettings()
|
||||
{
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
var type = assembly.GetTypes().FirstOrDefault(x => x.Name == nameof(Main));
|
||||
|
||||
IEnumerable<WindowsSetting>? settingsList = null;
|
||||
|
||||
try
|
||||
{
|
||||
var resourceName = $"{type?.Namespace}.{_settingsFile}";
|
||||
using var stream = assembly.GetManifestResourceStream(resourceName);
|
||||
if (stream is null)
|
||||
{
|
||||
throw new Exception("stream is null");
|
||||
}
|
||||
|
||||
var options = new JsonSerializerOptions();
|
||||
options.Converters.Add(new JsonStringEnumConverter());
|
||||
|
||||
using var reader = new StreamReader(stream);
|
||||
var text = reader.ReadToEnd();
|
||||
|
||||
settingsList = JsonSerializer.Deserialize<IEnumerable<WindowsSetting>>(text, options);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Exception("Error loading settings JSON file", exception, typeof(Main));
|
||||
}
|
||||
|
||||
return settingsList ?? Enumerable.Empty<WindowsSetting>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
// 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.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.PowerToys.Run.Plugin.WindowsSettings.Properties;
|
||||
using Wox.Plugin;
|
||||
using Wox.Plugin.Logger;
|
||||
|
||||
namespace Microsoft.PowerToys.Run.Plugin.WindowsSettings.Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class to easier work with results
|
||||
/// </summary>
|
||||
internal static class ResultHelper
|
||||
{
|
||||
/// <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<WindowsSetting> list,
|
||||
string query,
|
||||
in string iconPath)
|
||||
{
|
||||
var resultList = new List<Result>(list.Count());
|
||||
|
||||
foreach (var entry in list)
|
||||
{
|
||||
var result = new Result
|
||||
{
|
||||
Action = (_) => DoOpenSettingsAction(entry),
|
||||
IcoPath = iconPath,
|
||||
SubTitle = $"{Resources.Area} \"{entry.Area}\" {Resources.SubtitlePreposition} {entry.Type}",
|
||||
Title = entry.Name,
|
||||
ContextData = entry,
|
||||
};
|
||||
|
||||
AddOptionalToolTip(entry, result);
|
||||
|
||||
resultList.Add(result);
|
||||
}
|
||||
|
||||
SetScores(resultList, query);
|
||||
|
||||
return resultList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a tool-tip to the given <see cref="Result"/>, based o the given <see cref="IWindowsSetting"/>.
|
||||
/// </summary>
|
||||
/// <param name="entry">The <see cref="WindowsSetting"/> that contain informations for the tool-tip.</param>
|
||||
/// <param name="result">The <see cref="Result"/> that need a tool-tip.</param>
|
||||
private static void AddOptionalToolTip(WindowsSetting entry, Result result)
|
||||
{
|
||||
var toolTipText = new StringBuilder();
|
||||
|
||||
toolTipText.AppendLine($"{Resources.Application}: {entry.Type}");
|
||||
toolTipText.AppendLine($"{Resources.Area}: {entry.Area}");
|
||||
|
||||
if (entry.AltNames != null && entry.AltNames.Any())
|
||||
{
|
||||
var altList = entry.AltNames.Aggregate((current, next) => $"{current}, {next}");
|
||||
|
||||
toolTipText.AppendLine($"{Resources.AlternativeName}: {altList}");
|
||||
}
|
||||
|
||||
toolTipText.Append($"{Resources.Command}: {entry.Command}");
|
||||
|
||||
if (!string.IsNullOrEmpty(entry.Note))
|
||||
{
|
||||
toolTipText.AppendLine(string.Empty);
|
||||
toolTipText.AppendLine(string.Empty);
|
||||
toolTipText.Append($"{Resources.Note}: {entry.Note}");
|
||||
}
|
||||
|
||||
result.ToolTipData = new ToolTipData(entry.Name, toolTipText.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Open the settings page of the given <see cref="IWindowsSetting"/>.
|
||||
/// </summary>
|
||||
/// <param name="entry">The <see cref="WindowsSetting"/> that contain the information to open the setting on command level.</param>
|
||||
/// <returns><see langword="true"/> if the settings could be opened, otherwise <see langword="false"/>.</returns>
|
||||
private static bool DoOpenSettingsAction(WindowsSetting entry)
|
||||
{
|
||||
ProcessStartInfo processStartInfo;
|
||||
|
||||
var command = entry.Command;
|
||||
|
||||
if (command.Contains("%windir%", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
var windowsFolder = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
|
||||
command = command.Replace("%windir%", windowsFolder, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
if (command.Contains(' '))
|
||||
{
|
||||
var commandSplit = command.Split(' ');
|
||||
var file = commandSplit.FirstOrDefault();
|
||||
var arguments = command[file.Length..].TrimStart();
|
||||
|
||||
processStartInfo = new ProcessStartInfo(file, arguments)
|
||||
{
|
||||
UseShellExecute = false,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
processStartInfo = new ProcessStartInfo(command)
|
||||
{
|
||||
UseShellExecute = true,
|
||||
};
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Process.Start(processStartInfo);
|
||||
return true;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Exception("can't open settings", exception, typeof(ResultHelper));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the score (known as order number or ranking number)
|
||||
/// for all <see cref="Results"/> in the given list, based on the given query.
|
||||
/// </summary>
|
||||
/// <param name="resultList">A list with <see cref="Result"/>s that need scores.</param>
|
||||
/// <param name="query">The query to calculated the score for the <see cref="Result"/>s.</param>
|
||||
private static void SetScores(IEnumerable<Result> resultList, string query)
|
||||
{
|
||||
var lowScore = 1_000;
|
||||
var mediumScore = 5_000;
|
||||
var highScore = 10_000;
|
||||
|
||||
foreach (var result in resultList)
|
||||
{
|
||||
if (!(result.ContextData is WindowsSetting windowsSetting))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (windowsSetting.Name.StartsWith(query, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
result.Score = highScore--;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If query starts with second or next word of name, set score.
|
||||
if (windowsSetting.Name.Contains($" {query}", StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
result.Score = mediumScore--;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (windowsSetting.Area.StartsWith(query, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
result.Score = lowScore--;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (windowsSetting.AltNames is null)
|
||||
{
|
||||
result.Score = lowScore--;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (windowsSetting.AltNames.Any(x => x.StartsWith(query, StringComparison.CurrentCultureIgnoreCase)))
|
||||
{
|
||||
result.Score = mediumScore--;
|
||||
continue;
|
||||
}
|
||||
|
||||
result.Score = lowScore--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// 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.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Microsoft.PowerToys.Run.Plugin.WindowsSettings.Properties;
|
||||
using Wox.Plugin.Logger;
|
||||
|
||||
namespace Microsoft.PowerToys.Run.Plugin.WindowsSettings.Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class to easier work with translations.
|
||||
/// </summary>
|
||||
internal static class TranslationHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Translate all settings of the given list with <see cref="WindowsSetting"/>.
|
||||
/// </summary>
|
||||
/// <param name="settingsList">The list that contains <see cref="WindowsSetting"/> to translate.</param>
|
||||
internal static void TranslateAllSettings(in IEnumerable<WindowsSetting>? settingsList)
|
||||
{
|
||||
if (settingsList is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var settings in settingsList)
|
||||
{
|
||||
var area = Resources.ResourceManager.GetString($"Area{settings.Area}");
|
||||
var name = Resources.ResourceManager.GetString(settings.Name);
|
||||
var type = Resources.ResourceManager.GetString(settings.Type);
|
||||
|
||||
if (string.IsNullOrEmpty(area))
|
||||
{
|
||||
Log.Warn($"Resource string for [Area{settings.Area}] not found", typeof(Main));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
Log.Warn($"Resource string for [{settings.Name}] not found", typeof(Main));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(type))
|
||||
{
|
||||
Log.Warn($"Resource string for [{settings.Name}] not found", typeof(Main));
|
||||
}
|
||||
|
||||
settings.Area = area ?? settings.Area ?? string.Empty;
|
||||
settings.Name = name ?? settings.Name ?? string.Empty;
|
||||
settings.Type = type ?? settings.Type ?? string.Empty;
|
||||
|
||||
if (!string.IsNullOrEmpty(settings.Note))
|
||||
{
|
||||
var note = Resources.ResourceManager.GetString(settings.Note);
|
||||
if (string.IsNullOrEmpty(note))
|
||||
{
|
||||
Log.Warn($"Resource string for [{settings.Note}] not found", typeof(Main));
|
||||
}
|
||||
|
||||
settings.Note = note ?? settings.Note ?? string.Empty;
|
||||
}
|
||||
|
||||
if (!(settings.AltNames is null) && settings.AltNames.Any())
|
||||
{
|
||||
var translatedAltNames = new Collection<string>();
|
||||
|
||||
foreach (var altName in settings.AltNames)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(altName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var translatedAltName = Resources.ResourceManager.GetString(altName);
|
||||
if (string.IsNullOrEmpty(translatedAltName))
|
||||
{
|
||||
Log.Warn($"Resource string for [{altName}] not found", typeof(Main));
|
||||
}
|
||||
|
||||
translatedAltNames.Add(translatedAltName ?? altName);
|
||||
}
|
||||
|
||||
settings.AltNames = translatedAltNames;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// 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 Wox.Plugin.Logger;
|
||||
|
||||
namespace Microsoft.PowerToys.Run.Plugin.WindowsSettings.Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class to easier work with the version of the Windows OS
|
||||
/// </summary>
|
||||
internal static class UnsupportedSettingsHelper
|
||||
{
|
||||
private const string _keyPath = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion";
|
||||
private const string _keyNameBuild = "CurrentBuild";
|
||||
private const string _keyNameBuildNumber = "CurrentBuildNumber";
|
||||
|
||||
/// <summary>
|
||||
/// Remove all <see cref="WindowsSetting"/> of the given list that are not present on the current used Windows build.
|
||||
/// </summary>
|
||||
/// <param name="settingsList">The list with <see cref="WindowsSetting"/> to filter.</param>
|
||||
/// <returns>A new list with <see cref="WindowsSetting"/> that only contain present Windows settings for this OS.</returns>
|
||||
internal static IEnumerable<WindowsSetting> FilterByBuild(in IEnumerable<WindowsSetting>? settingsList)
|
||||
{
|
||||
if (settingsList is null)
|
||||
{
|
||||
return Enumerable.Empty<WindowsSetting>();
|
||||
}
|
||||
|
||||
var currentBuild = GetNumericRegistryValue(_keyPath, _keyNameBuild);
|
||||
var currentBuildNumber = GetNumericRegistryValue(_keyPath, _keyNameBuildNumber);
|
||||
|
||||
if (currentBuild != currentBuildNumber)
|
||||
{
|
||||
var usedValueName = currentBuild != uint.MinValue ? _keyNameBuild : _keyNameBuildNumber;
|
||||
var warningMessage =
|
||||
$"Detecting the Windows version in registry ({_keyPath}) leads to an inconclusive"
|
||||
+ $" result ({_keyNameBuild}={currentBuild}, {_keyNameBuildNumber}={currentBuildNumber})!"
|
||||
+ $" For resolving the conflict we use the value of '{usedValueName}'.";
|
||||
|
||||
Log.Warn(warningMessage, typeof(UnsupportedSettingsHelper));
|
||||
}
|
||||
|
||||
var currentWindowsBuild = currentBuild != uint.MinValue
|
||||
? currentBuild
|
||||
: currentBuildNumber;
|
||||
|
||||
var filteredSettingsList = settingsList.Where(found
|
||||
=> (found.DeprecatedInBuild == null || currentWindowsBuild < found.DeprecatedInBuild)
|
||||
&& (found.IntroducedInBuild == null || currentWindowsBuild >= found.IntroducedInBuild));
|
||||
|
||||
filteredSettingsList = filteredSettingsList.OrderBy(found => found.Name);
|
||||
|
||||
return filteredSettingsList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a unsigned numeric value from given registry value name inside the given registry key.
|
||||
/// </summary>
|
||||
/// <param name="registryKey">The registry key.</param>
|
||||
/// <param name="valueName">The name of the registry value.</param>
|
||||
/// <returns>A registry value or <see cref="uint.MinValue"/> on error.</returns>
|
||||
private static uint GetNumericRegistryValue(in string registryKey, in string valueName)
|
||||
{
|
||||
object registryValueData;
|
||||
|
||||
try
|
||||
{
|
||||
registryValueData = Win32.Registry.GetValue(registryKey, valueName, uint.MinValue);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Exception(
|
||||
$"Can't get registry value for '{valueName}'",
|
||||
exception,
|
||||
typeof(UnsupportedSettingsHelper));
|
||||
|
||||
return uint.MinValue;
|
||||
}
|
||||
|
||||
return uint.TryParse(registryValueData as string, out var buildNumber)
|
||||
? buildNumber
|
||||
: uint.MinValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user