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.

471 lines
23 KiB

1 year ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
1 year ago
1 year ago
1 year ago
1 year ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
1 year ago
6 months ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
6 months ago
1 year ago
6 months ago
6 months ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
1 year ago
6 months ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
6 months ago
1 year ago
6 months ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
1 year ago
6 months ago
6 months ago
6 months ago
1 year ago
  1. #nullable enable
  2. using System;
  3. using System.Collections.Specialized;
  4. using System.Configuration.Install;
  5. using System.Diagnostics;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Management;
  9. using System.ServiceProcess;
  10. using System.Text.RegularExpressions;
  11. using System.Threading.Tasks;
  12. using System.Windows;
  13. using Microsoft.Win32;
  14. using TrustedUninstaller.Shared.Exceptions;
  15. using TrustedUninstaller.Shared.Tasks;
  16. using YamlDotNet.Serialization;
  17. namespace TrustedUninstaller.Shared.Actions
  18. {
  19. public enum ServiceOperation
  20. {
  21. Stop,
  22. Continue,
  23. Start,
  24. Pause,
  25. Delete,
  26. Change
  27. }
  28. public class ServiceAction : TaskAction, ITaskAction
  29. {
  30. public void RunTaskOnMainThread() { throw new NotImplementedException(); }
  31. [YamlMember(typeof(ServiceOperation), Alias = "operation")]
  32. public ServiceOperation Operation { get; set; } = ServiceOperation.Delete;
  33. [YamlMember(typeof(string), Alias = "name")]
  34. public string ServiceName { get; set; } = null!;
  35. [YamlMember(typeof(int), Alias = "startup")]
  36. public int? Startup { get; set; }
  37. [YamlMember(typeof(bool), Alias = "deleteStop")]
  38. public bool DeleteStop { get; set; } = true;
  39. [YamlMember(typeof(bool), Alias = "deleteUsingRegistry")]
  40. public bool RegistryDelete { get; set; } = false;
  41. [YamlMember(typeof(string), Alias = "device")]
  42. public bool Device { get; set; } = false;
  43. [YamlMember(typeof(string), Alias = "weight")]
  44. public int ProgressWeight { get; set; } = 4;
  45. public int GetProgressWeight() => ProgressWeight;
  46. private bool InProgress { get; set; }
  47. public void ResetProgress() => InProgress = false;
  48. public string ErrorString() => $"ServiceAction failed to {Operation.ToString().ToLower()} service {ServiceName}.";
  49. private ServiceController? GetService()
  50. {
  51. if (ServiceName.EndsWith("*") && ServiceName.StartsWith("*")) return ServiceController.GetServices()
  52. .FirstOrDefault(service => service.ServiceName.IndexOf(ServiceName.Trim('*'), StringComparison.CurrentCultureIgnoreCase) >= 0);
  53. if (ServiceName.EndsWith("*")) return ServiceController.GetServices()
  54. .FirstOrDefault(service => service.ServiceName.StartsWith(ServiceName.TrimEnd('*'), StringComparison.CurrentCultureIgnoreCase));
  55. if (ServiceName.StartsWith("*")) return ServiceController.GetServices()
  56. .FirstOrDefault(service => service.ServiceName.EndsWith(ServiceName.TrimStart('*'), StringComparison.CurrentCultureIgnoreCase));
  57. return ServiceController.GetServices()
  58. .FirstOrDefault(service => service.ServiceName.Equals(ServiceName, StringComparison.CurrentCultureIgnoreCase));
  59. }
  60. private ServiceController? GetDevice()
  61. {
  62. if (ServiceName.EndsWith("*") && ServiceName.StartsWith("*")) return ServiceController.GetDevices()
  63. .FirstOrDefault(service => service.ServiceName.IndexOf(ServiceName.Trim('*'), StringComparison.CurrentCultureIgnoreCase) >= 0);
  64. if (ServiceName.EndsWith("*")) return ServiceController.GetDevices()
  65. .FirstOrDefault(service => service.ServiceName.StartsWith(ServiceName.TrimEnd('*'), StringComparison.CurrentCultureIgnoreCase));
  66. if (ServiceName.StartsWith("*")) return ServiceController.GetDevices()
  67. .FirstOrDefault(service => service.ServiceName.EndsWith(ServiceName.TrimStart('*'), StringComparison.CurrentCultureIgnoreCase));
  68. return ServiceController.GetDevices()
  69. .FirstOrDefault(service => service.ServiceName.Equals(ServiceName, StringComparison.CurrentCultureIgnoreCase));
  70. }
  71. public UninstallTaskStatus GetStatus()
  72. {
  73. if (InProgress) return UninstallTaskStatus.InProgress;
  74. if (Operation == ServiceOperation.Change && Startup.HasValue)
  75. {
  76. // TODO: Implement dev log. Example:
  77. // if (Registry.LocalMachine.OpenSubKey($@"SYSTEM\CurrentControlSet\Services\{ServiceName}") == null) WriteToDevLog($"Warning: Service name '{ServiceName}' not found in registry.");
  78. var root = Registry.LocalMachine.OpenSubKey($@"SYSTEM\CurrentControlSet\Services\{ServiceName}");
  79. if (root == null) return UninstallTaskStatus.Completed;
  80. var value = root.GetValue("Start");
  81. return (int)value == Startup.Value ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo;
  82. }
  83. ServiceController? serviceController;
  84. if (Device) serviceController = GetDevice();
  85. else serviceController = GetService();
  86. if (Operation == ServiceOperation.Delete && RegistryDelete)
  87. {
  88. // TODO: Implement dev log. Example:
  89. // if (Registry.LocalMachine.OpenSubKey($@"SYSTEM\CurrentControlSet\Services\{ServiceName}") == null) WriteToDevLog($"Warning: Service name '{ServiceName}' not found in registry.");
  90. var root = Registry.LocalMachine.OpenSubKey($@"SYSTEM\CurrentControlSet\Services\{ServiceName}");
  91. return root == null ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo;
  92. }
  93. return Operation switch
  94. {
  95. ServiceOperation.Stop =>
  96. serviceController == null ||
  97. serviceController?.Status == ServiceControllerStatus.Stopped
  98. || serviceController?.Status == ServiceControllerStatus.StopPending ?
  99. UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo,
  100. ServiceOperation.Continue =>
  101. serviceController == null ||
  102. serviceController?.Status == ServiceControllerStatus.Running
  103. || serviceController?.Status == ServiceControllerStatus.ContinuePending ?
  104. UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo,
  105. ServiceOperation.Start =>
  106. serviceController?.Status == ServiceControllerStatus.StartPending
  107. || serviceController?.Status == ServiceControllerStatus.Running ?
  108. UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo,
  109. ServiceOperation.Pause =>
  110. serviceController == null ||
  111. serviceController?.Status == ServiceControllerStatus.Paused
  112. || serviceController?.Status == ServiceControllerStatus.PausePending ?
  113. UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo,
  114. ServiceOperation.Delete =>
  115. serviceController == null || Win32.ServiceEx.IsPendingDeleteOrDeleted(serviceController.ServiceName) ?
  116. UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo,
  117. _ => throw new ArgumentOutOfRangeException("Argument out of Range", new ArgumentOutOfRangeException())
  118. };
  119. }
  120. private readonly string[] RegexNoKill = { "DcomLaunch" };
  121. public async Task<bool> RunTask()
  122. {
  123. if (InProgress) throw new TaskInProgressException("Another Service action was called while one was in progress.");
  124. if (Operation == ServiceOperation.Change && !Startup.HasValue) throw new ArgumentException("Startup property must be specified with the change operation.");
  125. if (Operation == ServiceOperation.Change && (Startup.Value > 4 || Startup.Value < 0)) throw new ArgumentException("Startup property must be between 1 and 4.");
  126. // This is a little cursed but it works and is concise lol
  127. Console.WriteLine($"{Operation.ToString().Replace("Stop", "Stopp").TrimEnd('e')}ing services matching '{ServiceName}'...");
  128. if (Operation == ServiceOperation.Change)
  129. {
  130. var action = new RegistryValueAction()
  131. {
  132. KeyName = $@"HKLM\SYSTEM\CurrentControlSet\Services\{ServiceName}",
  133. Value = "Start",
  134. Data = Startup.Value,
  135. Type = RegistryValueType.REG_DWORD,
  136. Operation = RegistryValueOperation.Set
  137. };
  138. await action.RunTask();
  139. InProgress = false;
  140. return true;
  141. }
  142. ServiceController? service;
  143. if (Device) service = GetDevice();
  144. else service = GetService();
  145. if (service == null)
  146. {
  147. Console.WriteLine($"No services found matching '{ServiceName}'.");
  148. //ErrorLogger.WriteToErrorLog($"The service matching '{ServiceName}' does not exist.", Environment.StackTrace, "ServiceAction Error");
  149. if (Operation == ServiceOperation.Start)
  150. throw new ArgumentException("Service " + ServiceName + " not found.");
  151. return false;
  152. }
  153. InProgress = true;
  154. var cmdAction = new CmdAction();
  155. if ((Operation == ServiceOperation.Delete && DeleteStop) || Operation == ServiceOperation.Stop)
  156. {
  157. if (RegexNoKill.Any(regex => Regex.Match(ServiceName, regex, RegexOptions.IgnoreCase).Success))
  158. {
  159. Console.WriteLine($"Skipping {ServiceName}...");
  160. return false;
  161. }
  162. try
  163. {
  164. foreach (ServiceController dependentService in service.DependentServices.Where(x => x.Status != ServiceControllerStatus.Stopped))
  165. {
  166. Console.WriteLine($"Killing dependent service {dependentService.ServiceName}...");
  167. if (dependentService.Status != ServiceControllerStatus.StopPending && dependentService.Status != ServiceControllerStatus.Stopped)
  168. {
  169. try
  170. {
  171. dependentService.Stop();
  172. }
  173. catch (Exception e)
  174. {
  175. dependentService.Refresh();
  176. if (dependentService.Status != ServiceControllerStatus.Stopped && dependentService.Status != ServiceControllerStatus.StopPending)
  177. ErrorLogger.WriteToErrorLog("Dependent service stop failed: " + e.Message, e.StackTrace, "ServiceAction Warning", dependentService.ServiceName);
  178. }
  179. cmdAction.Command = Environment.Is64BitOperatingSystem ?
  180. $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {dependentService.ServiceName} -caction stop" :
  181. $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {dependentService.ServiceName} -caction stop";
  182. if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread();
  183. }
  184. Console.WriteLine("Waiting for the dependent service to stop...");
  185. try
  186. {
  187. dependentService.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromMilliseconds(5000));
  188. }
  189. catch (Exception e)
  190. {
  191. dependentService.Refresh();
  192. if (service.Status != ServiceControllerStatus.Stopped)
  193. ErrorLogger.WriteToErrorLog("Dependent service stop timeout exceeded.", e.StackTrace, "ServiceAction Warning", ServiceName);
  194. }
  195. try
  196. {
  197. var killServ = new TaskKillAction()
  198. {
  199. ProcessID = Win32.ServiceEx.GetServiceProcessId(dependentService.ServiceName)
  200. };
  201. await killServ.RunTask();
  202. }
  203. catch (Exception e)
  204. {
  205. dependentService.Refresh();
  206. if (dependentService.Status != ServiceControllerStatus.Stopped)
  207. ErrorLogger.WriteToErrorLog($"Could not kill dependent service {dependentService.ServiceName}.",
  208. e.StackTrace, "ServiceAction Error", ServiceName);
  209. }
  210. }
  211. }
  212. catch (Exception e)
  213. {
  214. ErrorLogger.WriteToErrorLog($"Error killing dependent services: " + e.Message,
  215. e.StackTrace, "ServiceAction Error", ServiceName);
  216. }
  217. }
  218. if (Operation == ServiceOperation.Delete)
  219. {
  220. if (DeleteStop && service.Status != ServiceControllerStatus.StopPending && service.Status != ServiceControllerStatus.Stopped)
  221. {
  222. try
  223. {
  224. service.Stop();
  225. }
  226. catch (Exception e)
  227. {
  228. service.Refresh();
  229. if (service.Status != ServiceControllerStatus.Stopped && service.Status != ServiceControllerStatus.StopPending)
  230. ErrorLogger.WriteToErrorLog("Service stop failed: " + e.Message, e.StackTrace, "ServiceAction Warning", ServiceName);
  231. }
  232. cmdAction.Command = Environment.Is64BitOperatingSystem ?
  233. $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction stop" :
  234. $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction stop";
  235. if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread();
  236. Console.WriteLine("Waiting for the service to stop...");
  237. try
  238. {
  239. service.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromMilliseconds(5000));
  240. }
  241. catch (Exception e)
  242. {
  243. service.Refresh();
  244. if (service.Status != ServiceControllerStatus.Stopped)
  245. ErrorLogger.WriteToErrorLog("Service stop timeout exceeded.", e.StackTrace, "ServiceAction Warning", ServiceName);
  246. }
  247. try
  248. {
  249. var killServ = new TaskKillAction()
  250. {
  251. ProcessID = Win32.ServiceEx.GetServiceProcessId(service.ServiceName)
  252. };
  253. await killServ.RunTask();
  254. }
  255. catch (Exception e)
  256. {
  257. service.Refresh();
  258. if (service.Status != ServiceControllerStatus.Stopped)
  259. ErrorLogger.WriteToErrorLog($"Could not kill service {service.ServiceName}.", e.StackTrace, "ServiceAction Error");
  260. }
  261. }
  262. if (RegistryDelete)
  263. {
  264. var action = new RegistryKeyAction()
  265. {
  266. KeyName = $@"HKLM\SYSTEM\CurrentControlSet\Services\{ServiceName}",
  267. Operation = RegistryKeyOperation.Delete
  268. };
  269. await action.RunTask();
  270. }
  271. else
  272. {
  273. try
  274. {
  275. ServiceInstaller ServiceInstallerObj = new ServiceInstaller();
  276. ServiceInstallerObj.Context = new InstallContext();
  277. ServiceInstallerObj.ServiceName = service.ServiceName;
  278. ServiceInstallerObj.Uninstall(null);
  279. }
  280. catch (Exception e)
  281. {
  282. ErrorLogger.WriteToErrorLog("Service uninstall failed: " + e.Message, e.StackTrace, "ServiceAction Warning", ServiceName);
  283. }
  284. cmdAction.Command = Environment.Is64BitOperatingSystem ?
  285. $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction delete" :
  286. $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction delete";
  287. if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread();
  288. }
  289. } else if (Operation == ServiceOperation.Start)
  290. {
  291. try
  292. {
  293. service.Start();
  294. }
  295. catch (Exception e)
  296. {
  297. service.Refresh();
  298. if (service.Status != ServiceControllerStatus.Running)
  299. ErrorLogger.WriteToErrorLog("Service start failed: " + e.Message, e.StackTrace, "ServiceAction Warning", ServiceName);
  300. }
  301. cmdAction.Command = Environment.Is64BitOperatingSystem ?
  302. $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction start" :
  303. $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction start";
  304. if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread();
  305. try
  306. {
  307. service.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromMilliseconds(5000));
  308. }
  309. catch (Exception e)
  310. {
  311. service.Refresh();
  312. if (service.Status != ServiceControllerStatus.Running)
  313. ErrorLogger.WriteToErrorLog("Service start timeout exceeded.", e.StackTrace, "ServiceAction Warning", ServiceName);
  314. }
  315. } else if (Operation == ServiceOperation.Stop)
  316. {
  317. try
  318. {
  319. service.Stop();
  320. }
  321. catch (Exception e)
  322. {
  323. service.Refresh();
  324. if (service.Status != ServiceControllerStatus.Stopped && service.Status != ServiceControllerStatus.StopPending)
  325. ErrorLogger.WriteToErrorLog("Service stop failed: " + e.Message, e.StackTrace, "ServiceAction Warning", ServiceName);
  326. }
  327. cmdAction.Command = Environment.Is64BitOperatingSystem ?
  328. $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction stop" :
  329. $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction stop";
  330. if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread();
  331. Console.WriteLine("Waiting for the service to stop...");
  332. try
  333. {
  334. service.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromMilliseconds(5000));
  335. }
  336. catch (Exception e)
  337. {
  338. service.Refresh();
  339. if (service.Status != ServiceControllerStatus.Stopped)
  340. ErrorLogger.WriteToErrorLog("Service stop timeout exceeded.", e.StackTrace, "ServiceAction Warning", ServiceName);
  341. }
  342. try
  343. {
  344. var killServ = new TaskKillAction()
  345. {
  346. ProcessID = Win32.ServiceEx.GetServiceProcessId(service.ServiceName)
  347. };
  348. await killServ.RunTask();
  349. }
  350. catch (Exception e)
  351. {
  352. service.Refresh();
  353. if (service.Status != ServiceControllerStatus.Stopped)
  354. ErrorLogger.WriteToErrorLog($"Could not kill dependent service {service.ServiceName}.",
  355. e.StackTrace, "ServiceAction Error");
  356. }
  357. } else if (Operation == ServiceOperation.Pause)
  358. {
  359. try
  360. {
  361. service.Pause();
  362. }
  363. catch (Exception e)
  364. {
  365. service.Refresh();
  366. if (service.Status != ServiceControllerStatus.Paused)
  367. ErrorLogger.WriteToErrorLog("Service pause failed: " + e.Message, e.StackTrace, "ServiceAction Warning", ServiceName);
  368. }
  369. cmdAction.Command = Environment.Is64BitOperatingSystem ?
  370. $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction pause" :
  371. $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction pause";
  372. if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread();
  373. try
  374. {
  375. service.WaitForStatus(ServiceControllerStatus.Paused, TimeSpan.FromMilliseconds(5000));
  376. }
  377. catch (Exception e)
  378. {
  379. service.Refresh();
  380. if (service.Status != ServiceControllerStatus.Paused)
  381. ErrorLogger.WriteToErrorLog("Service pause timeout exceeded.", e.StackTrace, "ServiceAction Warning", ServiceName);
  382. }
  383. }
  384. else if (Operation == ServiceOperation.Continue)
  385. {
  386. try
  387. {
  388. service.Pause();
  389. }
  390. catch (Exception e)
  391. {
  392. service.Refresh();
  393. if (service.Status != ServiceControllerStatus.Running)
  394. ErrorLogger.WriteToErrorLog("Service continue failed: " + e.Message, e.StackTrace, "ServiceAction Warning", ServiceName);
  395. }
  396. cmdAction.Command = Environment.Is64BitOperatingSystem ?
  397. $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction continue" :
  398. $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction continue";
  399. if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread();
  400. try
  401. {
  402. service.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromMilliseconds(5000));
  403. }
  404. catch (Exception e)
  405. {
  406. service.Refresh();
  407. if (service.Status != ServiceControllerStatus.Running)
  408. ErrorLogger.WriteToErrorLog("Service continue timeout exceeded.", e.StackTrace, "ServiceAction Warning", ServiceName);
  409. }
  410. }
  411. service?.Dispose();
  412. await Task.Delay(100);
  413. InProgress = false;
  414. return true;
  415. }
  416. }
  417. }