using System; using System.Collections.Generic; using System.Configuration.Install; using System.Diagnostics; using System.IO; using System.Linq; using System.Management; using System.Reflection; using System.Runtime.InteropServices; using System.Security.AccessControl; using System.ServiceProcess; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Windows; using TrustedUninstaller.Shared.Exceptions; using TrustedUninstaller.Shared.Tasks; using YamlDotNet.Serialization; namespace TrustedUninstaller.Shared.Actions { public class FileAction : TaskAction, ITaskAction { public void RunTaskOnMainThread() { throw new NotImplementedException(); } [YamlMember(typeof(string), Alias = "path")] public string RawPath { get; set; } [YamlMember(typeof(string), Alias = "prioritizeExe")] public bool ExeFirst { get; set; } = false; [YamlMember(typeof(string), Alias = "weight")] public int ProgressWeight { get; set; } = 2; [YamlMember(typeof(string), Alias = "useNSudoTI")] public bool TrustedInstaller { get; set; } = false; public int GetProgressWeight() => ProgressWeight; private bool InProgress { get; set; } public void ResetProgress() => InProgress = false; public string ErrorString() => $"FileAction failed to remove file or directory '{Environment.ExpandEnvironmentVariables(RawPath)}'."; private string GetRealPath() { return Environment.ExpandEnvironmentVariables(RawPath); } private string GetRealPath(string path) { return Environment.ExpandEnvironmentVariables(path); } public UninstallTaskStatus GetStatus() { if (InProgress) return UninstallTaskStatus.InProgress; var realPath = GetRealPath(); if (realPath.Contains("*")) { var lastToken = realPath.LastIndexOf("\\"); var parentPath = realPath.Remove(lastToken).TrimEnd('\\'); // This is to prevent it from re-iterating with an incorrect argument if (parentPath.Contains("*")) return UninstallTaskStatus.Completed; var filter = realPath.Substring(lastToken + 1); if (Directory.Exists(parentPath) && (Directory.GetFiles(parentPath, filter).Any() || Directory.GetDirectories(parentPath, filter).Any())) { return UninstallTaskStatus.ToDo; } else return UninstallTaskStatus.Completed; } var isFile = File.Exists(realPath); var isDirectory = Directory.Exists(realPath); return isFile || isDirectory ? UninstallTaskStatus.ToDo : UninstallTaskStatus.Completed; } [DllImport("Unlocker.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] private static extern bool EzUnlockFileW(string path); private async Task DeleteFile(string file, bool log = false) { if (!TrustedInstaller) { try { File.Delete(file);} catch (Exception e) { } if (File.Exists(file)) { try { var result = EzUnlockFileW(file); Testing.WriteLine($"ExUnlock on ({file}) result: " + result); } catch (Exception e) { ErrorLogger.WriteToErrorLog($"Error while unlocking file: " + e.Message, e.StackTrace, $"FileAction Error", file); } try {await Task.Run(() => File.Delete(file));} catch (Exception e) {Testing.WriteLine(e, "DeleteFile > File.Delete(File)");} CmdAction delAction = new CmdAction() { Command = $"del /q /f \"{file}\"" }; delAction.RunTaskOnMainThread(); } } else if (File.Exists("NSudoLC.exe")) { try { var result = EzUnlockFileW(file); Testing.WriteLine($"ExUnlock on ({file}) result: " + result); } catch (Exception e) { ErrorLogger.WriteToErrorLog($"Error while unlocking file: " + e.Message, e.StackTrace, $"FileAction Error", file); } RunAction tiDelAction = new RunAction() { Exe = "NSudoLC.exe", Arguments = $"-U:T -P:E -M:S -Priority:RealTime -UseCurrentConsole -Wait cmd /c \"del /q /f \"{file}\"\"", BaseDir = true, CreateWindow = false }; tiDelAction.RunTaskOnMainThread(); if (tiDelAction.Output != null) { if (log) ErrorLogger.WriteToErrorLog(tiDelAction.Output, Environment.StackTrace, $"FileAction Error", file); } } else { ErrorLogger.WriteToErrorLog($"NSudo was invoked with no supplied NSudo executable.", Environment.StackTrace, $"FileAction Error", file); } } private async Task RemoveDirectory(string dir, bool log = false) { if (!TrustedInstaller) { try { Directory.Delete(dir, true); } catch { } if (Directory.Exists(dir)) { Console.WriteLine("Directory still exists.. trying second method."); var deleteDirCmd = new CmdAction() { Command = $"rmdir /Q /S \"{dir}\"" }; deleteDirCmd.RunTaskOnMainThread(); if (deleteDirCmd.StandardError != null) { Console.WriteLine($"Error Output: {deleteDirCmd.StandardError}"); } if (deleteDirCmd.StandardOutput != null) { Console.WriteLine($"Standard Output: {deleteDirCmd.StandardOutput}"); } } } else if (File.Exists("NSudoLC.exe")) { RunAction tiDelAction = new RunAction() { Exe = "NSudoLC.exe", Arguments = $"-U:T -P:E -M:S -Priority:RealTime -UseCurrentConsole -Wait cmd /c \"rmdir /q /s \"{dir}\"\"", BaseDir = true, CreateWindow = false }; tiDelAction.RunTaskOnMainThread(); if (tiDelAction.Output != null) { if (log) ErrorLogger.WriteToErrorLog(tiDelAction.Output, Environment.StackTrace, $"FileAction Error", dir); } } else { ErrorLogger.WriteToErrorLog($"NSudo was invoked with no supplied NSudo executable.", Environment.StackTrace, $"FileAction Error", dir); } } private async Task DeleteItemsInDirectory(string dir, string filter = "*") { var realPath = GetRealPath(dir); var files = Directory.EnumerateFiles(realPath, filter); var directories = Directory.EnumerateDirectories(realPath, filter); if (ExeFirst) files = files.ToList().OrderByDescending(x => x.EndsWith(".exe")); var lockedFilesList = new List { "MpOAV.dll", "MsMpLics.dll", "EppManifest.dll", "MpAsDesc.dll", "MpClient.dll", "MsMpEng.exe" }; foreach (var file in files) { Console.WriteLine($"Deleting {file}..."); System.GC.Collect(); System.GC.WaitForPendingFinalizers(); await DeleteFile(file); if (File.Exists(file)) { TaskKillAction taskKillAction = new TaskKillAction(); if (file.EndsWith(".sys")) { var driverService = Path.GetFileNameWithoutExtension(file); try { //ServiceAction won't work here due to it not being able to detect driver services. var cmdAction = new CmdAction(); Console.WriteLine($"Removing driver service {driverService}..."); // TODO: Replace with win32 try { ServiceInstaller ServiceInstallerObj = new ServiceInstaller(); ServiceInstallerObj.Context = new InstallContext(); ServiceInstallerObj.ServiceName = driverService; ServiceInstallerObj.Uninstall(null); } catch (Exception e) { ErrorLogger.WriteToErrorLog("Service uninstall failed: " + e.Message, e.StackTrace, "FileAction Warning", RawPath); } cmdAction.Command = Environment.Is64BitOperatingSystem ? $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {driverService} -caction stop" : $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {driverService} -caction stop"; if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread(); cmdAction.Command = Environment.Is64BitOperatingSystem ? $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {driverService} -caction delete" : $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {driverService} -caction delete"; if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread(); } catch (Exception servException) { ErrorLogger.WriteToErrorLog(servException.Message, servException.StackTrace, $"FileAction Error: Error while trying to delete driver service {driverService}.", file); } } if (lockedFilesList.Contains(Path.GetFileName(file))) { TaskKillAction killAction = new TaskKillAction() { ProcessName = "MsMpEng" }; await killAction.RunTask(); killAction.ProcessName = "NisSrv"; await killAction.RunTask(); killAction.ProcessName = "SecurityHealthService"; await killAction.RunTask(); killAction.ProcessName = "smartscreen"; await killAction.RunTask(); } var processes = new List(); try { processes = WinUtil.WhoIsLocking(file); } catch (Exception e) { ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, $"FileAction Error", file); } var delay = 0; int svcCount = 0; foreach (var svchost in processes.Where(x => x.ProcessName.Equals("svchost"))) { try { foreach (var serviceName in Win32.ServiceEx.GetServicesFromProcessId(svchost.Id)) { svcCount++; try { var serviceController = ServiceController.GetServices().FirstOrDefault(x => x.ServiceName.Equals(serviceName)); if (serviceController != null) svcCount += serviceController.DependentServices.Length; } catch (Exception e) { Console.WriteLine($"\r\nError: Could not get amount of dependent services for {serviceName}.\r\nException: " + e.Message); } } } catch (Exception e) { Console.WriteLine($"\r\nError: Could not get amount of services locking file.\r\nException: " + e.Message); } } while (processes.Any() && delay <= 800) { Console.WriteLine("Processes locking the file:"); foreach (var process in processes) { Console.WriteLine(process.ProcessName); } if (svcCount > 10) { Console.WriteLine("Amount of locking services exceeds 10, skipping..."); break; } foreach (var process in processes) { try { if (process.ProcessName.Equals("TrustedUninstaller.CLI")) { Console.WriteLine("Skipping TU.CLI..."); continue; } if (Regex.Match(process.ProcessName, "ame.?wizard", RegexOptions.IgnoreCase).Success) { Console.WriteLine("Skipping AME Wizard..."); continue; } taskKillAction.ProcessName = process.ProcessName; taskKillAction.ProcessID = process.Id; Console.WriteLine($"Killing locking process {process.ProcessName} with PID {process.Id}..."); } catch (InvalidOperationException) { // Calling ProcessName on a process object that has exited will thrown this exception causing the // entire loop to abort. Since killing a process takes a bit of time, another process in the loop // could exit during that time. This accounts for that. continue; } try { await taskKillAction.RunTask(); } catch (Exception e) { ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, $"FileAction Error: Could not kill process {taskKillAction.ProcessName}."); } } // This gives any obstinant processes some time to unlock the file on their own. // // This could be done above but it's likely to cause HasExited errors if delays are // introduced after WhoIsLocking. System.Threading.Thread.Sleep(delay); try { processes = WinUtil.WhoIsLocking(file); } catch (Exception e) { ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, $"FileAction Error", file); } delay += 100; } if (delay >= 800) ErrorLogger.WriteToErrorLog($"Could not kill locking processes for file '{file}'. Process termination loop exceeded max cycles (8).", Environment.StackTrace, "FileAction Error"); if (Path.GetExtension(file).Equals(".exe", StringComparison.OrdinalIgnoreCase)) { await new TaskKillAction() { ProcessName = Path.GetFileNameWithoutExtension(file) }.RunTask(); } await DeleteFile(file, true); } } //Loop through any subdirectories foreach (var directory in directories) { //Deletes the content of the directory await DeleteItemsInDirectory(directory); System.GC.Collect(); System.GC.WaitForPendingFinalizers(); await RemoveDirectory(directory, true); if (Directory.Exists(directory)) ErrorLogger.WriteToErrorLog($"Could not remove directory '{directory}'.", Environment.StackTrace, $"FileAction Error"); } } public async Task RunTask() { if (InProgress) throw new TaskInProgressException("Another File action was called while one was in progress."); InProgress = true; var realPath = GetRealPath(); Console.WriteLine($"Removing file or directory '{realPath}'..."); if (realPath.Contains("*")) { var lastToken = realPath.LastIndexOf("\\"); var parentPath = realPath.Remove(lastToken).TrimEnd('\\'); if (parentPath.Contains("*")) throw new ArgumentException("Parent directories to a given file filter cannot contain wildcards."); var filter = realPath.Substring(lastToken + 1); await DeleteItemsInDirectory(parentPath, filter); InProgress = false; return true; } var isFile = File.Exists(realPath); var isDirectory = Directory.Exists(realPath); if (isDirectory) { System.GC.Collect(); System.GC.WaitForPendingFinalizers(); await RemoveDirectory(realPath); if (Directory.Exists(realPath)) { CmdAction permAction = new CmdAction() { Command = $"takeown /f \"{realPath}\" /r /d Y>NUL & icacls \"{realPath}\" /t /grant Administrators:F /c > NUL", Timeout = 5000 }; try { permAction.RunTaskOnMainThread(); } catch (Exception e) { ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, "FileAction Error", realPath); } try { if (realPath.Contains("Defender")) { TaskKillAction killAction = new TaskKillAction() { ProcessName = "MsMpEng" }; await killAction.RunTask(); killAction.ProcessName = "NisSrv"; await killAction.RunTask(); killAction.ProcessName = "SecurityHealthService"; await killAction.RunTask(); killAction.ProcessName = "smartscreen"; await killAction.RunTask(); } } catch (Exception e) { ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, $"FileAction Error", realPath); } await RemoveDirectory(realPath, true); if (Directory.Exists(realPath)) { //Delete the files in the initial directory. DOES delete directories. await DeleteItemsInDirectory(realPath); System.GC.Collect(); System.GC.WaitForPendingFinalizers(); await RemoveDirectory(realPath, true); } } } if (isFile) { try { var lockedFilesList = new List { "MpOAV.dll", "MsMpLics.dll", "EppManifest.dll", "MpAsDesc.dll", "MpClient.dll", "MsMpEng.exe" }; var fileName = realPath.Split('\\').LastOrDefault(); System.GC.Collect(); System.GC.WaitForPendingFinalizers(); await DeleteFile(realPath); if (File.Exists(realPath)) { CmdAction permAction = new CmdAction() { Command = $"takeown /f \"{realPath}\" /r /d Y>NUL & icacls \"{realPath}\" /t /grant Administrators:F /c > NUL", Timeout = 5000 }; try { permAction.RunTaskOnMainThread(); } catch (Exception e) { ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, "FileAction Error", realPath); } TaskKillAction taskKillAction = new TaskKillAction(); if (realPath.EndsWith(".sys")) { var driverService = Path.GetFileNameWithoutExtension(realPath); try { //ServiceAction won't work here due to it not being able to detect driver services. var cmdAction = new CmdAction(); Console.WriteLine($"Removing driver service {driverService}..."); // TODO: Replace with win32 try { ServiceInstaller ServiceInstallerObj = new ServiceInstaller(); ServiceInstallerObj.Context = new InstallContext(); ServiceInstallerObj.ServiceName = driverService; ServiceInstallerObj.Uninstall(null); } catch (Exception e) { ErrorLogger.WriteToErrorLog("Service uninstall failed: " + e.Message, e.StackTrace, "FileAction Warning", RawPath); } WinUtil.CheckKph(); cmdAction.Command = Environment.Is64BitOperatingSystem ? $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {driverService} -caction stop" : $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {driverService} -caction stop"; if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread(); cmdAction.Command = Environment.Is64BitOperatingSystem ? $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {driverService} -caction delete" : $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {driverService} -caction delete"; if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread(); } catch (Exception servException) { ErrorLogger.WriteToErrorLog(servException.Message, servException.StackTrace, $"FileAction Error: Error trying to delete driver service {driverService}.", realPath); } } if (lockedFilesList.Contains(fileName)) { TaskKillAction killAction = new TaskKillAction() { ProcessName = "MsMpEng" }; await killAction.RunTask(); killAction.ProcessName = "NisSrv"; await killAction.RunTask(); killAction.ProcessName = "SecurityHealthService"; await killAction.RunTask(); killAction.ProcessName = "smartscreen"; await killAction.RunTask(); } var processes = new List(); try { processes = WinUtil.WhoIsLocking(realPath); } catch (Exception e) { ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, $"FileAction Error", realPath); } var delay = 0; int svcCount = 0; foreach (var svchost in processes.Where(x => x.ProcessName.Equals("svchost"))) { try { foreach (var serviceName in Win32.ServiceEx.GetServicesFromProcessId(svchost.Id)) { svcCount++; try { var serviceController = ServiceController.GetServices().FirstOrDefault(x => x.ServiceName.Equals(serviceName)); if (serviceController != null) svcCount += serviceController.DependentServices.Length; } catch (Exception e) { Console.WriteLine($"\r\nError: Could not get amount of dependent services for {serviceName}.\r\nException: " + e.Message); } } } catch (Exception e) { Console.WriteLine($"\r\nError: Could not get amount of services locking file.\r\nException: " + e.Message); } } if (svcCount > 8) Console.WriteLine("Amount of locking services exceeds 8, skipping..."); while (processes.Any() && delay <= 800 && svcCount <= 8) { Console.WriteLine("Processes locking the file:"); foreach (var process in processes) { Console.WriteLine(process.ProcessName); } foreach (var process in processes) { try { if (process.ProcessName.Equals("TrustedUninstaller.CLI")) { Console.WriteLine("Skipping TU.CLI..."); continue; } if (Regex.Match(process.ProcessName, "ame.?wizard", RegexOptions.IgnoreCase).Success) { Console.WriteLine("Skipping AME Wizard..."); continue; } taskKillAction.ProcessName = process.ProcessName; taskKillAction.ProcessID = process.Id; Console.WriteLine($"Killing {process.ProcessName} with PID {process.Id}... it is locking {realPath}"); } catch (InvalidOperationException) { // Calling ProcessName on a process object that has exited will thrown this exception causing the // entire loop to abort. Since killing a process takes a bit of time, another process in the loop // could exit during that time. This accounts for that. continue; } try { await taskKillAction.RunTask(); } catch (Exception e) { ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, $"FileAction Error: Could not kill process {process.ProcessName}."); } } // This gives any obstinant processes some time to unlock the file on their own. // // This could be done above but it's likely to cause HasExited errors if delays are // introduced after WhoIsLocking. System.Threading.Thread.Sleep(delay); try { processes = WinUtil.WhoIsLocking(realPath); } catch (Exception e) { ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, $"FileAction Error", realPath); } delay += 100; } if (delay >= 800) ErrorLogger.WriteToErrorLog($"Could not kill locking processes for file '{realPath}'. Process termination loop exceeded max cycles (8).", Environment.StackTrace, "FileAction Error"); if (Path.GetExtension(realPath).Equals(".exe", StringComparison.OrdinalIgnoreCase)) { await new TaskKillAction() { ProcessName = Path.GetFileNameWithoutExtension(realPath) }.RunTask(); } await DeleteFile(realPath, true); } } catch (Exception e) { ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, $"FileAction Error: Error while trying to delete {realPath}."); } } else { Console.WriteLine($"File or directory '{realPath}' not found."); } InProgress = false; return true; } } }