RegistryHelper.cs
1 // Copyright (c) Microsoft Corporation 2 // The Microsoft Corporation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System; 6 using System.Collections.Generic; 7 using System.Collections.ObjectModel; 8 using System.Diagnostics; 9 using System.Linq; 10 using Microsoft.CmdPal.Ext.Registry.Classes; 11 using Microsoft.CmdPal.Ext.Registry.Constants; 12 using Microsoft.CmdPal.Ext.Registry.Properties; 13 using Microsoft.Win32; 14 15 namespace Microsoft.CmdPal.Ext.Registry.Helpers; 16 17 /// <summary> 18 /// Helper class to easier work with the registry 19 /// </summary> 20 internal static class RegistryHelper 21 { 22 /// <summary> 23 /// A list that contain all registry base keys in a long/full version and in a short version (e.g HKLM = HKEY_LOCAL_MACHINE) 24 /// </summary> 25 private static readonly IReadOnlyDictionary<string, RegistryKey> _baseKeys = new Dictionary<string, RegistryKey>(12) 26 { 27 { KeyName.ClassRootShort, Win32.Registry.ClassesRoot }, 28 { Win32.Registry.ClassesRoot.Name, Win32.Registry.ClassesRoot }, 29 { KeyName.CurrentConfigShort, Win32.Registry.CurrentConfig }, 30 { Win32.Registry.CurrentConfig.Name, Win32.Registry.CurrentConfig }, 31 { KeyName.CurrentUserShort, Win32.Registry.CurrentUser }, 32 { Win32.Registry.CurrentUser.Name, Win32.Registry.CurrentUser }, 33 { KeyName.LocalMachineShort, Win32.Registry.LocalMachine }, 34 { Win32.Registry.LocalMachine.Name, Win32.Registry.LocalMachine }, 35 { KeyName.PerformanceDataShort, Win32.Registry.PerformanceData }, 36 { Win32.Registry.PerformanceData.Name, Win32.Registry.PerformanceData }, 37 { KeyName.UsersShort, Win32.Registry.Users }, 38 { Win32.Registry.Users.Name, Win32.Registry.Users }, 39 }; 40 41 /// <summary> 42 /// Try to find registry base keys based on the given query 43 /// </summary> 44 /// <param name="query">The query to search</param> 45 /// <returns>A combination of a list of base <see cref="RegistryKey"/> and the sub keys</returns> 46 #pragma warning disable CS8632 47 internal static (IEnumerable<RegistryKey>? BaseKey, string SubKey) GetRegistryBaseKey(in string query) 48 { 49 if (string.IsNullOrWhiteSpace(query)) 50 { 51 return (null, string.Empty); 52 } 53 54 var baseKey = query.Split('\\').FirstOrDefault() ?? string.Empty; 55 var subKey = string.Empty; 56 if (!string.IsNullOrEmpty(baseKey)) 57 { 58 subKey = query.Replace(baseKey, string.Empty, StringComparison.InvariantCultureIgnoreCase).TrimStart('\\'); 59 } 60 61 var baseKeyResult = _baseKeys 62 .Where(found => found.Key.StartsWith(baseKey, StringComparison.InvariantCultureIgnoreCase)) 63 .Select(found => found.Value) 64 .Distinct(); 65 66 return (baseKeyResult, subKey); 67 } 68 69 /// <summary> 70 /// Return a list of all registry base key 71 /// </summary> 72 /// <returns>A list with all registry base keys</returns> 73 internal static ICollection<RegistryEntry> GetAllBaseKeys() 74 { 75 return new Collection<RegistryEntry> 76 { 77 new(Win32.Registry.ClassesRoot), 78 new(Win32.Registry.CurrentConfig), 79 new(Win32.Registry.CurrentUser), 80 new(Win32.Registry.LocalMachine), 81 new(Win32.Registry.PerformanceData), 82 new(Win32.Registry.Users), 83 }; 84 } 85 86 /// <summary> 87 /// Search for the given sub-key path in the given registry base key 88 /// </summary> 89 /// <param name="baseKey">The base <see cref="RegistryKey"/></param> 90 /// <param name="subKeyPath">The path of the registry sub-key</param> 91 /// <returns>A list with all found registry keys</returns> 92 internal static ICollection<RegistryEntry> SearchForSubKey(in RegistryKey baseKey, in string subKeyPath) 93 { 94 if (string.IsNullOrEmpty(subKeyPath)) 95 { 96 return FindSubKey(baseKey, string.Empty); 97 } 98 99 var subKeysNames = subKeyPath.Split('\\'); 100 var index = 0; 101 RegistryKey? subKey = baseKey; 102 #pragma warning restore CS8632 103 ICollection<RegistryEntry> result; 104 105 do 106 { 107 result = FindSubKey(subKey, subKeysNames.ElementAtOrDefault(index) ?? string.Empty); 108 109 if (result.Count == 0) 110 { 111 // If a subKey can't be found, show no results. 112 break; 113 } 114 115 if (result.Count == 1 && index < subKeysNames.Length) 116 { 117 subKey = result.First().Key; 118 } 119 120 if (result.Count > 1 || subKey is null) 121 { 122 break; 123 } 124 125 index++; 126 } 127 while (index < subKeysNames.Length); 128 129 return result; 130 } 131 132 /// <summary> 133 /// Return a human readable summary of a given <see cref="RegistryKey"/> 134 /// </summary> 135 /// <param name="key">The <see cref="RegistryKey"/> for the summary</param> 136 /// <returns>A human readable summary</returns> 137 internal static string GetSummary(in RegistryKey key) 138 { 139 return $"{Resources.SubKeys} {key.SubKeyCount} - {Resources.Values} {key.ValueCount}"; 140 } 141 142 /// <summary> 143 /// Open a given registry key in the registry editor 144 /// </summary> 145 /// <param name="fullKey">The registry key to open</param> 146 internal static void OpenRegistryKey(in string fullKey) 147 { 148 // Set the registry key 149 Win32.Registry.SetValue(@"HKEY_Current_User\Software\Microsoft\Windows\CurrentVersion\Applets\Regedit", "LastKey", fullKey); 150 151 // Open regedit.exe with multi-instance option 152 ProcessStartInfo startInfo = new ProcessStartInfo 153 { 154 FileName = "regedit.exe", 155 Arguments = "-m", 156 UseShellExecute = true, 157 Verb = "runas", // Runs as Administrator 158 }; 159 160 Process.Start(startInfo); 161 } 162 163 /// <summary> 164 /// Try to find the given registry sub-key in the given registry parent-key 165 /// </summary> 166 /// <param name="parentKey">The parent-key, also the root to start the search</param> 167 /// <param name="searchSubKey">The sub-key to find</param> 168 /// <returns>A list with all found registry sub-keys</returns> 169 private static Collection<RegistryEntry> FindSubKey(in RegistryKey parentKey, in string searchSubKey) 170 { 171 var list = new Collection<RegistryEntry>(); 172 173 try 174 { 175 foreach (var subKey in parentKey.GetSubKeyNames().OrderBy(found => found)) 176 { 177 if (!subKey.StartsWith(searchSubKey, StringComparison.InvariantCultureIgnoreCase)) 178 { 179 continue; 180 } 181 182 if (string.Equals(subKey, searchSubKey, StringComparison.OrdinalIgnoreCase)) 183 { 184 var key = parentKey.OpenSubKey(subKey, RegistryKeyPermissionCheck.ReadSubTree); 185 if (key is not null) 186 { 187 list.Add(new RegistryEntry(key)); 188 } 189 190 return list; 191 } 192 193 try 194 { 195 var key = parentKey.OpenSubKey(subKey, RegistryKeyPermissionCheck.ReadSubTree); 196 if (key is not null) 197 { 198 list.Add(new RegistryEntry(key)); 199 } 200 } 201 catch (Exception exception) 202 { 203 list.Add(new RegistryEntry($"{parentKey.Name}\\{subKey}", exception)); 204 } 205 } 206 } 207 catch (Exception ex) 208 { 209 list.Add(new RegistryEntry(parentKey.Name, ex)); 210 } 211 212 return list; 213 } 214 215 /// <summary> 216 /// Return a list with a registry sub-keys of the given registry parent-key 217 /// </summary> 218 /// <param name="parentKey">The registry parent-key</param> 219 /// <param name="maxCount">(optional) The maximum count of the results</param> 220 /// <returns>A list with all found registry sub-keys</returns> 221 private static Collection<RegistryEntry> GetAllSubKeys(in RegistryKey parentKey, in int maxCount = 50) 222 { 223 var list = new Collection<RegistryEntry>(); 224 225 try 226 { 227 foreach (var subKey in parentKey.GetSubKeyNames()) 228 { 229 if (list.Count >= maxCount) 230 { 231 break; 232 } 233 234 list.Add(new RegistryEntry(parentKey)); 235 } 236 } 237 catch (Exception exception) 238 { 239 list.Add(new RegistryEntry(parentKey.Name, exception)); 240 } 241 242 return list; 243 } 244 }