diff --git a/mRemoteNG/Tools/WindowsRegistry/IRegistry.cs b/mRemoteNG/Tools/WindowsRegistry/IRegistry.cs index 0bf2da86c..3458a37fb 100644 --- a/mRemoteNG/Tools/WindowsRegistry/IRegistry.cs +++ b/mRemoteNG/Tools/WindowsRegistry/IRegistry.cs @@ -6,39 +6,98 @@ using System.Runtime.Versioning; namespace mRemoteNG.Tools.WindowsRegistry { [SupportedOSPlatform("windows")] - /// - /// Interface for the Registry class providing methods for interacting with the Windows Registry. - /// public interface IRegistry { - #region registry reader + #region Registry Reader + + /// + /// Gets the names of subkeys under the specified registry hive and path. + /// string[] GetSubKeyNames(RegistryHive hive, string path); - string GetPropertyValue(WindowsRegistryKey key); - string GetPropertyValue(RegistryHive hive, string path, string name); + /// + /// Gets the value of a registry entry specified by its name. + /// + string GetValue(RegistryHive hive, string path, string name); + + /// + /// Gets the string value of a registry entry specified by its name. + /// + string GetStringValue(RegistryHive hive, string path, string name, string defaultValue = null); + + /// + /// Gets the boolean value of a registry entry specified by its name. + /// bool GetBoolValue(RegistryHive hive, string path, string propertyName, bool defaultValue = false); - int GetDwordValue(RegistryHive hive, string path, string propertyName, int defaultValue = 0); - WindowsRegistryKey GetWindowsRegistryKey(RegistryHive hive, string path, string name); - WindowsRegistryKey GetWindowsRegistryKey(WindowsRegistryKey key); + /// + /// Gets the DWORD value of a registry entry specified by its name. + /// + int GetIntegerValue(RegistryHive hive, string path, string propertyName, int defaultValue = -1); - List GetRegistryEntries(RegistryHive hive, string path); - List GetRegistryEntryiesRecursive(RegistryHive hive, string path); + /// + /// Retrieves a specific registry entry of type string from the Windows Registry. + /// + WinRegistryEntry GetEntry(RegistryHive hive, string path, string name); + + /// + /// Retrieves a list of string-type registry entries from the Windows Registry under the specified path. + /// + List> GetEntries(RegistryHive hive, string path); + + /// + /// Retrieves a list of string-type registry entries from the Windows Registry under the specified path and its subkeys recursively. + /// + List> GetEntriesRecursive(RegistryHive hive, string path); #endregion - #region registry writer - void SetRegistryValue(WindowsRegistryKey key); - void SetRegistryValue(RegistryHive hive, string path, string name, object value, RegistryValueKind valueKind); - void DeleteRegistryKey(RegistryHive hive, string path, bool ignoreNotFound = false); + #region Registry Writer + + /// + /// Sets the value of a registry entry. + /// + void SetValue(RegistryHive hive, string path, string name, object value, RegistryValueKind valueKind); + + /// + /// Creates a new registry key. + /// + void CreateKey(RegistryHive hive, string path); + + /// + /// Deletes a registry value. + /// + void DeleteRegistryValue(RegistryHive hive, string path, string name); + + /// + /// Deletes a registry key and all its subkeys and values. + /// + void DeleteTree(RegistryHive hive, string path); #endregion - #region registry tools + #region Registry Tools + + /// + /// Converts a string representation of a registry hive to the corresponding RegistryHive enum value. + /// RegistryHive ConvertStringToRegistryHive(string hiveString); + + /// + /// Converts a string representation of a registry value kind to the corresponding RegistryValueKind enum value. + /// RegistryValueKind ConvertStringToRegistryValueKind(string valueType); + + /// + /// Converts a .NET type to the corresponding RegistryValueKind enum value. + /// RegistryValueKind ConvertTypeToRegistryValueKind(Type valueType); + + /// + /// Converts a RegistryValueKind enum value to the corresponding .NET type. + /// Type ConvertRegistryValueKindToType(RegistryValueKind valueKind); + #endregion } -} \ No newline at end of file +} diff --git a/mRemoteNG/Tools/WindowsRegistry/IRegistryRead.cs b/mRemoteNG/Tools/WindowsRegistry/IRegistryRead.cs index 28b5d1423..7228266b5 100644 --- a/mRemoteNG/Tools/WindowsRegistry/IRegistryRead.cs +++ b/mRemoteNG/Tools/WindowsRegistry/IRegistryRead.cs @@ -6,31 +6,74 @@ using System.Runtime.Versioning; namespace mRemoteNG.Tools.WindowsRegistry { [SupportedOSPlatform("windows")] - /// - /// Interface for the Registry class providing methods for interacting with read actions in the Windows Registry. - /// public interface IRegistryRead { - #region registry reader + #region Registry Reader + + /// + /// Gets the names of subkeys under the specified registry hive and path. + /// string[] GetSubKeyNames(RegistryHive hive, string path); - string GetPropertyValue(WindowsRegistryKey key); - string GetPropertyValue(RegistryHive hive, string path, string name); + /// + /// Gets the value of a registry entry specified by its name. + /// + string GetValue(RegistryHive hive, string path, string name); + + /// + /// Gets the string value of a registry entry specified by its name. + /// + string GetStringValue(RegistryHive hive, string path, string name, string defaultValue = null); + + /// + /// Gets the boolean value of a registry entry specified by its name. + /// bool GetBoolValue(RegistryHive hive, string path, string propertyName, bool defaultValue = false); - int GetDwordValue(RegistryHive hive, string path, string propertyName, int defaultValue = 0); - WindowsRegistryKey GetWindowsRegistryKey(RegistryHive hive, string path, string name); - WindowsRegistryKey GetWindowsRegistryKey(WindowsRegistryKey key); + /// + /// Gets the DWORD value of a registry entry specified by its name. + /// + int GetIntegerValue(RegistryHive hive, string path, string propertyName, int defaultValue = -1); + + /// + /// Retrieves a specific registry entry of type string from the Windows Registry. + /// + WinRegistryEntry GetEntry(RegistryHive hive, string path, string name); + + /// + /// Retrieves a list of string-type registry entries from the Windows Registry under the specified path. + /// + List> GetEntries(RegistryHive hive, string path); + + /// + /// Retrieves a list of string-type registry entries from the Windows Registry under the specified path and its subkeys recursively. + /// + List> GetEntriesRecursive(RegistryHive hive, string path); - List GetRegistryEntries(RegistryHive hive, string path); - List GetRegistryEntryiesRecursive(RegistryHive hive, string path); #endregion - #region registry tools + #region Registry Tools + + /// + /// Converts a string representation of a registry hive to the corresponding RegistryHive enum value. + /// RegistryHive ConvertStringToRegistryHive(string hiveString); + + /// + /// Converts a string representation of a registry value kind to the corresponding RegistryValueKind enum value. + /// RegistryValueKind ConvertStringToRegistryValueKind(string valueType); + + /// + /// Converts a .NET type to the corresponding RegistryValueKind enum value. + /// RegistryValueKind ConvertTypeToRegistryValueKind(Type valueType); + + /// + /// Converts a RegistryValueKind enum value to the corresponding .NET type. + /// Type ConvertRegistryValueKindToType(RegistryValueKind valueKind); + #endregion } } \ No newline at end of file diff --git a/mRemoteNG/Tools/WindowsRegistry/WinRegistry.cs b/mRemoteNG/Tools/WindowsRegistry/WinRegistry.cs new file mode 100644 index 000000000..f4d55ce7b --- /dev/null +++ b/mRemoteNG/Tools/WindowsRegistry/WinRegistry.cs @@ -0,0 +1,491 @@ +using Microsoft.Win32; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Versioning; +using System.Security; + +namespace mRemoteNG.Tools.WindowsRegistry +{ + [SupportedOSPlatform("windows")] + /// + /// Provides functionality to interact with the Windows Registry, including reading, writing, and managing registry entries. + /// + /// + /// This class encapsulates common functionality for working with the Windows Registry. It offers methods to open registry keys, create subkeys, and read or write values. + /// The class serves as a base for specialized registry entry classes, centralizing registry operations and validation checks. + /// Users can create instances of this class to perform various registry operations, such as retrieving subkey names, reading values of different types, creating keys, and deleting registry entries or keys. + /// Additionally, the class includes methods for converting between different registry value types and handling custom validation rules. + /// + /// License: + /// This class is licensed under MIT License. + /// + public class WinRegistry : IRegistry, IRegistryRead + { + #region Public Read Method: GetSubKeyNames + + /// + /// Retrieves the names of subkeys under a specified registry key path. + /// + /// The RegistryHive where the subkeys are located. + /// The path to the registry key containing the subkeys. + /// An array of strings containing the names of subkeys, or an empty array if no subkeys are found. + public string[] GetSubKeyNames(RegistryHive Hive, string Path) + { + ThrowIfHiveInvalid(Hive); + ThrowIfPathInvalid(Path); + + using var key = RegistryKey.OpenBaseKey(Hive, RegistryView.Default).OpenSubKey(Path); + if (key != null) + return key.GetSubKeyNames(); + else + return Array.Empty(); + } + + #endregion + + #region Public Read Value Method + + /// + /// Retrieves the data value associated with the specified registry key and value name. + /// + /// The registry hive. + /// The registry key path. + /// The name of the value. Null or Empty to get default. + /// The value data as a string, or null if the value is not found. + /// Thrown when the specified registry hive, path or name is invalid + public string GetValue(RegistryHive Hive, string Path, string Name) + { + ThrowIfHiveInvalid(Hive); + ThrowIfPathInvalid(Path); + + using var key = RegistryKey.OpenBaseKey(Hive, RegistryView.Default).OpenSubKey(Path); + if (key == null) + return null; + + // Ensure name is null when null or empty to get defaults value + if (string.IsNullOrEmpty(Name)) + Name = null; + + return key.GetValue(Name)?.ToString(); + } + + /// + /// Retrieves the data value associated with the specified registry key and uses the default value if the value name is not specified. + /// + /// The registry hive. + /// The registry key path. + /// The value data as a string, or null if the value is not found. + public string GetDefaultValue(RegistryHive Hive, string Path) + { + return GetValue(Hive, Path, null); + } + + /// + /// Retrieves the string value from the specified REG_SZ registry key. + /// + /// The registry hive. + /// The registry key path. + /// The name of the value. Null or Empty to get default. + /// The default value to return if the property is not found. + /// The value data as string, or the specified default value if the value is not found. + public string GetStringValue(RegistryHive Hive, string Path, string Name, string DefaultValue = null) + { + string value = GetValue(Hive, Path, Name); + return value ?? DefaultValue; + } + + /// + /// Retrieves the bool value from from the specified REG_SZ or REG_DWORD registry key. + /// + /// The registry hive. + /// The registry key path. + /// The name of the value. + /// The default value to return if the property is not found or cannot be parsed. (Default = false) + /// The value data as bool, parsed from its REG_SZ or REG_DWORD representation if possible, or the specified default value if the value is not found or cannot be parsed. + public bool GetBoolValue(RegistryHive Hive, string Path, string Name, bool DefaultValue = false) + { + string value = GetValue(Hive, Path, Name); + + if (!string.IsNullOrEmpty(value)) + { + if (int.TryParse(value, out int intValue)) + return intValue == 1; + if (bool.TryParse(value, out bool boolValue)) + return boolValue; + } + + return DefaultValue; + } + + /// + /// Retrieves the integer value from from the specified REG_DWORD registry key. + /// + /// The registry hive. + /// The registry key path. + /// The name of the value. + /// The default value to return if the property is not found or cannot be parsed. (Default = 0) + /// The value data as integer, parsed from its REG_DWORD representation if possible, or the specified default value if the value is not found or cannot be parsed. + public int GetIntegerValue(RegistryHive Hive, string Path, string Name, int DefaultValue = 0) + { + string value = GetValue(Hive, Path, Name); + + if (int.TryParse(value, out int intValue)) + return intValue; + + return DefaultValue; + } + + #endregion + + #region Public Read Tree Methods + + /// + /// Retrieves a windows registry entry for a specific registry hive, path, and value name. + /// + /// The RegistryHive of the key. + /// The path of the key. + /// The name of the value to retrieve. + /// A WinRegistryEntry object representing the specified registry key and value. + public WinRegistryEntry GetEntry(RegistryHive hive, string path, string name) + { + return WinRegistryEntry + .New(hive, path, name) + .Read(); + } + + /// + /// Retrieves a list of registry entries and their values under a given key path. + /// + /// The registry hive. + /// The registry key path. + /// A list of WinRegistryEntry objects, each representing a value within the specified registry key path. + public List> GetEntries(RegistryHive hive, string path) + { + using var key = RegistryKey.OpenBaseKey(hive, RegistryView.Default).OpenSubKey(path); + if (key == null) + return new List>(); // Return an empty list when no key is found + + return key.GetValueNames() + .Select(name => WinRegistryEntry.New(hive, path, name) + .Read()) + .ToList(); + } + + /// + /// Recursively retrieves registry entries under a given key path and its subkeys. + /// + /// The registry hive. + /// The registry key path. + /// A list of WinRegistryEntry objects, each representing a value within the specified registry key path. + public List> GetEntriesRecursive(RegistryHive hive, string path) + { + List> list = new(); + using (var baseKey = RegistryKey.OpenBaseKey(hive, RegistryView.Default)) + using (var key = baseKey.OpenSubKey(path)) + { + if (key != null) + { + foreach (string subPathName in key.GetSubKeyNames()) + { + string subKey = $"{path}\\{subPathName}"; + list.AddRange(GetEntriesRecursive(hive, subKey)); + } + } + } + + list.AddRange(GetEntries(hive, path)); + return list; + } + #endregion + + #region Public Write Methods + + /// + /// Sets the value of a specific property within a registry key using individual parameters. + /// + /// The registry hive. + /// The registry key path. + /// The name of the value. + /// The value to set for the property. + /// The data type of the value to set. + /// Thrown when an error occurs while writing to the Windows Registry key. + public void SetValue(RegistryHive Hive, string Path, string Name, object Value, RegistryValueKind ValueKind) + { + ThrowIfHiveInvalid(Hive); + ThrowIfPathInvalid(Path); + + string name = string.IsNullOrEmpty(Name) ? null : Name; + RegistryValueKind valueKind = string.IsNullOrEmpty(Name) ? RegistryValueKind.String : ValueKind; + + ThrowIfValueKindInvalid(valueKind); + + try + { + using RegistryKey baseKey = RegistryKey.OpenBaseKey(Hive, RegistryView.Default); + using RegistryKey registryKey = baseKey.CreateSubKey(Path, true); + + registryKey.SetValue(name, Value, valueKind); + } + catch (Exception ex) + { + throw new InvalidOperationException("Error writing to the Windows Registry.", ex); + } + } + + /// + /// Sets the default value of a registry key to the specified string value. + /// + /// The registry hive. + /// The registry key path. + /// The value to set for the default property. + /// Thrown when the specified registry hive or path is invalid. + /// Thrown when an error occurs while writing to the Windows Registry key. + public void SetDefaultValue(RegistryHive Hive, string Path, string Value) + { + SetValue(Hive, Path, null, Value, RegistryValueKind.String); + } + + /// + /// Creates a registry key at the specified location. + /// + /// The registry hive to create the key under. + /// The path of the registry key to create. + /// Thrown when the specified registry hive or path is invalid + /// Thrown when an error occurs while creating the Windows Registry key. + public void CreateKey(RegistryHive Hive, string Path) + { + ThrowIfHiveInvalid(Hive); + ThrowIfPathInvalid(Path); + + try + { + using RegistryKey baseKey = RegistryKey.OpenBaseKey(Hive, RegistryView.Default); + baseKey.CreateSubKey(Path); + } + catch (Exception ex) + { + throw new InvalidOperationException("Error creating the Windows Registry key.", ex); + } + } + + #endregion + + #region Public Delete Methods + + /// + /// Deletes a registry value under the specified registry key. + /// + /// The registry hive to open. + /// The path of the registry key where the value exists. + /// The name of the value to delete. + /// Thrown when the specified registry hive, path or name is invalid + /// Thrown when the user does not have the necessary permissions to delete the registry value. + /// Thrown when the user does not have the necessary permissions to delete the registry value due to security restrictions. + public void DeleteRegistryValue(RegistryHive Hive, string Path, string Name) + { + ThrowIfHiveInvalid(Hive); + ThrowIfPathInvalid(Path); + ThrowIfNameInvalid(Name); + try + { + using RegistryKey baseKey = RegistryKey.OpenBaseKey(Hive, RegistryView.Default); + using RegistryKey key = baseKey.OpenSubKey(Path, true); + key?.DeleteValue(Name, true); + } + catch (SecurityException ex) + { + throw new SecurityException("Insufficient permissions to delete the registry key or value.", ex); + } + catch (UnauthorizedAccessException ex) + { + throw new UnauthorizedAccessException("Insufficient permissions to delete the registry key or value due to security restrictions.", ex); + } + catch (Exception ex) + { + throw new Exception("An error occurred while deleting the registry key or value.", ex); + } + } + + /// + /// Deletes a registry key and its subkeys. + /// + /// The registry hive to open. + /// The path of the registry key to delete. + /// Thrown when the specified registry hive or path is invalid + /// Thrown when the user does not have the necessary permissions to delete the registry key. + /// Thrown when the user does not have the necessary permissions to delete the registry key due to security restrictions. + public void DeleteTree(RegistryHive Hive, string Path) + { + ThrowIfHiveInvalid(Hive); + ThrowIfPathInvalid(Path); + try + { + using RegistryKey baseKey = RegistryKey.OpenBaseKey(Hive, RegistryView.Default); + baseKey?.DeleteSubKeyTree(Path, true); + } + catch (SecurityException ex) + { + throw new SecurityException("Insufficient permissions to delete the registry key or value.", ex); + } + catch (UnauthorizedAccessException ex) + { + throw new UnauthorizedAccessException("Insufficient permissions to delete the registry key or value due to security restrictions.", ex); + } + catch (Exception ex) + { + throw new Exception("An error occurred while deleting the registry key or value.", ex); + } + } + + #endregion + + #region Public Converter Methods + + /******************************************************* + * + * Converter methods + * + ******************************************************/ + /// + /// Converts a string representation of a Registry Hive to the corresponding RegistryHive enum value. + /// + /// A string representation of a Registry Hive, not case-sensitive. + /// The RegistryHive enum value corresponding to the provided string representation. + /// Thrown if the provided string does not match a valid Registry Hive. + public RegistryHive ConvertStringToRegistryHive(string HiveString) + { + if (string.IsNullOrEmpty(HiveString)) + throw new ArgumentNullException(nameof(HiveString), "The registry hive string cannot be null or empty. Please provide a valid registry hive string."); + + return HiveString.ToLower() switch + { + "hkcr" or "hkey_classes_root" or "classesroot" => RegistryHive.ClassesRoot, + "hkcu" or "hkey_current_user" or "currentuser" => RegistryHive.CurrentUser, + "hklm" or "hkey_local_machine" or "localmachine" => RegistryHive.LocalMachine, + "hku" or "hkey_users" or "users" => RegistryHive.Users, + "hkcc" or "hkey_current_config" or "currentconfig" => RegistryHive.CurrentConfig, + _ => throw new ArgumentException("Invalid registry hive string.", nameof(HiveString)), + }; + } + + /// + /// Converts a string representation of a RegistryValueKind to the corresponding RegistryValueKind enum value. + /// + /// A string representation of a RegistryValueKind, not case-sensitive. + /// The RegistryValueKind enum value corresponding to the provided string representation. + /// Thrown if the provided string does not match a valid RegistryValueKind. + public RegistryValueKind ConvertStringToRegistryValueKind(string ValueType) + { + if (string.IsNullOrEmpty(ValueType)) + throw new ArgumentNullException(nameof(ValueType), "The registry value type string cannot be null or empty. Please provide a valid registry value type string."); + + return ValueType.ToLower() switch + { + "string" or "reg_sz" => RegistryValueKind.String, + "dword" or "reg_dword" => RegistryValueKind.DWord, + "binary" or "reg_binary" => RegistryValueKind.Binary, + "qword" or "reg_qword" => RegistryValueKind.QWord, + "multistring" or "reg_multi_sz" => RegistryValueKind.MultiString, + "expandstring" or "reg_expand_sz" => RegistryValueKind.ExpandString, + _ => throw new ArgumentException("Invalid RegistryValueKind string representation.", nameof(ValueType)), + }; + } + + + /// + /// Converts a .NET data type to the corresponding RegistryValueKind. + /// + /// The .NET data type to convert. + /// The corresponding RegistryValueKind. + public RegistryValueKind ConvertTypeToRegistryValueKind(Type ValueType) + { + if (ValueType == null) + throw new ArgumentNullException(nameof(ValueType), "The value type argument cannot be null."); + + return Type.GetTypeCode(ValueType) switch + { + TypeCode.String => RegistryValueKind.String, + TypeCode.Int32 => RegistryValueKind.DWord, + TypeCode.Int64 => RegistryValueKind.QWord, + TypeCode.Boolean => RegistryValueKind.DWord, + TypeCode.Byte => RegistryValueKind.Binary, + /* + TypeCode.Single => RegistryValueKind.String, + TypeCode.Double => RegistryValueKind.String; + TypeCode.DateTime => RegistryValueKind.String; + TypeCode.Char => RegistryValueKind.String; + TypeCode.Decimal => RegistryValueKind.String; + */ + _ => RegistryValueKind.String,// Default to String for unsupported types + }; + } + + /// + /// Converts a RegistryValueKind enumeration value to its corresponding .NET Type. + /// + /// The RegistryValueKind value to be converted. + /// The .NET Type that corresponds to the given RegistryValueKind. + public Type ConvertRegistryValueKindToType(RegistryValueKind ValueKind) + { + return ValueKind switch + { + RegistryValueKind.String or RegistryValueKind.ExpandString => typeof(string), + RegistryValueKind.DWord => typeof(int), + RegistryValueKind.QWord => typeof(long), + RegistryValueKind.Binary => typeof(byte[]), + RegistryValueKind.MultiString => typeof(string[]), + _ => typeof(object), + }; + } + + #endregion + + #region throw if invalid methods + + /// + /// Validates the specified RegistryHive value. + /// + /// The RegistryHive value to validate. + /// Thrown when an unknown or unsupported RegistryHive value is provided. + private static void ThrowIfHiveInvalid(RegistryHive Hive) + { + if (!Enum.IsDefined(typeof(RegistryHive), Hive) || Hive == RegistryHive.CurrentConfig || Hive == 0) + throw new ArgumentException("Invalid parameter: Unknown or unsupported RegistryHive value.", nameof(Hive)); + } + + /// + /// Throws an exception if the specified path is null or empty. + /// + /// The path to validate. + /// The validated parameter path. + private static void ThrowIfPathInvalid(string Path) + { + if (string.IsNullOrWhiteSpace(Path)) + throw new ArgumentException("Invalid parameter: Path cannot be null, empty, or consist only of whitespace characters.", nameof(Path)); + } + + /// + /// Throws an exception if the specified name is null or empty. + /// + /// The name to validate. + private static void ThrowIfNameInvalid(string Name) + { + if (Name == null) + throw new ArgumentNullException(nameof(Name), "Invalid parameter: Name cannot be null."); + } + + /// + /// Throws an exception if the specified RegistryValueKind is unknown. + /// + /// The RegistryValueKind to validate. + /// Thrown when the RegistryValueKind is Unknown. + private static void ThrowIfValueKindInvalid(RegistryValueKind ValueKind) + { + if (!Enum.IsDefined(typeof(RegistryValueKind), ValueKind) || ValueKind == RegistryValueKind.Unknown || ValueKind == RegistryValueKind.None) + throw new ArgumentException("Invalid parameter: Unknown or unsupported RegistryValueKind value.", nameof(ValueKind)); + } + + #endregion + } +} \ No newline at end of file diff --git a/mRemoteNG/Tools/WindowsRegistry/WinRegistryEntry.cs b/mRemoteNG/Tools/WindowsRegistry/WinRegistryEntry.cs new file mode 100644 index 000000000..6e7649432 --- /dev/null +++ b/mRemoteNG/Tools/WindowsRegistry/WinRegistryEntry.cs @@ -0,0 +1,904 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Versioning; +using Microsoft.Win32; + +namespace mRemoteNG.Tools.WindowsRegistry +{ + [SupportedOSPlatform("windows")] + /// + /// Represents an entry in the Windows Registry. + /// + /// + /// This class encapsulates the functionality needed to interact with Windows Registry entries, + /// including reading and writing values. It provides a comprehensive set of methods to handle + /// different value types, ensuring flexibility and ease of use. + /// + /// Key features include: + /// - Reading values from a specified registry path and name. + /// - Writing values to a specified registry path and name. + /// - Support for multiple data types such as strings, integers, and binary data. + /// - Ability to specify the registry hive (e.g., HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER). + /// + /// This class is designed to simplify the manipulation of registry entries by providing a + /// straightforward interface for common registry operations. + /// + /// Example usage: + /// + /// var registryEntry = new RegistryEntry(RegistryHive.LocalMachine, @"Software\MyApp", "Settings"); + /// var value = registryEntry.Read(); + /// if (value != **) + /// registryEntry.Write("newVal"); + /// + /// + /// + /// var registryEntry = new RegistryEntry(RegistryHive.LocalMachine, @"Software\MyApp", "Settings").Read(); + /// + /// + /// + /// var registryEntry = new RegistryEntry(RegistryHive.LocalMachine, @"Software\MyApp", "Settings").SetValidation(min, max).Read(); + /// if (registryEntry.IsValid()) + /// Do Something + /// + /// + /// + public class WinRegistryEntry + { + #region Registry Fileds & Properties + + /// + /// Represents the registry hive associated with the registry key. + /// + /// + /// The default value is . + /// + public RegistryHive Hive + { + get { return privateHive; } + set + { + privateHive = + !Enum.IsDefined(typeof(RegistryHive), value) || value == RegistryHive.CurrentConfig || value == RegistryHive.ClassesRoot + ? throw new ArgumentException("Invalid parameter: Unknown or unsupported RegistryHive value.", nameof(Hive)) + : value; + } + } + private RegistryHive privateHive = RegistryHive.CurrentUser; + + /// + /// Represents the path of the registry entry. + /// + public string Path + { + get { return privatePath; } + set + { + privatePath = + !string.IsNullOrWhiteSpace(value) + ? value + : throw new ArgumentNullException(nameof(Path), "Invalid parameter: Path cannot be null, empty, or consist only of whitespace characters."); + } + } + private string privatePath; + + /// + /// Represents the name of the registry entry. + /// + public string Name { get; set; } + + /// + /// Represents the kind of data stored in the registry value. + /// + public RegistryValueKind ValueKind { get; private set; } = InitialRegistryValueKind(); + + /// + /// Represents the value of the registry entry. + /// + public T Value + { + get { return privateValue; } + set + { + privateValue = ValueValidationRules(value); + } + } + private T privateValue; + + #endregion + + #region Aditional Fileds & Properties + + private T[] AllowedValues; + private int? MinInt32Value; + private int? MaxInt32Value; + private long? MinInt64Value; + private long? MaxInt64Value; + private Type EnumType; + + /// + /// Represents the raw value retrieved directly from the registry. + /// + private string RawValue; + + /// + /// Represents the type of the generic parameter T. + /// + private readonly Type ElementType = typeof(T); + + /// + /// Indicates whether the reading operation for the registry value was successful. + /// + private bool ReadOperationSucceeded; + + /// + /// Indicates whether a lock operation should be performed after a successful read operation. + /// + private bool DoLock; + + /// + /// Indicates whether the WinRegistryEntry is currently locked, preventing further read operations. + /// + public bool IsLocked { get; private set; } + + /// + /// Gets a value indicating whether the entry has been explicitly set in the registry. + /// This check is faster as it only verifies if a value was readed (set in registry). + /// + public bool IsSet => IsEntrySet(); + + /// + /// Gets a value indicating whether the entry's value is valid according to custom validation rules. + /// This check includes whether the value has been set and whether it adheres to the defined validation criteria. + /// + public bool IsValid => CheckIsValid(); + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class with default values. + /// + public WinRegistryEntry() { } + + /// + /// Initializes a new instance of the class for reading a default value from the specified registry hive and path. + /// + /// The registry hive of the entry. + /// The path of the registry entry. + public WinRegistryEntry(RegistryHive hive, string path) + { + Hive = hive; + Path = path; + } + + /// + /// Initializes a new instance of the class for writing a default value to the specified registry hive and path. + /// + /// The registry hive of the entry. + /// The path of the registry entry. + /// The value of the registry entry. + public WinRegistryEntry(RegistryHive hive, string path, T value) + { + Hive = hive; + Path = path; + Value = value; + } + + /// + /// Initializes a new instance of the class for reading a specific value from the specified registry hive, path, and name. + /// + /// The registry hive of the entry. + /// The path of the registry entry. + /// The name of the registry entry. + public WinRegistryEntry(RegistryHive hive, string path, string name) + { + Hive = hive; + Path = path; + Name = name; + } + + /// + /// Initializes a new instance of the class for writing a specific value to the specified registry hive, path, and name. + /// + /// The registry hive of the entry. + /// The path of the registry entry. + /// The name of the registry entry. + /// The value of the registry entry. + public WinRegistryEntry(RegistryHive hive, string path, string name, T value) + { + Hive = hive; + Path = path; + Name = name; + Value = value; + } + + #endregion + + #region Factory Methods + + /// + /// Creates a new instance of the class for reading a default value from the specified registry hive and path. + /// + /// The registry hive of the entry. + /// The path of the registry entry. + /// A new instance of the class. + public static WinRegistryEntry New(RegistryHive hive, string path) + { + return new WinRegistryEntry(hive, path); + } + + /// + /// Creates a new instance of the class for writing a value to the specified registry hive and path. + /// + /// The registry hive of the entry. + /// The path of the registry entry. + /// The value of the registry entry. + /// A new instance of the class. + public static WinRegistryEntry New(RegistryHive hive, string path, T value) + { + return new WinRegistryEntry(hive, path, value); + } + + /// + /// Creates a new instance of the class for reading a specific value from the specified registry hive, path, and name. + /// + /// The registry hive of the entry. + /// The path of the registry entry. + /// The name of the registry entry. + /// A new instance of the class. + public static WinRegistryEntry New(RegistryHive hive, string path, string name) + { + return new WinRegistryEntry(hive, path, name); + } + + /// + /// Creates a new instance of the class for writing a specific value to the specified registry hive, path, and name. + /// + /// The registry hive of the entry. + /// The path of the registry entry. + /// The name of the registry entry. + /// The value of the registry entry. + /// A new instance of the class. + public static WinRegistryEntry New(RegistryHive hive, string path, string name, T value) + { + return new WinRegistryEntry(hive, path, name, value); + } + + #endregion + + #region Public Methods + + /// + /// Sets the kind of the registry value, ensuring it is a valid and defined . + /// + /// The registry value kind to set. + /// The current instance of to allow for method chaining. + public WinRegistryEntry SetValueKind(RegistryValueKind valueKind) + { + if (ValueKindValidationRule(valueKind)) + ValueKind = valueKind; + else + // Validation rule returned false, so the initial value will be used for the specified system type. + // Nothing will be changed + LogError("ValueKind is not valid and cannot be set."); + + return this; + } + + /// + /// Sets the allowed values for validation, with an option for case sensitivity. + /// + /// The array of allowed values. + public WinRegistryEntry SetValidation(T[] allowedValues) + { + ResetValidation(); + + if (allowedValues != null && allowedValues.Length > 0) + { + AllowedValues = allowedValues; + } + + return this; + } + + /// + /// Sets up validation using an array of allowed integer values. + /// + /// The array of allowed integer values. + public WinRegistryEntry SetValidation(int[] allowedValues) + { + T[] mappedValues = allowedValues?.Select(value => (T)(object)value).ToArray(); + return SetValidation(mappedValues); + } + + /// + /// Sets up validation using an array of allowed integer values. + /// + /// The array of allowed integer values. + public WinRegistryEntry SetValidation(long[] allowedValues) + { + T[] mappedValues = allowedValues?.Select(value => (T)(object)value).ToArray(); + return SetValidation(mappedValues); + } + + /// + /// Sets up validation for a range of integer values. + /// + /// The minimum value of the range. + /// The maximum value of the range. + public WinRegistryEntry SetValidation(int minValue, int maxValue) + { + ValidateRange(minValue, maxValue); + ResetValidation(); + + MinInt32Value = minValue; + MaxInt32Value = maxValue; + + return this; + } + + /// + /// Sets up validation for a range of integer values. + /// + /// The minimum value of the range. + /// The maximum value of the range. + public WinRegistryEntry SetValidation(long minValue, long maxValue) + { + ValidateRange(minValue, maxValue); + ResetValidation(); + + MinInt64Value = minValue; + MaxInt64Value = maxValue; + + return this; + } + + /// + /// Sets up validation rules for a range of Int32, Int64 values. + /// + /// The minimum value of the range. "*" can be provided to indicate no minimum value. + /// The maximum value of the range. "*" can be provided to indicate no maximum value. + /// The current instance of the WinRegistryEntry class. + /// Thrown when the registry entry type is not a valid Int32 or Int64. + /// Thrown when an invalid minimum value is provided for Int32 or Int64. + /// Thrown when an invalid maximum value is provided for Int32 or Int64. + + public WinRegistryEntry SetValidation(string minValue, string maxValue) + { + if (string.IsNullOrEmpty(minValue) || minValue == "*") + minValue = "0"; + if ((string.IsNullOrEmpty(maxValue) || maxValue == "*")) + maxValue = (ElementType == typeof(int)) + ? Int32.MaxValue.ToString() + : Int64.MaxValue.ToString(); + + if (ElementType == typeof(int)) + { + if (!int.TryParse(minValue, out int minIntValue)) + throw new ArgumentException("Invalid minimum value for Int32."); + if (!int.TryParse(maxValue, out int maxIntValue)) + throw new ArgumentException("Invalid maximum value for Int32."); + + return SetValidation(minIntValue, maxIntValue); + } + else if (ElementType == typeof(long)) + { + if (!long.TryParse(minValue, out long minLongValue)) + throw new ArgumentException("Invalid minimum value for Int64."); + if (!long.TryParse(maxValue, out long maxLongValue)) + throw new ArgumentException("Invalid maximum value for Int64."); + + return SetValidation(minLongValue, maxLongValue); + } + else + { + throw new ArgumentException("Registry entry type must be either a valid Int32 or Int64 to use this validation."); + } + } + + /// + /// Sets the validation to use an enumeration type + /// + /// The enumeration type. + public WinRegistryEntry SetValidation() where TEnum : Enum + { + ResetValidation(); + + Type enumType = typeof(TEnum); + if (enumType != null) + EnumType = enumType; + + return this; + } + + /// + /// Checks if the Windows Registry key is ready for reading by ensuring that the hive, + /// path, and name properties are set. + /// + /// True if the key is ready for reading, otherwise false. + public bool IsReadable() + { + return IsHiveSet() && IsPathSet(); + } + + /// + /// Checks if the Windows Registry key is ready for a write operation. + /// The key is considered write-ready if none of the following conditions are met: + /// - The hive is set + /// - The registry value type is set + /// - The key path is set + /// + /// Returns true if the key is write-ready, otherwise false. + public bool IsWritable() + { + return IsHiveSet() && IsValueKindSet() && IsPathSet(); + } + + /// + /// Reads the value of the registry entry from the specified registry path and assigns it to the Value property. + /// + /// The current instance of to allow for method chaining. + public WinRegistryEntry Read() + { + if (IsLocked) + throw new InvalidOperationException("Operation denied: Cannot read registry entry again. Lock is enabled."); + + if (!IsReadable()) + throw new InvalidOperationException("Unable to read registry key. Hive, path, and name are required."); + + string rawValue = null; + string name = string.IsNullOrEmpty(Name) ? null : Name; + + try + { + using var key = RegistryKey.OpenBaseKey(Hive, RegistryView.Default).OpenSubKey(Path); + if (key != null) + RawValue = rawValue = key.GetValue(name)?.ToString(); + + if (rawValue != null) + ValueKind = key.GetValueKind(name); + } + catch (Exception ex) + { + throw new InvalidOperationException("Error reading the Windows Registry.", ex); + } + + if (string.IsNullOrEmpty(rawValue)) + { + // Issue in Windows registry: Value was null or empty. + string logName = string.IsNullOrEmpty(Name) ? "Default Value" : Name; + LogInfo($"Value for {logName} is Null or Empty"); + } + else if (!ValueKindValidationRule(ValueKind)) + { + // Issue in Windows registry: Value kind of the value cannot be parsed to type T. + LogError($"Cannot parse a Value of type {ValueKind} to the specified type {typeof(T).FullName}."); + } + else + { + Value = ConvertValueBasedOnType(rawValue); + ReadOperationSucceeded = true; + + if (DoLock) + IsLocked = true; + } + + return this; + } + + /// + /// Writes the value of the registry entry to the specified registry path. + /// + /// The current instance of to allow for method chaining. + public WinRegistryEntry Write() + { + if (!IsWritable()) + throw new InvalidOperationException("Unable to write registry key. Hive, path, name, value kind, and value are required."); + + string name = string.IsNullOrEmpty(Name) ? null : Name; + RegistryValueKind valueKind = string.IsNullOrEmpty(Name) ? RegistryValueKind.String : ValueKind; + + string value; + if (typeof(T) == typeof(bool)) + { + value = (bool)(object)Value + ? ValueKind == RegistryValueKind.DWord ? "1" : "True" + : ValueKind == RegistryValueKind.DWord ? "0" : "False"; + } + else + { + value = Value.ToString(); + } + + try + { + using RegistryKey baseKey = RegistryKey.OpenBaseKey(Hive, RegistryView.Default); + using RegistryKey registryKey = baseKey.CreateSubKey(Path, true); + + registryKey.SetValue(name, value, valueKind); + } + catch (Exception ex) + { + throw new InvalidOperationException("Error writing to the Windows Registry.", ex); + } + + return this; + } + + /// + /// Writes a new value to the registry entry. + /// + /// The new value to be written to the registry entry. + /// The current instance of to allow for method chaining. + public WinRegistryEntry Write(T newValue) + { + Value = newValue; + return Write(); + } + + /// + /// Clears the current values of the instance. + /// + /// + /// This method resets the values to their default states. + /// After invoking this method, the "Validations" properties IsSet and IsValid will return false. + /// This is useful in scenarios where the value needs to be validated through alternative mechanisms. + /// + public void Clear() + { + RawValue = null; + Value = default; + ReadOperationSucceeded = false; + } + + /// + /// Locks the WinRegistryEntry to prevent further read operations. + /// If a read operation has already succeeded, the entry is immediately locked. + /// If no read operation has been performed yet, a flag indicating the intention to lock after a successful read operation is set. + /// + /// The current instance of to allow for method chaining. + public WinRegistryEntry Lock() + { + if (ReadOperationSucceeded) + IsLocked = true; + else + DoLock = true; + + return this; + } + + #endregion + + #region Private Methods + + /// + /// Converts a string value to the specified .NET data type . + /// + /// The target .NET data type to which the value is converted. + /// The string value to be converted. + /// The converted value of type . + /// Thrown when Conversion for failed.. + /// Thrown when Conversion not supported for type . + private T ConvertValueBasedOnType(string originalValue) + { + try + { + if (ElementType == typeof(string)) + { + return (T)(object)originalValue; + } + else if (ElementType == typeof(int)) + { + if (int.TryParse(originalValue, out int intValue)) + return (T)(object)intValue; + } + else if (ElementType == typeof(long)) + { + if (long.TryParse(originalValue, out long longValue)) + return (T)(object)longValue; + } + else if (ElementType == typeof(bool)) + { + if (bool.TryParse(originalValue, out bool boolValue)) + return (T)(object)boolValue; + else if (int.TryParse(originalValue, out int intBool)) + return (T)(object)(intBool == 1); + } + } + catch + { + throw new InvalidOperationException($"Conversion for '{ElementType}' failed."); + } + throw new InvalidOperationException($"Conversion not supported for type '{ElementType}'."); + } + + /// + /// Logs an error message to the standard error stream. + /// + /// The error message to log. + private static void LogError(string message) + { + Console.Error.WriteLine($"Error: {message}"); + } + + /// + /// Logs an info message to the standard error stream. + /// + /// The error message to log. + private static void LogInfo(string message) + { + Console.WriteLine($"Info: {message}"); + } + + /// + /// Validates the provided value based on its type-specific rules. + /// + /// The value to be validated. + /// Thrown when is bool and value is not True, False, 0 or 1. + /// Thrown when is int/long and value is negative. + /// Thrown when Value type is not supported. + private T ValueValidationRules(T value) + { + // Boolean values are either string or DWORD. Mapping is needed to update ValueKind. + var booleanRegistryValueKindMap = new Dictionary + { + { "true", RegistryValueKind.String }, + { "false", RegistryValueKind.String }, + { "0", RegistryValueKind.DWord }, + { "1", RegistryValueKind.DWord } + }; + + // For string elements, directly enforce validity and correct the input. + if (ElementType == typeof(string)) + { + return EnforceStringInputValidity(value); + } + // For boolean elements, check if the value is valid and convert it to the appropriate value kind. + else if (ElementType == typeof(bool)) + { + if (!booleanRegistryValueKindMap.TryGetValue(value.ToString().ToLower(), out var valueKind)) + throw new ArgumentException("Invalid value. Supported values are ci strings 'True'/'False' or numbers '0'/'1'.", nameof(value)); + + ValueKind = valueKind; + return value; + } + // For integer or long elements, ensure the value is not negative. + else if (ElementType == typeof(int) || ElementType == typeof(long)) + { + if (Convert.ToInt64(value) < 0) + throw new ArgumentException("Value cannot be negative.", nameof(value)); + return value; + } + // For byte elements, ensure the value is not null or empty. + else if (ElementType == typeof(byte)) + { + if (value == null || ((byte[])(object)value).Length == 0) + throw new ArgumentException("Value cannot be null or empty.", nameof(value)); + return value; + } + + // For unsupported element types, throw an ArgumentException. + throw new ArgumentException($"Value type '{ElementType.FullName}' is not supported.", nameof(value)); + } + + /// + /// Validates and corrects a string value based on a set of allowed values or enumeration values. + /// + /// The input value to be validated and potentially corrected. + /// The validated and potentially corrected value. + private T EnforceStringInputValidity(T value) + { + if (AllowedValues != null) + { + T matchedValue = AllowedValues.FirstOrDefault(v => v.ToString().Equals(value.ToString(), StringComparison.OrdinalIgnoreCase)); + + if (matchedValue != null) + // Correct the Value to ensure the correct spelling and avoid user typing mistakes + return matchedValue; + } + else if (EnumType != null && EnumType.IsEnum) + { + var matchedEnumValue = Enum.GetValues(EnumType) + .Cast() + .FirstOrDefault(e => e.ToString().Equals(value.ToString(), StringComparison.OrdinalIgnoreCase)); + + if (matchedEnumValue != null) + // Correct the Value to ensure the correct spelling and avoid user typing mistakes + return ConvertValueBasedOnType(matchedEnumValue.ToString()); + } + + return value; + } + + /// + /// Private method to validate the range values. + /// + /// The type of the values being validated. + /// The minimum value of the range. + /// The maximum value of the range. + /// The type of registry entry (used for error messages). + private static void ValidateRange(U minValue, U maxValue) where U : IComparable + { + Type typeCode = typeof(U); + + string type = + typeCode == typeof(int) ? "dword" : + typeCode == typeof(long) ? "qword" + : throw new ArgumentException("Registry entry type must be either Int32 or Int64 to use this validation."); + + if (minValue.CompareTo(default(U)) < 0) + throw new ArgumentException($"Negative value not allowed for {type} parameter.", nameof(minValue)); + if (maxValue.CompareTo(default(U)) < 0) + throw new ArgumentException($"Negative value not allowed for {type} parameter.", nameof(maxValue)); + if (minValue.CompareTo(maxValue) > 0) + throw new ArgumentException("MinValue must be less than or equal to MaxValue."); + } + + /// + /// Validates the specified registry value kind. + /// + /// The registry value kind to validate. + /// The validated . + /// Thrown when Invalid parameter: Unknown or unsupported + /// Thrown when Value type is not supported. + private bool ValueKindValidationRule(RegistryValueKind valueKind) + { + if (!Enum.IsDefined(typeof(RegistryValueKind), valueKind) || valueKind == RegistryValueKind.Unknown || valueKind == RegistryValueKind.None || valueKind == 0) + throw new ArgumentException("Invalid parameter: Unknown or unsupported RegistryValueKind value.", nameof(valueKind)); + + return Type.GetTypeCode(ElementType) switch + { + TypeCode.Boolean => valueKind == RegistryValueKind.String || valueKind == RegistryValueKind.DWord, + TypeCode.Int32 => valueKind == RegistryValueKind.DWord, + TypeCode.Int64 => valueKind == RegistryValueKind.QWord, + TypeCode.Byte => valueKind == RegistryValueKind.Binary, + TypeCode.String => valueKind == RegistryValueKind.String || valueKind == RegistryValueKind.DWord || valueKind == RegistryValueKind.QWord, // Strings are compatible with most data types. + _ => throw new ArgumentException($"Value type '{ElementType.FullName}' is not supported.") + }; + } + + /// + /// Determines the initial RegistryValueKind based on the type . + /// + /// The initial RegistryValueKind determined by the type . + private static RegistryValueKind InitialRegistryValueKind() + { + return Type.GetTypeCode(typeof(T)) switch + { + TypeCode.Int32 => RegistryValueKind.DWord, + TypeCode.Int64 => RegistryValueKind.QWord, + TypeCode.Boolean => RegistryValueKind.String, + TypeCode.Byte => RegistryValueKind.Binary, + _ => RegistryValueKind.String, // Default to String for unsupported types + }; + } + + /// + /// Determines whether the registry entry has been set. + /// + private bool IsEntrySet() => RawValue != null && ReadOperationSucceeded; + + /// + /// Determines whether the registry hive has been explicitly set. + /// + /// true if the hive is set; otherwise, false. + private bool IsHiveSet() => Hive != 0; + + /// + /// Determines whether the value kind of the registry entry has been explicitly set. + /// + /// true if the value kind is set; otherwise, false. + private bool IsValueKindSet() => ValueKind != 0; + + /// + /// Determines whether the path of the registry entry has been explicitly set. + /// + /// true if the path is set; otherwise, false. + private bool IsPathSet() => Path != null; + + /// + /// Checks if the current value is valid according to its type-specific rules and constraints. + /// + /// True if the value is valid; otherwise, false. + private bool CheckIsValid() + { + if (!IsEntrySet()) return false; + + return Type.GetTypeCode(ElementType) switch + { + TypeCode.String => ValidateString(), + TypeCode.Boolean => true, + TypeCode.Int32 => ValidateInt32(), + TypeCode.Int64 => ValidateInt64(), + _ => throw new ArgumentException($"Value type '{ElementType.FullName}' is not supported."), + }; + } + + /// + /// Resets all validation setup for the entry to their default values. + /// This includes clearing allowed values, resetting case sensitivity, setting numeric ranges and enum types to null. + /// + private void ResetValidation() + { + AllowedValues = null; + + MinInt32Value = null; + MaxInt32Value = null; + MinInt64Value = null; + MaxInt64Value = null; + + EnumType = null; + } + + /// + /// Validates a string value based on allowed values or enumeration values. + /// + /// True if the string value is valid; otherwise, false. + private bool ValidateString() + { + if (AllowedValues != null) + { + //return AllowedValues.FirstOrDefault(v => v.ToString().Equals(Value.ToString(), StringComparison.OrdinalIgnoreCase)) != null; + return AllowedValues.Contains(Value); + } + else if (EnumType != null && EnumType.IsEnum) + { + return Enum.GetValues(EnumType) + .Cast() + .Any(e => e.ToString().Equals(Value.ToString(), StringComparison.OrdinalIgnoreCase)); + } + + return true; + } + + /// + /// Validates an integer value based on allowed values, minimum and maximum values, or enumeration values. + /// + /// True if the integer value is valid; otherwise, false. + private bool ValidateInt32() + { + int value = (int)(object)Value; + + if (AllowedValues != null) + return AllowedValues.Contains(Value); + + else if (MinInt32Value != null && MaxInt32Value != null) + return value >= MinInt32Value && value <= MaxInt32Value; + + else if (EnumType != null && EnumType.IsEnum) + { + foreach (object enumValue in Enum.GetValues(EnumType)) + { + if (Convert.ToInt32(enumValue) == value) + { + return true; + } + } + + return false; + } + return true; + } + + /// + /// Validates a long integer value based on allowed values, minimum and maximum values, or enumeration values. + /// + /// True if the long integer value is valid; otherwise, false. + private bool ValidateInt64() + { + long value = (long)(object)Value; + + if (AllowedValues != null) + return AllowedValues.Contains(Value); + + else if (MinInt64Value != null && MaxInt64Value != null) + return value >= MinInt64Value && value <= MaxInt64Value; + + return true; + } + + #endregion + } +} diff --git a/mRemoteNGTests/Tools/Registry/RegistryEntryTest/BaseRegistryEntryTest.cs b/mRemoteNGTests/Tools/Registry/RegistryEntryTest/BaseRegistryEntryTest.cs new file mode 100644 index 000000000..3f95d613e --- /dev/null +++ b/mRemoteNGTests/Tools/Registry/RegistryEntryTest/BaseRegistryEntryTest.cs @@ -0,0 +1,248 @@ + +using Microsoft.Win32; +using NUnit.Framework.Internal; +using System.Runtime.Versioning; +using System; +using NUnit.Framework; +using mRemoteNG.Tools.WindowsRegistry; + +namespace mRemoteNGTests.Tools.Registry.RegistryEntryTest +{ + [SupportedOSPlatform("windows")] + internal class BaseRegistryEntryTest + { + private const string TestRoot = @"Software\mRemoteNGTest"; + private const RegistryHive TestHive = RegistryHive.CurrentUser; + private const string TestPath = $"{TestRoot}\\EntryTests"; + + [Test] + public void Path_SetValidValue_GetReturnsSameValue() + { + string expectedPath = @"SOFTWARE\Microsoft"; + var entry = new WinRegistryEntry + { + Path = expectedPath + }; + Assert.That(expectedPath, Is.EqualTo(entry.Path)); + } + + [Test] + public void Path_SetNullValue_ThrowsArgumentNullException() + { + var entry = new WinRegistryEntry(); + Assert.Throws(() => entry.Path = null); + } + + [Test] + public void Name_SetValidValue_GetReturnsSameValue() + { + string expectedName = "Version"; + var entry = new WinRegistryEntry + { + Name = expectedName + }; + Assert.That(expectedName, Is.EqualTo(entry.Name)); + } + + [Test] + public void Value_SetValidValue_GetReturnsSameValue() + { + string expectedValue = "1.0"; + var entry = new WinRegistryEntry + { + Value = expectedValue + }; + Assert.That(expectedValue, Is.EqualTo(entry.Value)); + } + + [Test] + public void ValueKind_SetInvalidValue_ThrowsArgumentException() + { + var entry = new WinRegistryEntry(); + Assert.Throws(() => entry.SetValueKind((RegistryValueKind)100)); + } + + [Test] + public void IsSet_ValueHasBeenSet_NotRead_ReturnsFalse() + { + var entry = new WinRegistryEntry + { + Value = "Test" + }; + Assert.That(entry.IsSet, Is.False); + } + + [Test] + public void IsKeyReadable_AllPropertiesSet_ReturnsTrue() + { + var entry = new WinRegistryEntry + { + Hive = RegistryHive.LocalMachine, + Path = @"SOFTWARE\Microsoft", + Name = "Version" + }; + Assert.That(entry.IsReadable, Is.True); + } + + [Test] + public void IsKeyWritable_AllPropertiesSet_ReturnsTrue() + { + var entry = new WinRegistryEntry + { + Hive = RegistryHive.LocalMachine, + Path = @"SOFTWARE\Microsoft", + Name = "Version", + Value = "1.0" + }; + Assert.That(entry.IsWritable, Is.True); + } + + [Test] + public void Read_WhenRegistryKeyIsNull_ThrowsInvalidOperationException() + { + var entry = new WinRegistryEntry(); + Assert.Throws(() => entry.Read()); + } + + [Test] + public void Write_WhenKeyIsUnwritable_ThrowsInvalidOperationException() + { + var entry = new WinRegistryEntry + { + Name = "Version" + }; + Assert.Throws(() => entry.Write()); + } + + [Test] + public void WriteDefaultAndReadDefault_Entry_DoesNotThrowAndReadsCorrectly() + { + var entry = new WinRegistryEntry(TestHive, TestPath, 0) + { + Hive = TestHive, + Path = TestPath, + Value = 0, + }; + + var readEntry = new WinRegistryEntry + { + Hive = TestHive, + Path = TestPath, + }; + + Assert.That(() => entry.Write(), Throws.Nothing); + Assert.That(() => readEntry.Read(), Throws.Nothing); + Assert.Multiple(() => + { + Assert.That(readEntry.ValueKind, Is.EqualTo(RegistryValueKind.String)); + Assert.That(readEntry.Value, Is.EqualTo(entry.Value)); + }); + } + + [Test] + public void WriteAndRead_Entry_DoesNotThrowAndReadsCorrectly() + { + var entry = new WinRegistryEntry + { + Hive = TestHive, + Path = TestPath, + Name = "TestRead", + Value = 200 + }; + + var readEntry = new WinRegistryEntry + { + Hive = TestHive, + Path = TestPath, + Name = "TestRead" + }; + + Assert.That(() => entry.Write(), Throws.Nothing); + Assert.That(() => readEntry.Read(), Throws.Nothing); + Assert.Multiple(() => + { + Assert.That(readEntry.ValueKind, Is.EqualTo(entry.ValueKind)); + Assert.That(readEntry.Value, Is.EqualTo(entry.Value)); + }); + } + + [Test] + public void FluentWriteAndRead_DoesNotThrow_ReturnsStrJustATest() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "FluentReadAndWriteTest", "JustATest").Write()); + var entry = WinRegistryEntry.New(TestHive, TestPath, "FluentReadAndWriteTest").Read(); + Assert.That(entry.Value, Is.EqualTo("JustATest")); + } + + [Test] + public void FluentWriteReadAndChange_DoesNotThrow_WriteReadsCorrectly() + { + var entry = WinRegistryEntry + .New(TestHive, TestPath, "FluentReadWriteAndChangeTest", "JustASecondTest") + .Write() + .Read(); + string result1 = entry.Value; + + string result2 = entry + .Write("JustAChangeTest") + .Read() + .Value; + + Assert.Multiple(() => + { + Assert.That(result1, Is.EqualTo("JustASecondTest")); + Assert.That(result2, Is.EqualTo("JustAChangeTest")); + }); + } + + [Test] + public void Read_LockAfter_IsLocked() + { + var entry = WinRegistryEntry + .New(TestHive, TestPath, "IsLockedAfter", "After") + .Write() + .Read() + .Lock(); + + Assert.Multiple(() => + { + Assert.That(entry.IsLocked, Is.EqualTo(true)); + }); + } + + [Test] + public void Read_LockBevore_IsLocked() + { + var entry = WinRegistryEntry + .New(TestHive, TestPath, "IsLockedBevore", "Bevore") + .Write() + .Read() + .Lock(); + + Assert.Multiple(() => + { + Assert.That(entry.IsLocked, Is.EqualTo(true)); + }); + } + + [Test] + public void Read_Lock_ThrowWhenRead() + { + var entry = WinRegistryEntry + .New(TestHive, TestPath, "ReadLockThrow", "ReadingIsLocked") + .Write() + .Read() + .Lock(); + + Assert.Throws(() => entry.Read()); + } + + [OneTimeTearDown] + public void Cleanup() + { + // Delete all created tests + WinRegistry winReg = new(); + winReg.DeleteTree(TestHive, TestRoot); + } + } +} diff --git a/mRemoteNGTests/Tools/Registry/RegistryEntryTest/BoolEntryTest.cs b/mRemoteNGTests/Tools/Registry/RegistryEntryTest/BoolEntryTest.cs new file mode 100644 index 000000000..9ba1a35fd --- /dev/null +++ b/mRemoteNGTests/Tools/Registry/RegistryEntryTest/BoolEntryTest.cs @@ -0,0 +1,71 @@ + +using Microsoft.Win32; +using NUnit.Framework.Internal; +using System.Runtime.Versioning; +using NUnit.Framework; +using mRemoteNG.Tools.WindowsRegistry; + +namespace mRemoteNGTests.Tools.Registry.RegistryEntryTest +{ + [SupportedOSPlatform("windows")] + internal class BoolEntryTest + { + private const string TestRoot = @"Software\mRemoteNGTest"; + private const RegistryHive TestHive = RegistryHive.CurrentUser; + private const string TestPath = $"{TestRoot}\\BoolEntryTest"; + + [Test] + public void StringTrue_SuccessfulToBool_ReturnsTrue() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "IsTrueString", true).Write()); + var entry = WinRegistryEntry.New(TestHive, TestPath, "IsTrueString").Read(); + Assert.That(entry.Value, Is.True); + } + + [Test] + public void StringFalse_SuccessfulToBool_ReturnsFalse() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "IsFalseString", false).Write()); + + var entry = WinRegistryEntry.New(TestHive, TestPath, "IsFalseString"); + entry.Value = true; // change Value to true to ensure a value was readed + entry.Read(); + + Assert.That(entry.Value, Is.False); + } + + [Test] + public void DWordTrue_SuccessfulToBool_ReturnsTrue() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "IsTrueDword", true).SetValueKind(RegistryValueKind.DWord).Write()); + var entry = WinRegistryEntry.New(TestHive, TestPath, "IsTrueDword").Read(); + Assert.That(entry.Value, Is.True); + } + + [Test] + public void DWordFalse_SuccessfulToBool_ReturnsFalse() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "IsFalseDword", false).SetValueKind(RegistryValueKind.DWord).Write()); + var entry = WinRegistryEntry.New(TestHive, TestPath, "IsFalseDword"); + entry.Value = true; // change Value to true to ensure a value was readed + entry.Read(); + Assert.That(entry.Value, Is.False); + } + + [Test] + public void FluentWrite_And_Read_DoesNotThrow_ReturnsBoolFalse() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "FluentReadAndWriteTest", false).SetValueKind(RegistryValueKind.DWord).Write()); + var entry = WinRegistryEntry.New(TestHive, TestPath, "FluentReadAndWriteTest", true).Read(); + Assert.That(entry.Value, Is.False); + } + + [OneTimeTearDown] + public void Cleanup() + { + // Delete all created tests + WinRegistry winReg = new(); + winReg.DeleteTree(TestHive, TestRoot); + } + } +} diff --git a/mRemoteNGTests/Tools/Registry/RegistryEntryTest/IntegerEntryTest.cs b/mRemoteNGTests/Tools/Registry/RegistryEntryTest/IntegerEntryTest.cs new file mode 100644 index 000000000..e3fb2072c --- /dev/null +++ b/mRemoteNGTests/Tools/Registry/RegistryEntryTest/IntegerEntryTest.cs @@ -0,0 +1,133 @@ + +using Microsoft.Win32; +using NUnit.Framework.Internal; +using System; +using System.Runtime.Versioning; +using NUnit.Framework; +using mRemoteNG.Tools.WindowsRegistry; + +namespace mRemoteNGTests.Tools.Registry.RegistryEntryTest +{ + [SupportedOSPlatform("windows")] + internal class IntegerEntryTest + { + private const string TestRoot = @"Software\mRemoteNGTest"; + private const RegistryHive TestHive = RegistryHive.CurrentUser; + private const string TestPath = $"{TestRoot}\\IntegerEntryTest"; + + public enum TestEnum + { + First = 1, + Second = 2, + Third = 3 + } + + [Test] + public void IsValid_NoValidationSet_EntryComplete_ReturnsTrue() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "IsValid", 1).Write()); + var entry = WinRegistryEntry.New(TestHive, TestPath, "IsValid").Read(); + Assert.That(entry.IsValid, Is.True); + } + + [Test] + public void IsValid_AllowedValuesSet_ValueInAllowedValues_ReturnsTrue() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "IsValid", 2).Write()); + var entry = WinRegistryEntry.New(TestHive, TestPath, "IsValid").SetValidation(new int[] { 1, 2, 3 }).Read(); + Assert.That(entry.IsValid, Is.True); + } + + [Test] + public void IsValid_AllowedValuesSet_ValueNotInAllowedValues_ReturnsFalse() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "IsValid", 4).Write()); + var entry = WinRegistryEntry.New(TestHive, TestPath, "IsValid").SetValidation(new int[] { 1, 2, 3 }).Read(); + Assert.That(entry.IsValid, Is.False); + } + + [Test] + public void IsValid_RangeSet_ValueInRange_ReturnsTrue() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "IsValid", 5).Write()); + var entry = WinRegistryEntry.New(TestHive, TestPath, "IsValid").SetValidation(1,10).Read(); + Assert.That(entry.IsValid, Is.True); + } + + [Test] + public void IsValid_RangeSet_ValueOutOfRange_ReturnsFalse() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "IsValid", 50).Write()); + var entry = WinRegistryEntry.New(TestHive, TestPath, "IsValid").SetValidation(1, 10).Read(); + Assert.That(entry.IsValid, Is.False); + } + + [Test] + public void IsValid_RangeSet_Value0_ValueOutOfRange_ReturnsTrue() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "IsValidZero", 0).Write()); + var entry = WinRegistryEntry.New(TestHive, TestPath, "IsValidZero").SetValidation(0, 10).Read(); + Assert.That(entry.IsValid, Is.True); + } + + [Test] + public void IsValid_RangeSet_DefaultMin_ValueInRange_ReturnsTrue() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "IsValidDefMin", 10).Write()); + var entry = WinRegistryEntry.New(TestHive, TestPath, "IsValidDefMin").SetValidation("*", "10").Read(); + Assert.That(entry.IsValid, Is.True); + } + + [Test] + public void IsValid_RangeSet_DefaultMax_ValueInRange_ReturnsTrue() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "IsValidDefMax", 1000).Write()); + var entry = WinRegistryEntry.New(TestHive, TestPath, "IsValidDefMax").SetValidation("50", "*").Read(); + Assert.That(entry.IsValid, Is.True); + } + + [Test] + public void IsValid_EnumSet_ValueInEnum_ReturnsTrue() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "IsValid", 2).Write()); + var entry = WinRegistryEntry.New(TestHive, TestPath, "IsValid").SetValidation().Read(); + Assert.That(entry.IsValid, Is.True); + } + + [Test] + public void IsValid_EnumSet_ValueNotInEnum_ReturnsFalse() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "IsValid", 5).Write()); + var entry = WinRegistryEntry.New(TestHive, TestPath, "IsValid").SetValidation().Read(); + Assert.That(entry.IsValid, Is.False); + } + + [Test] + public void IsValid_InvalidValueKind_ThrowArgumentException() + { + Assert.Throws(() => WinRegistryEntry.New(TestHive, TestPath, "IsValid", 5).SetValueKind(RegistryValueKind.Unknown)); + } + + [Test] + public void Value_SetToNegativeNumber_ThrowArgumentException() + { + Assert.Throws(() => WinRegistryEntry.New(TestHive, TestPath, "IsValid", -100)); + } + + [Test] + public void FluentWrite_And_Read_DoesNotThrow_ReturnsInt12() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "FluentReadAndWriteTest", 12).Write()); + var entry = WinRegistryEntry.New(TestHive, TestPath, "FluentReadAndWriteTest", 42).Read(); + Assert.That(entry.Value, Is.EqualTo(12)); + } + + [OneTimeTearDown] + public void Cleanup() + { + // Delete all created tests + WinRegistry winReg = new(); + winReg.DeleteTree(TestHive, TestPath); + } + } +} diff --git a/mRemoteNGTests/Tools/Registry/RegistryEntryTest/LongIntegerEntryTest.cs b/mRemoteNGTests/Tools/Registry/RegistryEntryTest/LongIntegerEntryTest.cs new file mode 100644 index 000000000..d7e5bc90b --- /dev/null +++ b/mRemoteNGTests/Tools/Registry/RegistryEntryTest/LongIntegerEntryTest.cs @@ -0,0 +1,86 @@ + +using Microsoft.Win32; +using NUnit.Framework.Internal; +using System; +using System.Runtime.Versioning; +using NUnit.Framework; +using mRemoteNG.Tools.WindowsRegistry; + +namespace mRemoteNGTests.Tools.Registry.RegistryEntryTest +{ + [SupportedOSPlatform("windows")] + internal class LongIntegerEntryTest + { + private const string TestRoot = @"Software\mRemoteNGTest"; + private const RegistryHive TestHive = RegistryHive.CurrentUser; + private const string TestPath = $"{TestRoot}\\LongIntegerEntryTest"; + + [Test] + public void IsValid_NoValidationSet_EntryComplete_ReturnsTrue() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "IsValid", 3047483647).Write()); + var entry = WinRegistryEntry.New(TestHive, TestPath, "IsValid").Read(); + Assert.That(entry.IsValid, Is.True); + } + + [Test] + public void IsValid_AllowedValuesSet_ValueInAllowedValues_ReturnsTrue() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "IsValid", 2147483649).Write()); + var entry = WinRegistryEntry.New(TestHive, TestPath, "IsValid").SetValidation(new long[] { 2147483648, 2147483649, 2147483650 }).Read(); + Assert.That(entry.IsValid, Is.True); + } + + [Test] + public void IsValid_AllowedValuesSet_ValueNotInAllowedValues_ReturnsFalse() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "IsValid", 4).Write()); + var entry = WinRegistryEntry.New(TestHive, TestPath, "IsValid").SetValidation(new long[] { 2147483648, 2147483649, 2147483650 }).Read(); + Assert.That(entry.IsValid, Is.False); + } + + [Test] + public void IsValid_RangeSet_ValueInRange_ReturnsTrue() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "IsValid", 2147483652).Write()); + var entry = WinRegistryEntry.New(TestHive, TestPath, "IsValid").SetValidation(2147483647, 2200000000).Read(); + Assert.That(entry.IsValid, Is.True); + } + + [Test] + public void IsValid_RangeSet_ValueOutOfRange_ReturnsFalse() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "IsValid", 20).Write()); + var entry = WinRegistryEntry.New(TestHive, TestPath, "IsValid").SetValidation(2147483647, 2200000000).Read(); + Assert.That(entry.IsValid, Is.False); + } + + [Test] + public void IsValid_InvalidValueKind_ThrowArgumentException() + { + Assert.Throws(() => WinRegistryEntry.New(TestHive, TestPath, "IsValid", 5).SetValueKind(RegistryValueKind.Unknown)); + } + + [Test] + public void Value_SetToNegativeNumber_ThrowArgumentException() + { + Assert.Throws(() => WinRegistryEntry.New(TestHive, TestPath, "IsValid", -100)); + } + + [Test] + public void FluentWrite_And_Read_DoesNotThrow_ReturnsLong15() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "FluentReadAndWriteTest", 15).Write()); + var entry = WinRegistryEntry.New(TestHive, TestPath, "FluentReadAndWriteTest", 42).Read(); + Assert.That(entry.Value, Is.EqualTo(15)); + } + + [OneTimeTearDown] + public void Cleanup() + { + // Delete all created tests + WinRegistry winReg = new(); + winReg.DeleteTree(TestHive, TestRoot); + } + } +} diff --git a/mRemoteNGTests/Tools/Registry/RegistryEntryTest/StringEntryTest.cs b/mRemoteNGTests/Tools/Registry/RegistryEntryTest/StringEntryTest.cs new file mode 100644 index 000000000..80fd4d9ff --- /dev/null +++ b/mRemoteNGTests/Tools/Registry/RegistryEntryTest/StringEntryTest.cs @@ -0,0 +1,112 @@ + +using Microsoft.Win32; +using NUnit.Framework.Internal; +using System; +using System.Runtime.Versioning; +using NUnit.Framework; +using mRemoteNG.Tools.WindowsRegistry; + +namespace mRemoteNGTests.Tools.Registry.RegistryEntryTest +{ + [SupportedOSPlatform("windows")] + internal class StringEntryTest + { + private const string TestRoot = @"Software\mRemoteNGTest"; + private const RegistryHive TestHive = RegistryHive.CurrentUser; + private const string TestPath = $"{TestRoot}\\StringEntryTest"; + + public enum TestEnum + { + First = 1, + Second = 2, + Third = 3 + } + + [Test] + public void IsValid_NoValidationSet_EntryComplete_ReturnsTrue() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "IsValid", "IsValid").Write()); + var entry = WinRegistryEntry.New(TestHive, TestPath, "IsValid").Read(); + Assert.That(entry.IsValid, Is.True); + } + + [Test] + public void IsValid_AllowedValuesSet_ValueInAllowedValues_ReturnsTrue() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "ArrayIsValid", "Banana").Write()); + var entry = WinRegistryEntry.New(TestHive, TestPath, "ArrayIsValid").SetValidation(new string[] { "Banana", "Strawberry", "Apple" }).Read(); + Assert.That(entry.IsValid, Is.True); + } + + [Test] + public void IsValid_AllowedValuesSet_ValueNotInAllowedValues_ReturnsFalse() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "ArrayIsInValid", "Cheese").Write()); + var entry = WinRegistryEntry.New(TestHive, TestPath, "ArrayIsInValid").SetValidation(new string[] { "Banana", "Strawberry", "Apple" }).Read(); + Assert.That(entry.IsValid, Is.False); + } + + [Test] + public void IsValid_AllowedValuesSet_CorrectsValueSpellingAndValidatesSuccessfully() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "ArrayCorrectsSpellingIsValid", "StrawBerry").Write()); + var entry = WinRegistryEntry.New(TestHive, TestPath, "ArrayCorrectsSpellingIsValid").SetValidation(new string[] { "Banana", "Strawberry", "Apple" }).Read(); + + Assert.Multiple(() => + { + Assert.That(entry.IsValid, Is.True); + Assert.That(entry.Value, Is.EqualTo("Strawberry")); + }); + } + + [Test] + public void IsValid_EnumSet_ValueInEnum_ReturnsTrue() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "IsValid", "Second").Write()); + var entry = WinRegistryEntry.New(TestHive, TestPath, "IsValid").SetValidation().Read(); + Assert.That(entry.IsValid, Is.True); + } + + [Test] + public void IsValid_EnumSet_ValueNotInEnum_ReturnsFalse() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "IsInValid", "Fourth").Write()); + var entry = WinRegistryEntry.New(TestHive, TestPath, "IsInValid").SetValidation().Read(); + Assert.That(entry.IsValid, Is.False); + } + + [Test] + public void IsValid_EnumSet_CorrectsValueSpellingAndValidatesSuccessfully() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "IsValidCorrectsSpelling", "SecOND").Write()); + var entry = WinRegistryEntry.New(TestHive, TestPath, "IsValidCorrectsSpelling").SetValidation().Read(); + Assert.Multiple(() => + { + Assert.That(entry.IsValid, Is.True); + Assert.That(entry.Value, Is.EqualTo("Second")); + }); + } + + [Test] + public void IsValid_InvalidValueKind_ThrowArgumentException() + { + Assert.Throws(() => WinRegistryEntry.New(TestHive, TestPath, "IsValid", "Windows").SetValueKind(RegistryValueKind.Unknown)); + } + + [Test] + public void FluentWrite_And_Read_DoesNotThrow_ReturnsStrJustATest() + { + Assert.DoesNotThrow(() => WinRegistryEntry.New(TestHive, TestPath, "FluentReadAndWriteTest", "JustATest").Write()); + var entry = WinRegistryEntry.New(TestHive, TestPath, "FluentReadAndWriteTest", "TestFailed").Read(); + Assert.That(entry.Value, Is.EqualTo("JustATest")); + } + + [OneTimeTearDown] + public void Cleanup() + { + // Delete all created tests + WinRegistry winReg = new(); + winReg.DeleteTree(TestHive, TestRoot); + } + } +} diff --git a/mRemoteNGTests/Tools/Registry/WindowsRegistryTests.cs b/mRemoteNGTests/Tools/Registry/WindowsRegistryTests.cs index 2ee7ff0dd..40a4de20f 100644 --- a/mRemoteNGTests/Tools/Registry/WindowsRegistryTests.cs +++ b/mRemoteNGTests/Tools/Registry/WindowsRegistryTests.cs @@ -1,195 +1,756 @@ using System; +using Microsoft.Win32; +using System.Runtime.Versioning; using System.Collections.Generic; using System.Linq; -using System.Windows.Forms; -using Microsoft.Win32; +using System.Reflection; using mRemoteNG.Tools.WindowsRegistry; using NUnit.Framework; namespace mRemoteNGTests.Tools.Registry { - public class WindowsRegistryTests + [SupportedOSPlatform("windows")] + internal class WindowsRegistryTests : WinRegistry { - private IRegistryRead _registryReader; - private IRegistryWrite _registryWriter; + private const string TestRoot = @"Software\mRemoteNGTest"; + private const RegistryHive TestHive = RegistryHive.CurrentUser; + private const string TestPath = $"{TestRoot}\\WinRegistryTests"; [SetUp] public void Setup() { - _registryReader = new WindowsRegistry(); - _registryWriter = new WindowsRegistry(); } - #region GetSubKeyNames() tests - [Test] - public void CanGetSubkeyNames() + #region WindowsRegistry.ThrowIfHiveInvalid() + + private static MethodInfo? GetPrivateStaticMethod(string methodName) { - var subKeyNames = _registryReader.GetSubKeyNames(RegistryHive.CurrentUser, "Software"); - Assert.That(subKeyNames, Does.Contain("Microsoft")); + return typeof(WinRegistry).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static); } [Test] - public void GetSubkeyNamesThrowsIfGivenNullKeyPath() + public void ThrowIfHiveInvalid_ValidHive_DoesNotThrow() { - Assert.Throws(() => _registryReader.GetSubKeyNames(RegistryHive.CurrentUser, null)); + var method = GetPrivateStaticMethod("ThrowIfHiveInvalid"); + if (method != null) + Assert.DoesNotThrow(() => method.Invoke(null, new object[] { RegistryHive.LocalMachine })); + else + Assert.Fail("The method ThrowIfHiveInvalid could not be found."); } [Test] - public void GetSubkeyNamesThrowsIfGivenUnknownHive() + public void ThrowIfHiveInvalid_CurrentConfig_ThrowsArgumentException() { - Assert.Throws(() => _registryReader.GetSubKeyNames(new RegistryHive(), "Software")); + var method = GetPrivateStaticMethod("ThrowIfHiveInvalid"); + if (method != null) + Assert.Throws(() => method.Invoke(null, new object[] { null })); + else + Assert.Fail("The method ThrowIfHiveInvalid could not be found."); } + + [Test] + public void ThrowIfHiveInvalid_InvalidHive_ThrowsArgumentException() + { + var method = GetPrivateStaticMethod("ThrowIfHiveInvalid"); + if (method != null) + Assert.Throws(() => method ? .Invoke(null, new object[] { (RegistryHive)100 })); + else + Assert.Fail("The method ThrowIfHiveInvalid could not be found."); + } + #endregion - #region GetPropertyValue() tests + #region WindowsRegistry.ThrowIfPathInvalid() + [Test] - public void CanGetPropertyValue() + public void ThrowIfPathInvalid_ValidPath_DoesNotThrow() { - var keyValue = _registryReader.GetPropertyValue(RegistryHive.ClassesRoot, @".dll\PersistentHandler", ""); - Assert.That(keyValue, Is.EqualTo("{098f2470-bae0-11cd-b579-08002b30bfeb}")); + var method = GetPrivateStaticMethod("ThrowIfPathInvalid"); + if (method != null) + Assert.DoesNotThrow(() => method.Invoke(null, new object[] { @"SOFTWARE\Microsoft" })); + else + Assert.Fail("The method ThrowIfPathInvalid could not be found."); } [Test] - public void CanGetPropertyValueByRegistryKeyObject() + public void ThrowIfPathInvalid_NullPath_ThrowsArgumentException() { - WindowsRegistryKey key = new() - { - Hive = RegistryHive.ClassesRoot, - Path = @".dll\PersistentHandler", - Name = "" - }; - - var keyValue = _registryReader.GetPropertyValue(key); - Assert.That(keyValue, Is.EqualTo("{098f2470-bae0-11cd-b579-08002b30bfeb}")); - } - [Test] - public void GetPropertyValueThrowsIfGivenNullKeyPath() - { - Assert.Throws(() => _registryReader.GetPropertyValue(RegistryHive.CurrentUser, null, "")); + var method = GetPrivateStaticMethod("ThrowIfPathInvalid"); + if (method != null) + Assert.Throws(() => method.Invoke(null, new object[] { null })); + else + Assert.Fail("The method ThrowIfPathInvalid could not be found."); } [Test] - public void GetPropertyValueThrowsIfGivenNullPropertyName() + public void ThrowIfPathInvalid_EmptyPath_ThrowsArgumentException() { - Assert.Throws(() => _registryReader.GetPropertyValue(RegistryHive.CurrentUser, "", null)); + var method = GetPrivateStaticMethod("ThrowIfPathInvalid"); + if (method != null) + Assert.Throws(() => method.Invoke(null, new object[] { string.Empty })); + else + Assert.Fail("The method ThrowIfPathInvalid could not be found."); } + + [Test] + public void ThrowIfPathInvalid_WhitespacePath_ThrowsArgumentException() + { + var method = GetPrivateStaticMethod("ThrowIfPathInvalid"); + if (method != null) + Assert.Throws(() => method.Invoke(null, new object[] { " " })); + else + Assert.Fail("The method ThrowIfPathInvalid could not be found."); + } + #endregion - #region GetWindowsRegistryKey() tests + #region WindowsRegistry.ThrowIfNameInvalid() + [Test] - public void CanGetWindowsRegistryKey() + public void ThrowIfNameInvalid_ValidName_DoesNotThrow() { - WindowsRegistryKey keyValue = _registryReader.GetWindowsRegistryKey(RegistryHive.ClassesRoot, @".dll\PersistentHandler", ""); - Assert.That(keyValue.Value, Is.EqualTo("{098f2470-bae0-11cd-b579-08002b30bfeb}")); + var method = GetPrivateStaticMethod("ThrowIfNameInvalid"); + if (method != null) + Assert.DoesNotThrow(() => method.Invoke(null, new object[] { "TestName" })); + else + Assert.Fail("The method ThrowIfNameInvalid could not be found."); } [Test] - public void CanGetWindowsRegistryKeyByObject() + public void ThrowIfNameInvalid_NullName_ThrowsArgumentNullException() { - WindowsRegistryKey key = new() - { - Hive = RegistryHive.ClassesRoot, - Path = @".dll\PersistentHandler", - Name = "" - }; + var method = GetPrivateStaticMethod("ThrowIfNameInvalid"); + if (method != null) + Assert.Throws(() => method.Invoke(null, new object[] { null })); + else + Assert.Fail("The method ThrowIfNameInvalid could not be found."); + } - WindowsRegistryKey keyValue = _registryReader.GetWindowsRegistryKey(key); - Assert.That(keyValue.Value, Is.EqualTo("{098f2470-bae0-11cd-b579-08002b30bfeb}")); + #endregion + + #region WindowsRegistry.ThrowIfValueKindInvalid() + + [Test] + public void ThrowIfValueKindInvalid_ValidValueKind_DoesNotThrow() + { + var method = GetPrivateStaticMethod("ThrowIfValueKindInvalid"); + if (method != null) + Assert.DoesNotThrow(() => method.Invoke(null, new object[] { RegistryValueKind.String })); + else + Assert.Fail("The method ThrowIfValueKindInvalid could not be found."); } [Test] - public void CanGetWindowsRegistryKeyForKeyNotExists() + public void ThrowIfValueKindInvalid_InvalidValueKind_ThrowsArgumentException() { - // No exception. Only null value - WindowsRegistryKey keyValue = _registryReader.GetWindowsRegistryKey(RegistryHive.LocalMachine, @"Software\Testabcdefg", "abcdefg"); - Assert.That(keyValue.Value, Is.EqualTo(null)); + var method = GetPrivateStaticMethod("ThrowIfValueKindInvalid"); + if (method != null) + Assert.Throws(() => method.Invoke(null, new object[] { (RegistryValueKind)100 })); + else + Assert.Fail("The method ThrowIfValueKindInvalid could not be found."); } [Test] - public void GetWindowsRegistryThrowNotReadable() + public void ThrowIfValueKindInvalid_ValueKindUnknown_ThrowsArgumentException() { - WindowsRegistryKey key = new() - { - Hive = RegistryHive.ClassesRoot, - }; + var method = GetPrivateStaticMethod("ThrowIfValueKindInvalid"); + if (method != null) + Assert.Throws(() => method.Invoke(null, new object[] { RegistryValueKind.Unknown })); + else + Assert.Fail("The method ThrowIfValueKindInvalid could not be found."); + } + + [Test] + public void ThrowIfValueKindInvalid_ValueKindNone_ThrowsArgumentException() + { + var method = GetPrivateStaticMethod("ThrowIfValueKindInvalid"); + if (method != null) + Assert.Throws(() => method.Invoke(null, new object[] { RegistryValueKind.None })); + else + Assert.Fail("The method ThrowIfValueKindInvalid could not be found."); + } + + #endregion + + #region WindowsRegistry.SetValue() + + [Test] + public void SetValue_NewKey_SetsValueSuccessfully() + { + const string testName = "TestValue"; + const string testValue = "Test1"; + const string testPath = TestPath + @"\SetValue"; + Assert.DoesNotThrow(() => SetValue(TestHive, testPath, testName, testValue, RegistryValueKind.String)); + + string key = GetStringValue(TestHive, testPath, testName, testValue); + Assert.That(key, Is.EqualTo(testValue)); + } + + [Test] + public void SetValue_OverwriteValue_SetsValueSuccessfully() + { + const string testName = "TestValue"; + const string testValue = "Test2"; + const string testPath = TestPath + @"\SetValue"; + Assert.DoesNotThrow(() => SetValue(TestHive, testPath, testName, testValue, RegistryValueKind.String)); + + string key = GetStringValue(TestHive, testPath, testName, testValue); + Assert.That(key, Is.EqualTo(testValue)); + } + + [Test] + public void SetValue_DefaultValueWithWrongValueKind_SetsValueSuccessfully() + { + const string? testName = null; + const string testValue = "SetDefault"; + const string testPath = TestPath + @"\SetValue"; + + Assert.DoesNotThrow(() => SetValue(TestHive, testPath, testName, testValue, RegistryValueKind.Unknown)); + + string key = GetStringValue(TestHive, testPath, testName, testValue); + Assert.That(key, Is.EqualTo(testValue)); + } + + #endregion + + #region WindowsRegistry.CreateKey() + + [Test] + public void CreateKey_NewKey_CreatesKeySuccessfully() + { + const string testPath = TestPath + @"\TestSubKey"; + Assert.DoesNotThrow(() => CreateKey(TestHive, testPath)); + } + + [Test] + public void CreateKey_ExistingKey_DoesNotThrow() + { + const string testPath = TestPath + @"\TestSubKey"; + Assert.DoesNotThrow(() => CreateKey(TestHive, testPath)); + } + + [Test] + public void CreateKey_InvalidHive_ThrowsArgumentException() + { + Assert.Throws(() => CreateKey((RegistryHive)(-1), @"Software\Test")); + } + + #endregion + + #region WindowsRegistry.GetSubKeyNames() + + [Test] + public void GetSubKeyNames_ExistingKey_ReturnsSubKeyNames() + { + const string testPath = TestPath + @"\GetSubKeyNames"; + const string testSubKey1 = testPath + @"\subkey1"; + const string testSubKey2 = testPath + @"\subkey2"; + const string testSubKey3 = testPath + @"\subkey3"; + + CreateKey(TestHive, testSubKey1); + CreateKey(TestHive, testSubKey2); + CreateKey(TestHive, testSubKey3); + + string[] subKeyNames = GetSubKeyNames(TestHive, testPath); + + Assert.That(subKeyNames.Length, Is.EqualTo(3)); + Assert.That(subKeyNames, Contains.Item("subkey1")); + Assert.That(subKeyNames, Contains.Item("subkey2")); + Assert.That(subKeyNames, Contains.Item("subkey3")); + } - Assert.Throws(() => _registryReader.GetWindowsRegistryKey(key)); + [Test] + public void GetSubKeyNames_NonExistingKey_ReturnsEmptyArray() + { + string[] subKeyNames = GetSubKeyNames(TestHive, @"Software\NonExistingKey"); + Assert.That(subKeyNames, Is.Empty); } + #endregion - #region GetRegistryEntries() + Recurse tests + #region WindowsRegistry.GetValue() + [Test] - public void CanGetRegistryEntries() + public void GetValue_RetrieveValue_ReturnsValue() { - List keys = _registryReader.GetRegistryEntries(RegistryHive.LocalMachine, @"HARDWARE\DESCRIPTION\System\BIOS"); - Assert.That(keys.Count, Is.Not.EqualTo(0)); + const string testPath = TestPath + @"\GetValue"; + const string testName = "TestValue"; + const string testValue = "Test"; + + Assert.DoesNotThrow(() => SetValue(TestHive, testPath, testName, testValue, RegistryValueKind.String)); + + string key = GetValue(TestHive, testPath, testName); + Assert.That(key, Is.EqualTo(testValue)); } [Test] - public void CanGetRegistryEntriesRecurse() + public void GetValue_RetrieveDefault_RetrunsValue() { - List keys = _registryReader.GetRegistryEntryiesRecursive(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\Windows\Windows Search"); - Assert.That(keys.Count, Is.Not.EqualTo(0)); + const string testPath = TestPath + @"\GetValue"; + const string testName = ""; + const string testValue = "TestDefault{098f2470-bae0-11cd-b579-08002b30bfeb}"; + + Assert.DoesNotThrow(() => SetValue(TestHive, testPath, testName, testValue, RegistryValueKind.String)); + + var key = GetValue(TestHive, testPath, testName); + Assert.That(key, Is.EqualTo(testValue)); } + + [Test] + public void GetValue_NonExistingValue_ReturnsNull() + { + const string testPath = TestPath + @"\GetValue"; + const string nonExistingName = "NonExistingValue"; + + string key = GetValue(TestHive, testPath, nonExistingName); + Assert.That(key, Is.Null); + } + + [Test] + public void SetAndGetDefaultValue_RetrieveValue_ReturnsValue() + { + const string testPath = TestPath + @"\DefautValue"; + const string testValue = "DefaultTestTestValue"; + + Assert.DoesNotThrow(() => SetDefaultValue(TestHive, testPath, testValue)); + + string key = GetDefaultValue(TestHive, testPath); + Assert.That(key, Is.EqualTo(testValue)); + } + #endregion - #region new WindowsRegistryKey() tests + #region WindowsRegistry.GetStringValue() + [Test] - public void IsWindowsRegistryKeyValid() + public void GetStringValue_RetrieveValue_ReturnsValue() { - // Tests property rules of WindowsRegistryKey - WindowsRegistryKey key = new(); + const string testPath = TestPath + @"\GetStringValue"; + const string testName = "TestValue"; + const string testValue = "Test"; + + Assert.DoesNotThrow(() => SetValue(TestHive, testPath, testName, testValue, RegistryValueKind.String)); + + var key = GetStringValue(TestHive, testPath, testName); + Assert.That(key, Is.TypeOf().And.EqualTo(testValue)); + } + + [Test] + public void GetStringValue_RetrieveDefault_ReturnsValue() + { + const string testPath = TestPath + @"\GetStringValue"; + const string testName = ""; + const string testValue = "TestDefault{098f2470-bae0-11cd-b579-08002b30bfeb}"; + + Assert.DoesNotThrow(() => SetValue(TestHive, testPath, testName, testValue, RegistryValueKind.String)); + + var key = GetStringValue(TestHive, testPath, testName); + Assert.That(key, Is.TypeOf().And.EqualTo(testValue)); + } + + [Test] + public void GetStringValue_NonExistingValue_ReturnsNull() + { + const string testPath = TestPath + @"\GetStringValue"; + const string nonExistingName = "NonExistingValue"; + + var key = GetStringValue(TestHive, testPath, nonExistingName); + Assert.That(key, Is.Null); + } + + [Test] + public void GetStringValue_NonExistingValue_ReturnsDefault() + { + const string testPath = TestPath + @"\GetStringValue"; + const string nonExistingName = "NonExistingValue"; + const string defaultValue = "DefaultValue"; + + string key = GetStringValue(TestHive, testPath, nonExistingName, defaultValue); + Assert.That(key, Is.TypeOf().And.EqualTo(defaultValue)); + } + + #endregion + + #region WindowsRegistry.GetBoolValue() + + [Test] + public void GetBoolValue_FromString_ReturnsTrue() + { + const string testPath = TestPath + @"\GetBoolValue"; + const string testName = "strBool_true"; + const string testValue = "true"; + Assert.DoesNotThrow(() => SetValue(TestHive, testPath, testName, testValue, RegistryValueKind.String)); + + var key = GetBoolValue(TestHive, testPath, testName, false); // set default to false + Assert.That(key, Is.TypeOf().And.EqualTo(true)); + } + + [Test] + public void GetBoolValue_FromString_ReturnsFalse() + { + const string testPath = TestPath + @"\GetBoolValue"; + const string testName = "strBool_false"; + const string testValue = "false"; + Assert.DoesNotThrow(() => SetValue(TestHive, testPath, testName, testValue, RegistryValueKind.String)); + + var key = GetBoolValue(TestHive, testPath, testName, true); // set default to true + Assert.That(key, Is.TypeOf().And.EqualTo(false)); + } + + [Test] + public void GetBoolValue_FromDword_ReturnsFalse() + { + const string testPath = TestPath + @"\GetBoolValue"; + const string testName = "intBool_false"; + const int testValue = 0; + Assert.DoesNotThrow(() => SetValue(TestHive, testPath, testName, testValue, RegistryValueKind.DWord)); + + var key = GetBoolValue(TestHive, testPath, testName, true); // set default to true + Assert.That(key, Is.TypeOf().And.EqualTo(false)); + } + + [Test] + public void GetBoolValue_FromDword_ReturnsTrue() + { + const string testPath = TestPath + @"\GetBoolValue"; + const string testName = "intBool_true"; + const int testValue = 1; + Assert.DoesNotThrow(() => SetValue(TestHive, testPath, testName, testValue, RegistryValueKind.DWord)); + + var key = GetBoolValue(TestHive, testPath, testName, false); // set default to false + Assert.That(key, Is.TypeOf().And.EqualTo(true)); + } + + [Test] + public void GetBoolValue_NonExistingValue_ReturnsFalse() + { + const string testPath = TestPath + @"\GetStringValue"; + const string nonExistingName = "NonExistingValue"; + + var key = GetBoolValue(TestHive, testPath, nonExistingName); + Assert.That(key, Is.TypeOf().And.EqualTo(false)); + } + + #endregion + + #region WindowsRegistry.GetDwordValue() + + [Test] + public void GetIntegerValue_RetrieveValue_ReturnsValue() + { + const string testPath = TestPath + @"\GetIntegerValue"; + const string testName = "ExistingDword"; + const int testValue = 2; + Assert.DoesNotThrow(() => SetValue(TestHive, testPath, testName, testValue, RegistryValueKind.DWord)); + + var key = GetIntegerValue(TestHive, testPath, testName); + Assert.That(key, Is.TypeOf().And.EqualTo(testValue)); + } + + [Test] + public void GetIntegerValue_NonExistingValue_ReturnsZero() + { + const string testPath = TestPath + @"\GetStringValue"; + const string nonExistingName = "NonExistingValue"; + + var key = GetIntegerValue(TestHive, testPath, nonExistingName); + Assert.That(key, Is.TypeOf().And.EqualTo(0)); + } + + [Test] + public void GetIntegerValue_NotExistingKey_ReturnsDefault() + { + const string testPath = TestPath + @"\GetStringValue"; + const string testName = "NotExistingDword"; + + var key = GetIntegerValue(TestHive, testPath, testName, 12); // set default to true + Assert.That(key, Is.TypeOf().And.EqualTo(12)); + } + + #endregion + + #region WindowsRegistry.GetEntry() + + [Test] + public void GetRegistryEntry_ReturnsCorrectEntry() + { + const string testName = "TestValue"; + const int testValue = 2011; + const string testPath = TestPath + @"\GetRegistryEntry"; + Assert.DoesNotThrow(() => SetValue(TestHive, testPath, testName, testValue, RegistryValueKind.DWord)); + + var entry = GetEntry(TestHive, testPath, testName); Assert.Multiple(() => { - Assert.DoesNotThrow(() => key.Hive = RegistryHive.CurrentUser); - Assert.DoesNotThrow(() => key.ValueKind = RegistryValueKind.String); - Assert.DoesNotThrow(() => key.Path = "Software"); - Assert.DoesNotThrow(() => key.Name = "NotThereButOK"); - //Assert.DoesNotThrow(() => key.Value = "Equal", ""); + Assert.That(entry.Hive, Is.EqualTo(TestHive)); + Assert.That(entry.Path, Is.EqualTo(testPath)); + Assert.That(entry.Name, Is.EqualTo(testName)); + Assert.That(entry.ValueKind, Is.EqualTo(RegistryValueKind.DWord)); + Assert.That(entry.IsSet, Is.EqualTo(true)); }); - - } - [Test] - public void WindowsRegistryKeyThrowHiveNullException() - { - WindowsRegistryKey key = new(); - Assert.Throws(() => key.Hive = 0, "Expected IsHiveValid to throw ArgumentNullException"); } - [Test] - public void WindowsRegistryKeyValueKindUnknown() - { - WindowsRegistryKey key = new(); - Assert.That(key.ValueKind, Is.EqualTo(RegistryValueKind.Unknown)); + #endregion + + #region WinRegistryEC.GetRegistryEntries() - } [Test] - public void WindowsRegistryKeyThrowPathNullException() + public void GetRegistryEntries_ReturnsCorrectEntries() { - WindowsRegistryKey key = new(); - Assert.Throws(() => key.Path = null, "Expected IsPathValid to throw ArgumentNullException"); + const string testPath = TestPath + @"\GetRegistryEntries"; + const string testNamePrefix = "TestEntry"; + const string testValue = "winRegEntriesTest"; + + for (int i = 1; i <= 10; i++) + { + string testName = $"{testNamePrefix}{i}"; + Assert.DoesNotThrow(() => SetValue(TestHive, testPath, testName, testValue, RegistryValueKind.String)); + } + + const string testPathSubkeys = testPath + @"\Subkey"; + const string testNameSubKey = "TestSubEntry"; + Assert.DoesNotThrow(() => SetValue(TestHive, testPathSubkeys, testNameSubKey, testValue, RegistryValueKind.String)); + + var entries = GetEntries(TestHive, testPath); + + + Assert.That(entries, Is.Not.Null); + Assert.That(entries, Is.Not.Empty); + Assert.That(entries, Is.InstanceOf>>()); + + foreach (var entry in entries) + { + Assert.Multiple(() => + { + Assert.That(entry.Name, Is.Not.Null & Is.Not.EqualTo(testNameSubKey)); //Subkeys should not be read (non-recursive). + + Assert.That(entry.Hive, Is.EqualTo(TestHive)); + Assert.That(entry.Path, Is.EqualTo(testPath)); + Assert.That(entry.Value, Is.EqualTo(testValue)); + Assert.That(entry.ValueKind, Is.EqualTo(RegistryValueKind.String)); + }); + } } + + #endregion + + #region WinRegistryEC.GetRegistryEntriesRecursive() + [Test] - public void WindowsRegistryKeyThrowNameNullException() + public void GetRegistryEntriesRecursive_ReturnsCorrectEntries() { - WindowsRegistryKey key = new(); - Assert.Throws(() => key.Name = null, "Expected IsNameValid to throw ArgumentNullException"); + const string testPath = TestPath + @"\GetRegistryEntriesRecursive"; + const string testNamePrefix = "TestEntry"; + const string testValue = "winRegEntriesTest"; + + for (int i = 1; i <= 10; i++) + { + string testName = $"{testNamePrefix}{i}"; + Assert.DoesNotThrow(() => SetValue(TestHive, testPath, testName, testValue, RegistryValueKind.String)); + } + + const string testSubkeyPath = testPath + @"\Subkey"; + const string testNameSubKey = "TestSubEntry"; + Assert.DoesNotThrow(() => SetValue(TestHive, testSubkeyPath, testNameSubKey, testValue, RegistryValueKind.String)); + + var entries = GetEntriesRecursive(TestHive, testPath); + + Assert.That(entries, Is.Not.Null); + Assert.That(entries, Is.Not.Empty); + Assert.That(entries, Is.InstanceOf>> ()); + + // Assert that the subkey is included in the entries list + Assert.That(entries.Any(e => e.Name == testNameSubKey), Is.True, "Subkey entry should be included in the entries list."); + + foreach (var entry in entries) + { + Assert.Multiple(() => + { + Assert.That(entry.Name, Is.Not.Null); + Assert.That(entry.Hive, Is.EqualTo(TestHive)); + Assert.That(entry.Value, Is.EqualTo(testValue)); + Assert.That(entry.ValueKind, Is.EqualTo(RegistryValueKind.String)); + }); + } + } + + #endregion + + #region WindowsRegistry.DeleteRegistryValue() + [Test] + public void DeleteRegistryValue_DeletesValue() + { + const string testPath = TestPath + @"\DeleteRegistryValue"; + const string testName01 = "TestValue01"; + const string testName02 = "TestValue02"; + + Assert.DoesNotThrow(() => SetValue(TestHive, testPath, testName01, "Test", RegistryValueKind.String)); + Assert.DoesNotThrow(() => SetValue(TestHive, testPath, testName02, "Test", RegistryValueKind.String)); + + Assert.DoesNotThrow(() => DeleteRegistryValue(TestHive, testPath, testName01)); + Assert.Multiple(() => + { + Assert.That(GetStringValue(TestHive, testPath, testName01), Is.Null); + Assert.That(GetStringValue(TestHive, testPath, testName02), Is.Not.Null); + }); } #endregion - #region SetRegistryValue() tests - [Test] - public void CanSetRegistryValue() - { - Assert.DoesNotThrow(() => _registryWriter.SetRegistryValue(RegistryHive.CurrentUser, @"SOFTWARE\mRemoteNGTest", "TestKey", "A value string", RegistryValueKind.String)); - } + #region WindowsRegistry.DeleteTree() [Test] - public void SetRegistryValueThrowAccessDenied() + public void DeleteTree_RemovesKeyAndSubkeys() { - Assert.Throws(() => _registryWriter.SetRegistryValue(RegistryHive.LocalMachine, @"SOFTWARE\mRemoteNGTest", "TestKey", "A value string", RegistryValueKind.String)); + const string testPath = TestPath + @"\DeleteTree"; + Assert.DoesNotThrow(() => SetValue(TestHive, $"{testPath}\\Subkey0", "Test", "Test", RegistryValueKind.String)); + Assert.DoesNotThrow(() => SetValue(TestHive, $"{testPath}\\Subkey1", "Test", "Test", RegistryValueKind.String)); + Assert.DoesNotThrow(() => SetValue(TestHive, $"{testPath}\\Subkey2", "Test", "Test", RegistryValueKind.String)); + Assert.DoesNotThrow(() => SetValue(TestHive, $"{testPath}\\Subkey3", "Test", "Test", RegistryValueKind.String)); + + Assert.DoesNotThrow(() => DeleteTree(TestHive, testPath)); + Assert.That(GetSubKeyNames(TestHive, testPath), Is.Empty); } + #endregion + + #region WindowsRegistry.ConvertStringToRegistryHive() + + [Test] + public void ConvertStringToRegistryHive_ReturnsCorrectValue() + { + Assert.Multiple(() => + { + Assert.That(ConvertStringToRegistryHive("HKCR"), Is.EqualTo(RegistryHive.ClassesRoot)); + Assert.That(ConvertStringToRegistryHive("HKey_Classes_Root"), Is.EqualTo(RegistryHive.ClassesRoot)); + Assert.That(ConvertStringToRegistryHive("ClassesRoot"), Is.EqualTo(RegistryHive.ClassesRoot)); + + Assert.That(ConvertStringToRegistryHive("HKCU"), Is.EqualTo(RegistryHive.CurrentUser)); + Assert.That(ConvertStringToRegistryHive("HKey_Current_User"), Is.EqualTo(RegistryHive.CurrentUser)); + Assert.That(ConvertStringToRegistryHive("currentuser"), Is.EqualTo(RegistryHive.CurrentUser)); + + Assert.That(ConvertStringToRegistryHive("HKLM"), Is.EqualTo(RegistryHive.LocalMachine)); + Assert.That(ConvertStringToRegistryHive("HKey_Local_Machine"), Is.EqualTo(RegistryHive.LocalMachine)); + Assert.That(ConvertStringToRegistryHive("LocalMachine"), Is.EqualTo(RegistryHive.LocalMachine)); + + Assert.That(ConvertStringToRegistryHive("HKU"), Is.EqualTo(RegistryHive.Users)); + Assert.That(ConvertStringToRegistryHive("HKey_users"), Is.EqualTo(RegistryHive.Users)); + Assert.That(ConvertStringToRegistryHive("Users"), Is.EqualTo(RegistryHive.Users)); + + Assert.That(ConvertStringToRegistryHive("HKCC"), Is.EqualTo(RegistryHive.CurrentConfig)); + Assert.That(ConvertStringToRegistryHive("HKey_Current_Config"), Is.EqualTo(RegistryHive.CurrentConfig)); + Assert.That(ConvertStringToRegistryHive("CurrentConfig"), Is.EqualTo(RegistryHive.CurrentConfig)); + }); + } + + [Test] + public void ConvertStringToRegistryHive_InvalidHiveNull_ThrowArgumentNullException() + { + Assert.That(() => ConvertStringToRegistryHive(null), Throws.ArgumentNullException); + } + + [Test] + public void ConvertStringToRegistryHive_InvalidHive_ThrowArgumentException() + { + Assert.That(() => ConvertStringToRegistryHive("InvalidHive"), Throws.ArgumentException); + } + + #endregion + + #region WindowsRegistry.ConvertStringToRegistryValueKind() + + [Test] + public void ConvertStringToRegistryValueKind_ReturnsCorrectValue() + { + Assert.Multiple(() => + { + Assert.That(ConvertStringToRegistryValueKind("string"), Is.EqualTo(RegistryValueKind.String)); + Assert.That(ConvertStringToRegistryValueKind("reg_sz"), Is.EqualTo(RegistryValueKind.String)); + + Assert.That(ConvertStringToRegistryValueKind("dword"), Is.EqualTo(RegistryValueKind.DWord)); + Assert.That(ConvertStringToRegistryValueKind("reg_dword"), Is.EqualTo(RegistryValueKind.DWord)); + + Assert.That(ConvertStringToRegistryValueKind("binary"), Is.EqualTo(RegistryValueKind.Binary)); + Assert.That(ConvertStringToRegistryValueKind("reg_binary"), Is.EqualTo(RegistryValueKind.Binary)); + + Assert.That(ConvertStringToRegistryValueKind("qword"), Is.EqualTo(RegistryValueKind.QWord)); + Assert.That(ConvertStringToRegistryValueKind("reg_qword"), Is.EqualTo(RegistryValueKind.QWord)); + + Assert.That(ConvertStringToRegistryValueKind("multistring"), Is.EqualTo(RegistryValueKind.MultiString)); + Assert.That(ConvertStringToRegistryValueKind("reg_multi_sz"), Is.EqualTo(RegistryValueKind.MultiString)); + + Assert.That(ConvertStringToRegistryValueKind("expandstring"), Is.EqualTo(RegistryValueKind.ExpandString)); + Assert.That(ConvertStringToRegistryValueKind("reg_expand_sz"), Is.EqualTo(RegistryValueKind.ExpandString)); + }); + } + + [Test] + public void ConvertStringToRegistryValueKind_InvalidHiveNull_ThrowArgumentNullException() + { + Assert.That(() => ConvertStringToRegistryValueKind(null), Throws.ArgumentNullException); + } + + [Test] + public void ConvertStringToRegistryValueKind_InvalidHive_ThrowArgumentException() + { + Assert.That(() => ConvertStringToRegistryValueKind("InvalidKind"), Throws.ArgumentException); + } + + #endregion + + #region WindowsRegistry.ConvertTypeToRegistryValueKind() + + [Test] + public void ConvertTypeToRegistryValueKind_ReturnsCorrectValue() + { + Assert.Multiple(() => + { + Assert.That(ConvertTypeToRegistryValueKind(typeof(string)), Is.EqualTo(RegistryValueKind.String)); + Assert.That(ConvertTypeToRegistryValueKind(typeof(int)), Is.EqualTo(RegistryValueKind.DWord)); + Assert.That(ConvertTypeToRegistryValueKind(typeof(long)), Is.EqualTo(RegistryValueKind.QWord)); + Assert.That(ConvertTypeToRegistryValueKind(typeof(bool)), Is.EqualTo(RegistryValueKind.DWord)); + Assert.That(ConvertTypeToRegistryValueKind(typeof(byte)), Is.EqualTo(RegistryValueKind.Binary)); + }); + } + + [Test] + public void ConvertTypeToRegistryValueKind_UnknownType_ReturnsString() + { + Assert.That(ConvertTypeToRegistryValueKind(typeof(Single)), Is.EqualTo(RegistryValueKind.String)); + } + + [Test] + public void ConvertTypeToRegistryValueKind_WithNullValueType_ThrowsArgumentNullException() + { + Assert.Throws(() => ConvertTypeToRegistryValueKind(null)); + } + + #endregion + + #region WindowsRegistry.ConvertRegistryValueKindToType() + + [Test] + public void ConvertRegistryValueKindToType_ReturnsCorrectType() + { + Assert.Multiple(() => + { + Assert.That(ConvertRegistryValueKindToType(RegistryValueKind.String), Is.EqualTo(typeof(string))); + Assert.That(ConvertRegistryValueKindToType(RegistryValueKind.ExpandString), Is.EqualTo(typeof(string))); + Assert.That(ConvertRegistryValueKindToType(RegistryValueKind.DWord), Is.EqualTo(typeof(int))); + Assert.That(ConvertRegistryValueKindToType(RegistryValueKind.QWord), Is.EqualTo(typeof(long))); + Assert.That(ConvertRegistryValueKindToType(RegistryValueKind.Binary), Is.EqualTo(typeof(byte[]))); + Assert.That(ConvertRegistryValueKindToType(RegistryValueKind.MultiString), Is.EqualTo(typeof(string[]))); + Assert.That(ConvertRegistryValueKindToType((RegistryValueKind)100), Is.EqualTo(typeof(object))); + }); + } + + #endregion + + [OneTimeTearDown] + public void Cleanup() + { + // Delete all created tests + DeleteTree(TestHive, TestPath); + } } }