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.

314 lines
14 KiB

1 year ago
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Linq;
  5. using System.Management;
  6. using System.Runtime.InteropServices;
  7. using System.Text.RegularExpressions;
  8. using System.Threading.Tasks;
  9. using TrustedUninstaller.Shared.Tasks;
  10. using YamlDotNet.Serialization;
  11. namespace TrustedUninstaller.Shared.Actions
  12. {
  13. class TaskKillAction : ITaskAction
  14. {
  15. [DllImport("kernel32.dll", SetLastError=true)]
  16. [return: MarshalAs(UnmanagedType.Bool)]
  17. static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode);
  18. [YamlMember(typeof(string), Alias = "name")]
  19. public string? ProcessName { get; set; }
  20. [YamlMember(typeof(string), Alias = "pathContains")]
  21. public string? PathContains { get; set; }
  22. [YamlMember(typeof(string), Alias = "weight")]
  23. public int ProgressWeight { get; set; } = 2;
  24. public int GetProgressWeight() => ProgressWeight;
  25. private bool InProgress { get; set; }
  26. public void ResetProgress() => InProgress = false;
  27. public int? ProcessID { get; set; }
  28. public string ErrorString()
  29. {
  30. string text = $"TaskKillAction failed to kill processes matching '{ProcessName}'.";
  31. try
  32. {
  33. var processes = GetProcess().Select(process => process.ProcessName).Distinct().ToList();
  34. if (processes.Count > 1)
  35. {
  36. text = $"TaskKillAction failed to kill processes:";
  37. foreach (var process in processes)
  38. {
  39. text += "|NEWLINE|" + process;
  40. }
  41. }
  42. else if (processes.Count == 1) text = $"TaskKillAction failed to kill process {processes[0]}.";
  43. } catch (Exception) { }
  44. return text;
  45. }
  46. public UninstallTaskStatus GetStatus()
  47. {
  48. if (InProgress)
  49. {
  50. return UninstallTaskStatus.InProgress;
  51. }
  52. List<Process> processToTerminate = new List<Process>();
  53. if (ProcessID.HasValue)
  54. {
  55. try { processToTerminate.Add(Process.GetProcessById((int)ProcessID)); } catch (Exception) { }
  56. }
  57. else
  58. {
  59. processToTerminate = GetProcess().ToList();
  60. }
  61. return processToTerminate.Any() ? UninstallTaskStatus.ToDo : UninstallTaskStatus.Completed;
  62. }
  63. private IEnumerable<Process> GetProcess()
  64. {
  65. if (ProcessName == null) return new List<Process>();
  66. if (ProcessName.EndsWith("*") && ProcessName.StartsWith("*")) return Process.GetProcesses()
  67. .Where(process => process.ProcessName.IndexOf(ProcessName.Trim('*'), StringComparison.CurrentCultureIgnoreCase) >= 0);
  68. if (ProcessName.EndsWith("*")) return Process.GetProcesses()
  69. .Where(process => process.ProcessName.StartsWith(ProcessName.TrimEnd('*'), StringComparison.CurrentCultureIgnoreCase));
  70. if (ProcessName.StartsWith("*")) return Process.GetProcesses()
  71. .Where(process => process.ProcessName.EndsWith(ProcessName.TrimStart('*'), StringComparison.CurrentCultureIgnoreCase));
  72. return Process.GetProcessesByName(ProcessName);
  73. }
  74. [DllImport("kernel32.dll", SetLastError=true)]
  75. static extern bool IsProcessCritical(IntPtr hProcess, ref bool Critical);
  76. private readonly string[] RegexNoKill = { "lsass", "csrss", "winlogon", "TrustedUninstaller\\.CLI", "dwm", "conhost", "ame.?wizard", "ame.?assassin" };
  77. // These processes give access denied errors when getting their handle for IsProcessCritical.
  78. // TODO: Investigate how to properly acquire permissions.
  79. private readonly string[] RegexNotCritical = { "SecurityHealthService", "wscsvc", "MsMpEng", "SgrmBroker" };
  80. public async Task<bool> RunTask()
  81. {
  82. InProgress = true;
  83. if (string.IsNullOrEmpty(ProcessName) && ProcessID.HasValue)
  84. {
  85. Console.WriteLine($"Killing process with PID '{ProcessID.Value}'...");
  86. }
  87. else
  88. {
  89. if (ProcessName != null && RegexNoKill.Any(regex => Regex.Match(ProcessName, regex, RegexOptions.IgnoreCase).Success))
  90. {
  91. Console.WriteLine($"Skipping {ProcessName}...");
  92. return false;
  93. }
  94. Console.WriteLine($"Killing processes matching '{ProcessName}'...");
  95. }
  96. var cmdAction = new CmdAction();
  97. if (ProcessName != null)
  98. {
  99. //If the service is svchost, we stop the service instead of killing it.
  100. if (ProcessName.Contains("svchost"))
  101. {
  102. // bool serviceFound = false;
  103. try
  104. {
  105. using var search = new ManagementObjectSearcher($"select * from Win32_Service where ProcessId = '{ProcessID}'");
  106. foreach (ManagementObject queryObj in search.Get())
  107. {
  108. var serviceName = (string)queryObj["Name"]; // Access service name
  109. var stopServ = new ServiceAction()
  110. {
  111. ServiceName = serviceName,
  112. Operation = ServiceOperation.Stop
  113. };
  114. await stopServ.RunTask();
  115. }
  116. }
  117. catch (NullReferenceException e)
  118. {
  119. Console.WriteLine($"A service with PID: {ProcessID} could not be found.");
  120. ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, $"Could not find service with PID {ProcessID}.");
  121. }
  122. /* foreach (var serv in servicesToDelete)
  123. {
  124. //The ID can only be associated with one of the services, there's no need to loop through
  125. //them all if we already found the service.
  126. if (serviceFound)
  127. {
  128. break;
  129. }
  130. try
  131. {
  132. using var search = new ManagementObjectSearcher($"select ProcessId from Win32_Service where Name = '{serv}'").Get();
  133. var servID = (uint)search.OfType<ManagementObject>().FirstOrDefault()["ProcessID"];
  134. if (servID == ProcessID)
  135. {
  136. serviceFound = true;
  137. }
  138. search.Dispose();
  139. }
  140. catch (Exception e)
  141. {
  142. var search = new ManagementObjectSearcher($"select Name from Win32_Service where ProcessID = '{ProcessID}'").Get();
  143. var servName = search.OfType<ManagementObject>().FirstOrDefault()["Name"];
  144. Console.WriteLine($"Could not find {servName} but PID {ProcessID} still exists.");
  145. ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, $"Exception Type: {e.GetType()}");
  146. return false;
  147. }
  148. }*/
  149. //None of the services listed, we shouldn't kill svchost.
  150. /* if (!serviceFound)
  151. {
  152. var search = new ManagementObjectSearcher($"select Name from Win32_Service where ProcessID = '{ProcessID}'").Get();
  153. var servName = search.OfType<ManagementObject>().FirstOrDefault()["Name"];
  154. Console.WriteLine($"A critical system process \"{servName}\" with PID {ProcessID} caused the Wizard to fail.");
  155. await WinUtil.UninstallDriver();
  156. Environment.Exit(-1);
  157. return false;
  158. }*/
  159. await Task.Delay(100);
  160. InProgress = false;
  161. return true;
  162. }
  163. if (PathContains != null && !ProcessID.HasValue)
  164. {
  165. var processes = GetProcess().ToList();
  166. if (processes.Count > 0) Console.WriteLine("Processes:");
  167. foreach (var process in processes.Where(x => x.MainModule.FileName.Contains(PathContains)))
  168. {
  169. Console.WriteLine(process.ProcessName + " - " + process.Id);
  170. if (!RegexNotCritical.Any(x => Regex.Match(process.ProcessName, x, RegexOptions.IgnoreCase).Success)) {
  171. bool isCritical = false;
  172. IsProcessCritical(process.Handle, ref isCritical);
  173. if (isCritical)
  174. {
  175. Console.WriteLine($"{process.ProcessName} is a critical process, skipping...");
  176. continue;
  177. }
  178. }
  179. cmdAction.Command = Environment.Is64BitOperatingSystem ?
  180. $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {process.Id} -caction terminate" :
  181. $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {process.Id} -caction terminate";
  182. await cmdAction.RunTask();
  183. int i = 0;
  184. while (i <= 15 && GetProcess().Any(x => x.Id == process.Id && x.ProcessName == process.ProcessName))
  185. {
  186. await Task.Delay(300);
  187. i++;
  188. }
  189. if (i >= 15) ErrorLogger.WriteToErrorLog($"Task kill timeout exceeded.", Environment.StackTrace, "TaskKillAction Error");
  190. }
  191. InProgress = false;
  192. return true;
  193. }
  194. }
  195. if (ProcessID.HasValue)
  196. {
  197. if (ProcessName != null && ProcessName.Equals("explorer", StringComparison.OrdinalIgnoreCase))
  198. {
  199. try
  200. {
  201. var process = Process.GetProcessById(ProcessID.Value);
  202. TerminateProcess(process.Handle, 1);
  203. } catch (Exception)
  204. {
  205. cmdAction.Command = Environment.Is64BitOperatingSystem ?
  206. $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {ProcessID} -caction terminate" :
  207. $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {ProcessID} -caction terminate";
  208. await cmdAction.RunTask();
  209. }
  210. }
  211. else
  212. {
  213. var process = Process.GetProcessById(ProcessID.Value);
  214. if (!RegexNotCritical.Any(x => Regex.Match(process.ProcessName, x, RegexOptions.IgnoreCase).Success))
  215. {
  216. bool isCritical = false;
  217. IsProcessCritical(process.Handle, ref isCritical);
  218. if (isCritical)
  219. {
  220. Console.WriteLine($"{process.ProcessName} is a critical process, skipping...");
  221. return false;
  222. }
  223. }
  224. cmdAction.Command = Environment.Is64BitOperatingSystem ?
  225. $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {ProcessID} -caction terminate" :
  226. $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {ProcessID} -caction terminate";
  227. await cmdAction.RunTask();
  228. }
  229. await Task.Delay(100);
  230. }
  231. else
  232. {
  233. var processes = GetProcess().ToList();
  234. if (processes.Count > 0) Console.WriteLine("Processes:");
  235. foreach (var process in processes)
  236. {
  237. Console.WriteLine(process.ProcessName + " - " + process.Id);
  238. cmdAction.Command = Environment.Is64BitOperatingSystem ?
  239. $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {process.ProcessName}.exe -caction terminate" :
  240. $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {process.ProcessName}.exe -caction terminate";
  241. if (process.ProcessName == "explorer") TerminateProcess(process.Handle, 1);
  242. else
  243. {
  244. if (!RegexNotCritical.Any(x => Regex.Match(process.ProcessName, x, RegexOptions.IgnoreCase).Success))
  245. {
  246. bool isCritical = false;
  247. IsProcessCritical(process.Handle, ref isCritical);
  248. if (isCritical)
  249. {
  250. Console.WriteLine($"{process.ProcessName} is a critical process, skipping...");
  251. continue;
  252. }
  253. }
  254. await cmdAction.RunTask();
  255. }
  256. int i = 0;
  257. while (i <= 15 && GetProcess().Any(x => x.Id == process.Id && x.ProcessName == process.ProcessName))
  258. {
  259. await Task.Delay(300);
  260. i++;
  261. }
  262. if (i >= 15) ErrorLogger.WriteToErrorLog($"Task kill timeout exceeded.", Environment.StackTrace, "TaskKillAction Error");
  263. }
  264. }
  265. InProgress = false;
  266. return true;
  267. }
  268. }
  269. }