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.

336 lines
14 KiB

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