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  }