|
|
- 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<string> { "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<Process>();
- 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<bool> 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<string> { "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<Process>();
- 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;
- }
- }
- }
|