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.

717 lines
33 KiB

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;
}
}
}