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.

337 lines
14 KiB

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