using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Management; using System.Runtime.InteropServices; using System.Text.RegularExpressions; using System.Threading.Tasks; using TrustedUninstaller.Shared.Tasks; using YamlDotNet.Serialization; namespace TrustedUninstaller.Shared.Actions { class TaskKillAction : ITaskAction { [DllImport("kernel32.dll", SetLastError=true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode); [YamlMember(typeof(string), Alias = "name")] public string? ProcessName { get; set; } [YamlMember(typeof(string), Alias = "pathContains")] public string? PathContains { get; set; } [YamlMember(typeof(string), Alias = "weight")] public int ProgressWeight { get; set; } = 2; public int GetProgressWeight() => ProgressWeight; private bool InProgress { get; set; } public void ResetProgress() => InProgress = false; public int? ProcessID { get; set; } public string ErrorString() { string text = $"TaskKillAction failed to kill processes matching '{ProcessName}'."; try { var processes = GetProcess().Select(process => process.ProcessName).Distinct().ToList(); if (processes.Count > 1) { text = $"TaskKillAction failed to kill processes:"; foreach (var process in processes) { text += "|NEWLINE|" + process; } } else if (processes.Count == 1) text = $"TaskKillAction failed to kill process {processes[0]}."; } catch (Exception) { } return text; } public UninstallTaskStatus GetStatus() { if (InProgress) { return UninstallTaskStatus.InProgress; } List processToTerminate = new List(); if (ProcessID.HasValue) { try { processToTerminate.Add(Process.GetProcessById((int)ProcessID)); } catch (Exception) { } } else { processToTerminate = GetProcess().ToList(); } return processToTerminate.Any() ? UninstallTaskStatus.ToDo : UninstallTaskStatus.Completed; } private IEnumerable GetProcess() { if (ProcessName == null) return new List(); if (ProcessName.EndsWith("*") && ProcessName.StartsWith("*")) return Process.GetProcesses() .Where(process => process.ProcessName.IndexOf(ProcessName.Trim('*'), StringComparison.CurrentCultureIgnoreCase) >= 0); if (ProcessName.EndsWith("*")) return Process.GetProcesses() .Where(process => process.ProcessName.StartsWith(ProcessName.TrimEnd('*'), StringComparison.CurrentCultureIgnoreCase)); if (ProcessName.StartsWith("*")) return Process.GetProcesses() .Where(process => process.ProcessName.EndsWith(ProcessName.TrimStart('*'), StringComparison.CurrentCultureIgnoreCase)); return Process.GetProcessesByName(ProcessName); } [DllImport("kernel32.dll", SetLastError=true)] static extern bool IsProcessCritical(IntPtr hProcess, ref bool Critical); private readonly string[] RegexNoKill = { "lsass", "csrss", "winlogon", "TrustedUninstaller\\.CLI", "dwm", "conhost", "ame.?wizard", "ame.?assassin" }; // These processes give access denied errors when getting their handle for IsProcessCritical. // TODO: Investigate how to properly acquire permissions. private readonly string[] RegexNotCritical = { "SecurityHealthService", "wscsvc", "MsMpEng", "SgrmBroker" }; public async Task RunTask() { InProgress = true; if (string.IsNullOrEmpty(ProcessName) && ProcessID.HasValue) { Console.WriteLine($"Killing process with PID '{ProcessID.Value}'..."); } else { if (ProcessName != null && RegexNoKill.Any(regex => Regex.Match(ProcessName, regex, RegexOptions.IgnoreCase).Success)) { Console.WriteLine($"Skipping {ProcessName}..."); return false; } Console.WriteLine($"Killing processes matching '{ProcessName}'..."); } var cmdAction = new CmdAction(); if (ProcessName != null) { //If the service is svchost, we stop the service instead of killing it. if (ProcessName.Contains("svchost")) { // bool serviceFound = false; try { using var search = new ManagementObjectSearcher($"select * from Win32_Service where ProcessId = '{ProcessID}'"); foreach (ManagementObject queryObj in search.Get()) { var serviceName = (string)queryObj["Name"]; // Access service name var stopServ = new ServiceAction() { ServiceName = serviceName, Operation = ServiceOperation.Stop }; await stopServ.RunTask(); } } catch (NullReferenceException e) { Console.WriteLine($"A service with PID: {ProcessID} could not be found."); ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, $"Could not find service with PID {ProcessID}."); } /* foreach (var serv in servicesToDelete) { //The ID can only be associated with one of the services, there's no need to loop through //them all if we already found the service. if (serviceFound) { break; } try { using var search = new ManagementObjectSearcher($"select ProcessId from Win32_Service where Name = '{serv}'").Get(); var servID = (uint)search.OfType().FirstOrDefault()["ProcessID"]; if (servID == ProcessID) { serviceFound = true; } search.Dispose(); } catch (Exception e) { var search = new ManagementObjectSearcher($"select Name from Win32_Service where ProcessID = '{ProcessID}'").Get(); var servName = search.OfType().FirstOrDefault()["Name"]; Console.WriteLine($"Could not find {servName} but PID {ProcessID} still exists."); ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, $"Exception Type: {e.GetType()}"); return false; } }*/ //None of the services listed, we shouldn't kill svchost. /* if (!serviceFound) { var search = new ManagementObjectSearcher($"select Name from Win32_Service where ProcessID = '{ProcessID}'").Get(); var servName = search.OfType().FirstOrDefault()["Name"]; Console.WriteLine($"A critical system process \"{servName}\" with PID {ProcessID} caused the Wizard to fail."); await WinUtil.UninstallDriver(); Environment.Exit(-1); return false; }*/ await Task.Delay(100); InProgress = false; return true; } if (PathContains != null && !ProcessID.HasValue) { var processes = GetProcess().ToList(); if (processes.Count > 0) Console.WriteLine("Processes:"); foreach (var process in processes.Where(x => x.MainModule.FileName.Contains(PathContains))) { Console.WriteLine(process.ProcessName + " - " + process.Id); if (!RegexNotCritical.Any(x => Regex.Match(process.ProcessName, x, RegexOptions.IgnoreCase).Success)) { bool isCritical = false; IsProcessCritical(process.Handle, ref isCritical); if (isCritical) { Console.WriteLine($"{process.ProcessName} is a critical process, skipping..."); continue; } } cmdAction.Command = Environment.Is64BitOperatingSystem ? $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {process.Id} -caction terminate" : $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {process.Id} -caction terminate"; await cmdAction.RunTask(); int i = 0; while (i <= 15 && GetProcess().Any(x => x.Id == process.Id && x.ProcessName == process.ProcessName)) { await Task.Delay(300); i++; } if (i >= 15) ErrorLogger.WriteToErrorLog($"Task kill timeout exceeded.", Environment.StackTrace, "TaskKillAction Error"); } InProgress = false; return true; } } if (ProcessID.HasValue) { if (ProcessName != null && ProcessName.Equals("explorer", StringComparison.OrdinalIgnoreCase)) { try { var process = Process.GetProcessById(ProcessID.Value); TerminateProcess(process.Handle, 1); } catch (Exception) { cmdAction.Command = Environment.Is64BitOperatingSystem ? $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {ProcessID} -caction terminate" : $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {ProcessID} -caction terminate"; await cmdAction.RunTask(); } } else { var process = Process.GetProcessById(ProcessID.Value); if (!RegexNotCritical.Any(x => Regex.Match(process.ProcessName, x, RegexOptions.IgnoreCase).Success)) { bool isCritical = false; IsProcessCritical(process.Handle, ref isCritical); if (isCritical) { Console.WriteLine($"{process.ProcessName} is a critical process, skipping..."); return false; } } cmdAction.Command = Environment.Is64BitOperatingSystem ? $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {ProcessID} -caction terminate" : $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {ProcessID} -caction terminate"; await cmdAction.RunTask(); } await Task.Delay(100); } else { var processes = GetProcess().ToList(); if (processes.Count > 0) Console.WriteLine("Processes:"); foreach (var process in processes) { Console.WriteLine(process.ProcessName + " - " + process.Id); cmdAction.Command = Environment.Is64BitOperatingSystem ? $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {process.ProcessName}.exe -caction terminate" : $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {process.ProcessName}.exe -caction terminate"; if (process.ProcessName == "explorer") TerminateProcess(process.Handle, 1); else { if (!RegexNotCritical.Any(x => Regex.Match(process.ProcessName, x, RegexOptions.IgnoreCase).Success)) { bool isCritical = false; IsProcessCritical(process.Handle, ref isCritical); if (isCritical) { Console.WriteLine($"{process.ProcessName} is a critical process, skipping..."); continue; } } await cmdAction.RunTask(); } int i = 0; while (i <= 15 && GetProcess().Any(x => x.Id == process.Id && x.ProcessName == process.ProcessName)) { await Task.Delay(300); i++; } if (i >= 15) ErrorLogger.WriteToErrorLog($"Task kill timeout exceeded.", Environment.StackTrace, "TaskKillAction Error"); } } InProgress = false; return true; } } }