CLI tool for running Playbooks
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

332 lines
14 KiB

1 year ago
  1. #nullable enable
  2. using System;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Security;
  7. using System.Text.RegularExpressions;
  8. using System.Threading;
  9. using System.Threading.Tasks;
  10. using Microsoft.Win32;
  11. using TrustedUninstaller.Shared.Exceptions;
  12. using TrustedUninstaller.Shared.Tasks;
  13. using YamlDotNet.Serialization;
  14. namespace TrustedUninstaller.Shared.Actions
  15. {
  16. public enum RegistryValueOperation
  17. {
  18. Delete = 0,
  19. Add = 1,
  20. // This indicates to skip the action if the specified value does not already exist
  21. Set = 2
  22. }
  23. public enum RegistryValueType
  24. {
  25. REG_SZ = RegistryValueKind.String,
  26. REG_MULTI_SZ = RegistryValueKind.MultiString,
  27. REG_EXPAND_SZ = RegistryValueKind.ExpandString,
  28. REG_DWORD = RegistryValueKind.DWord,
  29. REG_QWORD = RegistryValueKind.QWord,
  30. REG_BINARY = RegistryValueKind.Binary,
  31. REG_NONE = RegistryValueKind.None,
  32. REG_UNKNOWN = RegistryValueKind.Unknown
  33. }
  34. public class RegistryValueAction : ITaskAction
  35. {
  36. [YamlMember(typeof(string), Alias = "path")]
  37. public string KeyName { get; set; }
  38. [YamlMember(typeof(string), Alias = "value")]
  39. public string Value { get; set; } = "";
  40. [YamlMember(typeof(object), Alias = "data")]
  41. public object? Data { get; set; }
  42. [YamlMember(typeof(RegistryValueType), Alias = "type")]
  43. public RegistryValueType Type { get; set; }
  44. [YamlMember(typeof(Scope), Alias = "scope")]
  45. public Scope Scope { get; set; } = Scope.AllUsers;
  46. [YamlMember(typeof(RegistryValueOperation), Alias = "operation")]
  47. public RegistryValueOperation Operation { get; set; } = RegistryValueOperation.Add;
  48. [YamlMember(typeof(string), Alias = "weight")]
  49. public int ProgressWeight { get; set; } = 0;
  50. public int GetProgressWeight()
  51. {
  52. int roots;
  53. try
  54. {
  55. roots = GetRoots().Count;
  56. }
  57. catch (Exception)
  58. {
  59. roots = 1;
  60. }
  61. return ProgressWeight + roots;
  62. }
  63. private bool InProgress { get; set; }
  64. public void ResetProgress() => InProgress = false;
  65. public string ErrorString() => $"RegistryValueAction failed to {Operation.ToString().ToLower()} value '{Value}' in key '{KeyName}'";
  66. private List<RegistryKey> GetRoots()
  67. {
  68. var hive = KeyName.Split('\\').GetValue(0).ToString().ToUpper();
  69. var list = new List<RegistryKey>();
  70. if (hive.Equals("HKCU") || hive.Equals("HKEY_CURRENT_USER"))
  71. {
  72. RegistryKey usersKey;
  73. List<string> userKeys;
  74. switch (Scope)
  75. {
  76. case Scope.AllUsers:
  77. WinUtil.RegistryManager.HookUserHives();
  78. usersKey = RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Default);
  79. userKeys = usersKey.GetSubKeyNames().
  80. Where(x => x.StartsWith("S-") &&
  81. usersKey.OpenSubKey(x).GetSubKeyNames().Any(y => y.Equals("Volatile Environment"))).ToList();
  82. userKeys.AddRange(usersKey.GetSubKeyNames().Where(x => x.StartsWith("AME_UserHive_") && !x.EndsWith("_Classes")).ToList());
  83. userKeys.ForEach(x => list.Add(usersKey.OpenSubKey(x, true)));
  84. return list;
  85. case Scope.ActiveUsers:
  86. usersKey = RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Default);
  87. userKeys = usersKey.GetSubKeyNames().
  88. Where(x => x.StartsWith("S-") &&
  89. usersKey.OpenSubKey(x).GetSubKeyNames().Any(y => y.Equals("Volatile Environment"))).ToList();
  90. userKeys.ForEach(x => list.Add(usersKey.OpenSubKey(x, true)));
  91. return list;
  92. case Scope.DefaultUser:
  93. usersKey = RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Default);
  94. userKeys = usersKey.GetSubKeyNames().Where(x => x.Equals("AME_UserHive_Default") && !x.EndsWith("_Classes")).ToList();
  95. userKeys.ForEach(x => list.Add(usersKey.OpenSubKey(x, true)));
  96. return list;
  97. }
  98. }
  99. list.Add(hive switch
  100. {
  101. "HKCU" => RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Default),
  102. "HKEY_CURRENT_USER" => RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Default),
  103. "HKLM" => RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default),
  104. "HKEY_LOCAL_MACHINE" => RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default),
  105. "HKCR" => RegistryKey.OpenBaseKey(RegistryHive.ClassesRoot, RegistryView.Default),
  106. "HKEY_CLASSES_ROOT" => RegistryKey.OpenBaseKey(RegistryHive.ClassesRoot, RegistryView.Default),
  107. "HKU" => RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Default),
  108. "HKEY_USERS" => RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Default),
  109. _ => throw new ArgumentException($"Key '{KeyName}' does not specify a valid registry hive.")
  110. });
  111. return list;
  112. }
  113. public string GetSubKey() => KeyName.Substring(KeyName.IndexOf('\\') + 1);
  114. private RegistryKey? OpenSubKey(RegistryKey root)
  115. {
  116. var subKeyPath = GetSubKey();
  117. if (subKeyPath == null) throw new ArgumentException($"Key '{KeyName}' is invalid.");
  118. return root.OpenSubKey(subKeyPath, true);
  119. }
  120. public object? GetCurrentValue(RegistryKey root)
  121. {
  122. var subkey = GetSubKey();
  123. return Registry.GetValue(root.Name + "\\" + subkey, Value, null);
  124. }
  125. public static byte[] StringToByteArray(string hex) {
  126. return Enumerable.Range(0, hex.Length)
  127. .Where(x => x % 2 == 0)
  128. .Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
  129. .ToArray();
  130. }
  131. public UninstallTaskStatus GetStatus()
  132. {
  133. try
  134. {
  135. var roots = GetRoots();
  136. foreach (var _root in roots)
  137. {
  138. var root = _root;
  139. var subKey = GetSubKey();
  140. if (root.Name.Contains("AME_UserHive_") && subKey.StartsWith("SOFTWARE\\Classes", StringComparison.CurrentCultureIgnoreCase))
  141. {
  142. var usersKey = RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Default);
  143. root = usersKey.OpenSubKey(root.Name.Substring(11) + "_Classes", true);
  144. subKey = Regex.Replace(subKey, @"^SOFTWARE\\*Classes\\*", "", RegexOptions.IgnoreCase);
  145. if (root == null)
  146. {
  147. continue;
  148. }
  149. }
  150. var openedSubKey = root.OpenSubKey(subKey);
  151. if (openedSubKey == null && (Operation == RegistryValueOperation.Set || Operation == RegistryValueOperation.Delete))
  152. continue;
  153. if (openedSubKey == null) return UninstallTaskStatus.ToDo;
  154. var value = openedSubKey.GetValue(Value);
  155. if (value == null)
  156. {
  157. if (Operation == RegistryValueOperation.Set || Operation == RegistryValueOperation.Delete)
  158. continue;
  159. return UninstallTaskStatus.ToDo;
  160. }
  161. if (Operation == RegistryValueOperation.Delete) return UninstallTaskStatus.ToDo;
  162. if (Data == null) return UninstallTaskStatus.ToDo;
  163. bool matches;
  164. try
  165. {
  166. matches = Type switch
  167. {
  168. RegistryValueType.REG_SZ =>
  169. Data.ToString() == value.ToString(),
  170. RegistryValueType.REG_EXPAND_SZ =>
  171. // RegistryValueOptions.DoNotExpandEnvironmentNames above did not seem to work.
  172. Environment.ExpandEnvironmentVariables(Data.ToString()) == value.ToString(),
  173. RegistryValueType.REG_MULTI_SZ =>
  174. Data.ToString() == "" ?
  175. ((string[])value).SequenceEqual(new string[] { }) :
  176. ((string[])value).SequenceEqual(Data.ToString().Split(new string[] { "\\0" }, StringSplitOptions.None)),
  177. RegistryValueType.REG_DWORD =>
  178. unchecked((int)Convert.ToUInt32(Data)) == (int)value,
  179. RegistryValueType.REG_QWORD =>
  180. Convert.ToUInt64(Data) == (ulong)value,
  181. RegistryValueType.REG_BINARY =>
  182. ((byte[])value).SequenceEqual(StringToByteArray(Data.ToString())),
  183. RegistryValueType.REG_NONE =>
  184. ((byte[])value).SequenceEqual(new byte[0]),
  185. RegistryValueType.REG_UNKNOWN =>
  186. Data.ToString() == value.ToString(),
  187. _ => throw new ArgumentException("Impossible.")
  188. };
  189. }
  190. catch (InvalidCastException)
  191. {
  192. matches = false;
  193. }
  194. if (!matches) return UninstallTaskStatus.ToDo;
  195. }
  196. }
  197. catch (Exception e)
  198. {
  199. ErrorLogger.WriteToErrorLog(e.Message,
  200. e.StackTrace, "RegistryValueAction Error");
  201. return UninstallTaskStatus.ToDo;
  202. }
  203. return UninstallTaskStatus.Completed;
  204. }
  205. public async Task<bool> RunTask()
  206. {
  207. Console.WriteLine($"{Operation.ToString().TrimEnd('e')}ing value '{Value}' in key '{KeyName}'...");
  208. var roots = GetRoots();
  209. foreach (var _root in roots)
  210. {
  211. var root = _root;
  212. var subKey = GetSubKey();
  213. try
  214. {
  215. if (root.Name.Contains("AME_UserHive_") && subKey.StartsWith("SOFTWARE\\Classes", StringComparison.CurrentCultureIgnoreCase))
  216. {
  217. var usersKey = RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Default);
  218. root = usersKey.OpenSubKey(root.Name.Substring(11) + "_Classes", true);
  219. subKey = Regex.Replace(subKey, @"^SOFTWARE\\*Classes\\*", "", RegexOptions.IgnoreCase);
  220. if (root == null)
  221. {
  222. ErrorLogger.WriteToErrorLog($"User classes hive not found for hive {_root.Name}.",
  223. Environment.StackTrace, "RegistryValueAction Error");
  224. continue;
  225. }
  226. }
  227. if (GetCurrentValue(root) == Data) continue;
  228. if (root.OpenSubKey(subKey) == null && Operation == RegistryValueOperation.Set) continue;
  229. if (root.OpenSubKey(subKey) == null && Operation == RegistryValueOperation.Add) root.CreateSubKey(subKey);
  230. if (Operation == RegistryValueOperation.Delete)
  231. {
  232. var key = root.OpenSubKey(subKey, true);
  233. key?.DeleteValue(Value);
  234. continue;
  235. }
  236. if (Type == RegistryValueType.REG_BINARY)
  237. {
  238. var data = StringToByteArray(Data.ToString());
  239. Registry.SetValue(root.Name + "\\" + subKey, Value, data, (RegistryValueKind)Type);
  240. }
  241. else if (Type == RegistryValueType.REG_DWORD)
  242. {
  243. // DWORD values using the highest bit set fail without this, for example '2962489444'.
  244. // See https://stackoverflow.com/questions/6608400/how-to-put-a-dword-in-the-registry-with-the-highest-bit-set;
  245. var value = unchecked((int)Convert.ToUInt32(Data));
  246. Registry.SetValue(root.Name + "\\" + subKey, Value, value, (RegistryValueKind)Type);
  247. }
  248. else if (Type == RegistryValueType.REG_QWORD)
  249. {
  250. Registry.SetValue(root.Name + "\\" + subKey, Value, Convert.ToUInt64(Data), (RegistryValueKind)Type);
  251. }
  252. else if (Type == RegistryValueType.REG_NONE)
  253. {
  254. byte[] none = new byte[0];
  255. Registry.SetValue(root.Name + "\\" + subKey, Value, none, (RegistryValueKind)Type);
  256. }
  257. else if (Type == RegistryValueType.REG_MULTI_SZ)
  258. {
  259. string[] data;
  260. if (Data.ToString() == "") data = new string[] { };
  261. else data = Data.ToString().Split(new string[] { "\\0" }, StringSplitOptions.None);
  262. Registry.SetValue(root.Name + "\\" + subKey, Value, data, (RegistryValueKind)Type);
  263. }
  264. else
  265. {
  266. Registry.SetValue(root.Name + "\\" + subKey, Value, Data, (RegistryValueKind)Type);
  267. }
  268. }
  269. catch (Exception e)
  270. {
  271. ErrorLogger.WriteToErrorLog(e.Message,
  272. e.StackTrace, "RegistryValueAction Error", root?.Name + "\\" + subKey);
  273. }
  274. }
  275. return true;
  276. }
  277. }
  278. }