diff --git a/TrustedUninstaller.CLI/CLI.cs b/TrustedUninstaller.CLI/CLI.cs index d2ecef6..96368ea 100644 --- a/TrustedUninstaller.CLI/CLI.cs +++ b/TrustedUninstaller.CLI/CLI.cs @@ -142,6 +142,30 @@ namespace TrustedUninstaller.CLI AmeliorationUtil.Playbook.Options = defaultOptions; } + if (!AmeliorationUtil.Playbook.UseKernelDriver.HasValue) + { + if (new RegistryValueAction() + { + KeyName = @"HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity", + Value = "Enabled", + Data = 1, + }.GetStatus() + != UninstallTaskStatus.Completed + && + new RegistryValueAction() + { + KeyName = @"HKLM\SYSTEM\CurrentControlSet\Control\CI\Config", + Value = "VulnerableDriverBlocklistEnable", + Data = 0, + }.GetStatus() + == UninstallTaskStatus.Completed && (await GetDefenderToggles()).All(toggleOn => !toggleOn)) + { + AmeliorationUtil.UseKernelDriver = true; + } + } + else + AmeliorationUtil.UseKernelDriver = AmeliorationUtil.Playbook.UseKernelDriver.Value; + try { if (!Directory.Exists(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ame-assassin"))) @@ -150,9 +174,11 @@ namespace TrustedUninstaller.CLI ExtractResourceFolder("resources", Directory.GetCurrentDirectory()); ExtractArchive(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "CLI-Resources.7z"), AppDomain.CurrentDomain.BaseDirectory); + if (AmeliorationUtil.UseKernelDriver) ExtractArchive(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ProcessInformer.7z"), AppDomain.CurrentDomain.BaseDirectory); try { File.Delete(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "CLI-Resources.7z")); + File.Delete(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ProcessInformer.7z")); } catch (Exception e) { } diff --git a/TrustedUninstaller.CLI/Properties/resources/CLI-Resources.7z b/TrustedUninstaller.CLI/Properties/resources/CLI-Resources.7z index ad024ba..d82b2f3 100644 Binary files a/TrustedUninstaller.CLI/Properties/resources/CLI-Resources.7z and b/TrustedUninstaller.CLI/Properties/resources/CLI-Resources.7z differ diff --git a/TrustedUninstaller.CLI/Properties/resources/ProcessInformer.7z b/TrustedUninstaller.CLI/Properties/resources/ProcessInformer.7z new file mode 100644 index 0000000..e829bbd Binary files /dev/null and b/TrustedUninstaller.CLI/Properties/resources/ProcessInformer.7z differ diff --git a/TrustedUninstaller.CLI/TrustedUninstaller.CLI.csproj b/TrustedUninstaller.CLI/TrustedUninstaller.CLI.csproj index 33ab5ac..41cd477 100644 --- a/TrustedUninstaller.CLI/TrustedUninstaller.CLI.csproj +++ b/TrustedUninstaller.CLI/TrustedUninstaller.CLI.csproj @@ -70,6 +70,13 @@ embedded SINGLE + + bin\x64\Release\ + x64 + true + embedded + SINGLE;DEBUG + B8F0A67800B779C5CEF49BEAB6E5247E373F9452 @@ -91,21 +98,19 @@ app.manifest - + for /f "usebackq delims=" %%A in (`DIR /B /S /A:d "$(SolutionDir)" ^| FINDSTR /R /c:".*\\bin\\.*\\de$" /c:".*\\bin\\.*\\en$" /c:".*\\bin\\.*\\es$" /c:".*\\bin\\.*\\fr$" /c:".*\\bin\\.*\\it$" /c:".*\\bin\\.*\\ja$" /c:".*\\bin\\.*\\ko$" /c:".*\\bin\\.*\\ru$" /c:".*\\bin\\.*\\zh-Hans$" /c:".*\\bin\\.*\\zh-Hant$" /c:".*\\bin\\.*\\pl$" /c:".*\\bin\\.*\\zh-CN$"`) do (RMDIR /Q /S "%%A" & cmd /c "exit /b 0") - cmd /c "echo Compiled> "$(SolutionDir)\TrustedUninstaller.GUI\gui-builder\Compiled.txt"" - - - PowerShell -NoP -C "Get-Process 'ame-builder' -EA SilentlyContinue | Wait-Process -EA SilentlyContinue; EXIT 0" - PowerShell -NoP -C "Start-Process '$(SolutionDir)\TrustedUninstaller.GUI\gui-builder\ame-builder.exe' -ArgumentList 'CLI' -WindowStyle Hidden" - + PowerShell -NoP -C "Start-Process '$(SolutionDir)\TrustedUninstaller.GUI\gui-builder\ame-builder.exe' -ArgumentList 'CLI','""""x64\$(Configuration)""""' -NoNewWindow -Wait" + + + + .allowedextension + - + for /f "usebackq delims=" %%A in (`DIR /B /S /A:d "$(SolutionDir)" ^| FINDSTR /R /c:".*\\bin\\.*\\de$" /c:".*\\bin\\.*\\en$" /c:".*\\bin\\.*\\es$" /c:".*\\bin\\.*\\fr$" /c:".*\\bin\\.*\\it$" /c:".*\\bin\\.*\\ja$" /c:".*\\bin\\.*\\ko$" /c:".*\\bin\\.*\\ru$" /c:".*\\bin\\.*\\zh-Hans$" /c:".*\\bin\\.*\\zh-Hant$" /c:".*\\bin\\.*\\pl$" /c:".*\\bin\\.*\\zh-CN$"`) do (RMDIR /Q /S "%%A" & cmd /c "exit /b 0") - - @@ -113,16 +118,6 @@ False False - - - - - - - - - - @@ -150,18 +145,12 @@ - - - - - - @@ -171,7 +160,7 @@ - + \ No newline at end of file diff --git a/TrustedUninstaller.Shared/Actions/AppxAction.cs b/TrustedUninstaller.Shared/Actions/AppxAction.cs index 7951ca2..725ff9a 100644 --- a/TrustedUninstaller.Shared/Actions/AppxAction.cs +++ b/TrustedUninstaller.Shared/Actions/AppxAction.cs @@ -16,6 +16,8 @@ namespace TrustedUninstaller.Shared.Actions // Integrate ame-assassin later internal class AppxAction : TaskAction, ITaskAction { + public void RunTaskOnMainThread() { throw new NotImplementedException(); } + public enum AppxOperation { Remove = 0, @@ -39,6 +41,9 @@ namespace TrustedUninstaller.Shared.Actions [YamlMember(typeof(bool), Alias = "verboseOutput")] public bool Verbose { get; set; } = false; + [YamlMember(typeof(bool), Alias = "unregister")] + public bool Unregister { get; set; } = false; + [YamlMember(typeof(string), Alias = "weight")] public int ProgressWeight { get; set; } = 30; public int GetProgressWeight() => ProgressWeight; @@ -69,14 +74,18 @@ namespace TrustedUninstaller.Shared.Actions InProgress = true; Console.WriteLine($"Removing APPX {Type.ToString().ToLower()} '{Name}'..."); + + WinUtil.CheckKph(); string verboseArg = Verbose ? " -Verbose" : ""; + string unregisterArg = Unregister ? " -Verbose" : ""; + string kernelDriverArg = AmeliorationUtil.UseKernelDriver ? " -UseKernelDriver" : ""; var psi = new ProcessStartInfo() { UseShellExecute = false, CreateNoWindow = true, - Arguments = $@"-{Type.ToString()} ""{Name}""" + verboseArg, + Arguments = $@"-{Type.ToString()} ""{Name}""" + verboseArg + unregisterArg + kernelDriverArg, FileName = Directory.GetCurrentDirectory() + "\\ame-assassin\\ame-assassin.exe", RedirectStandardOutput = true, RedirectStandardError = true @@ -98,7 +107,7 @@ namespace TrustedUninstaller.Shared.Actions bool exited = proc.WaitForExit(30000); // WaitForExit alone seems to not be entirely reliable - while (!exited && ExeRunning(proc)) + while (!exited && ExeRunning("ame-assassin", proc.Id)) { exited = proc.WaitForExit(30000); } @@ -121,11 +130,11 @@ namespace TrustedUninstaller.Shared.Actions if (!write.Equals("Complete!")) Console.WriteLine(write); } - private static bool ExeRunning(Process process) + private static bool ExeRunning(string name, int id) { try { - return Process.GetProcessesByName(process.ProcessName).Any(x => x.Id == process.Id); + return Process.GetProcessesByName(name).Any(x => x.Id == id); } catch (Exception) { diff --git a/TrustedUninstaller.Shared/Actions/CmdAction.cs b/TrustedUninstaller.Shared/Actions/CmdAction.cs index 145d6b7..739190a 100644 --- a/TrustedUninstaller.Shared/Actions/CmdAction.cs +++ b/TrustedUninstaller.Shared/Actions/CmdAction.cs @@ -1,7 +1,9 @@ using System; using System.Diagnostics; using System.IO; +using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; using TrustedUninstaller.Shared.Exceptions; using TrustedUninstaller.Shared.Tasks; @@ -11,6 +13,25 @@ namespace TrustedUninstaller.Shared.Actions { public class CmdAction : TaskAction, ITaskAction { + public void RunTaskOnMainThread() + { + if (InProgress) throw new TaskInProgressException("Another Cmd action was called while one was in progress."); + InProgress = true; + + Console.WriteLine($"Running cmd command '{Command}'..."); + + ExitCode = null; + + if (RunAs == Privilege.TrustedInstaller) + RunAsProcess(); + else + RunAsPrivilegedProcess(); + + InProgress = false; + } + [YamlMember(typeof(Privilege), Alias = "runas")] + public Privilege RunAs { get; set; } = Privilege.TrustedInstaller; + [YamlMember(typeof(string), Alias = "command")] public string Command { get; set; } @@ -48,21 +69,20 @@ namespace TrustedUninstaller.Shared.Actions return ExitCode == null ? UninstallTaskStatus.ToDo: UninstallTaskStatus.Completed; } - public async Task RunTask() + + public Task RunTask() { - if (InProgress) throw new TaskInProgressException("Another Cmd action was called while one was in progress."); - InProgress = true; - - Console.WriteLine($"Running cmd command '{Command}'..."); - - ExitCode = null; + return null; + } + private void RunAsProcess() + { var process = new Process(); var startInfo = new ProcessStartInfo { WindowStyle = ProcessWindowStyle.Normal, FileName = "cmd.exe", - Arguments = "/C " + $"\"{Environment.ExpandEnvironmentVariables(this.Command)}\"", + Arguments = "/C " + $"\"{this.Command}\"", UseShellExecute = false, RedirectStandardError = true, RedirectStandardOutput = true, @@ -83,7 +103,7 @@ namespace TrustedUninstaller.Shared.Actions if (!Wait) { process.Dispose(); - return true; + return; } var error = new StringBuilder(); @@ -97,6 +117,114 @@ namespace TrustedUninstaller.Shared.Actions }; process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + if (Timeout != null) + { + var exited = process.WaitForExit(Timeout.Value); + if (!exited) + { + process.Kill(); + throw new TimeoutException($"Command '{Command}' timeout exceeded."); + } + } + else + { + bool exited = process.WaitForExit(30000); + + // WaitForExit alone seems to not be entirely reliable + while (!exited && CmdRunning(process.Id)) + { + exited = process.WaitForExit(30000); + } + } + + int exitCode = 0; + try + { + exitCode = process.ExitCode; + } + catch (Exception ex) + { + ErrorLogger.WriteToErrorLog("Error fetching process exit code. (1)", null, "CmdAction Error", Command); + } + + if (exitCode != 0) + { + StandardError = error.ToString(); + Console.WriteLine($"cmd instance exited with error code: {exitCode}"); + if (!String.IsNullOrEmpty(StandardError)) Console.WriteLine($"Error message: {StandardError}"); + + ErrorLogger.WriteToErrorLog("Cmd exited with a non-zero exit code: " + exitCode, null, "CmdAction Error", Command); + + this.ExitCode = exitCode; + } + else + { + ExitCode = 0; + } + + process.CancelOutputRead(); + process.CancelErrorRead(); + process.Dispose(); + } + + private static bool CmdRunning(int id) + { + try + { + return Process.GetProcessesByName("cmd").Any(x => x.Id == id); + } + catch (Exception) + { + return false; + } + } + + private void RunAsPrivilegedProcess() + { + + var process = new AugmentedProcess.Process(); + var startInfo = new AugmentedProcess.ProcessStartInfo + { + WindowStyle = ProcessWindowStyle.Normal, + FileName = "cmd.exe", + Arguments = "/C " + $"\"{Environment.ExpandEnvironmentVariables(this.Command)}\"", + UseShellExecute = false, + RedirectStandardError = true, + RedirectStandardOutput = true, + CreateNoWindow = true + }; + if (ExeDir) startInfo.WorkingDirectory = AmeliorationUtil.Playbook.Path + "\\Executables"; + if (!Wait) + { + startInfo.RedirectStandardError = false; + startInfo.RedirectStandardOutput = false; + startInfo.WindowStyle = ProcessWindowStyle.Hidden; + startInfo.UseShellExecute = true; + } + + process.StartInfo = startInfo; + ProcessPrivilege.StartPrivilegedTask(process, RunAs); + + if (!Wait) + { + process.Dispose(); + return; + } + + var error = new StringBuilder(); + process.OutputDataReceived += PrivilegedProcOutputHandler; + process.ErrorDataReceived += delegate(object sender, AugmentedProcess.DataReceivedEventArgs args) + { + if (!String.IsNullOrEmpty(args.Data)) + error.AppendLine(args.Data); + else + error.AppendLine(); + }; + + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); if (Timeout != null) { @@ -127,14 +255,29 @@ namespace TrustedUninstaller.Shared.Actions } process.CancelOutputRead(); + process.CancelErrorRead(); process.Dispose(); - - InProgress = false; - return true; } + + private void PrivilegedProcOutputHandler(object sendingProcess, AugmentedProcess.DataReceivedEventArgs outLine) + { + var outputString = outLine.Data; - private static void ProcOutputHandler(object sendingProcess, - DataReceivedEventArgs outLine) + // Collect the sort command output. + if (!String.IsNullOrEmpty(outLine.Data)) + { + if (outputString.Contains("\\AME")) + { + outputString = outputString.Substring(outputString.IndexOf('>') + 1); + } + Console.WriteLine(outputString); + } + else + { + Console.WriteLine(); + } + } + private void ProcOutputHandler(object sendingProcess, DataReceivedEventArgs outLine) { var outputString = outLine.Data; diff --git a/TrustedUninstaller.Shared/Actions/FileAction.cs b/TrustedUninstaller.Shared/Actions/FileAction.cs index a5dd9be..706f0bd 100644 --- a/TrustedUninstaller.Shared/Actions/FileAction.cs +++ b/TrustedUninstaller.Shared/Actions/FileAction.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Configuration.Install; using System.Diagnostics; using System.IO; using System.Linq; @@ -9,6 +10,7 @@ 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; @@ -19,6 +21,7 @@ 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; } @@ -81,13 +84,14 @@ namespace TrustedUninstaller.Shared.Actions { if (!TrustedInstaller) { - try {await Task.Run(() => File.Delete(file));} catch {} + try { File.Delete(file);} catch (Exception e) { } if (File.Exists(file)) { try { - EzUnlockFileW(file); + var result = EzUnlockFileW(file); + Testing.WriteLine($"ExUnlock on ({file}) result: " + result); } catch (Exception e) { @@ -95,20 +99,21 @@ namespace TrustedUninstaller.Shared.Actions $"FileAction Error", file); } - try {await Task.Run(() => File.Delete(file));} catch {} - + 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}\"" }; - await delAction.RunTask(); + delAction.RunTaskOnMainThread(); } } else if (File.Exists("NSudoLC.exe")) { try { - EzUnlockFileW(file); + var result = EzUnlockFileW(file); + Testing.WriteLine($"ExUnlock on ({file}) result: " + result); } catch (Exception e) { @@ -123,7 +128,7 @@ namespace TrustedUninstaller.Shared.Actions CreateWindow = false }; - await tiDelAction.RunTask(); + tiDelAction.RunTaskOnMainThread(); if (tiDelAction.Output != null) { if (log) ErrorLogger.WriteToErrorLog(tiDelAction.Output, Environment.StackTrace, @@ -149,7 +154,7 @@ namespace TrustedUninstaller.Shared.Actions { Command = $"rmdir /Q /S \"{dir}\"" }; - await deleteDirCmd.RunTask(); + deleteDirCmd.RunTaskOnMainThread(); if (deleteDirCmd.StandardError != null) { @@ -171,7 +176,7 @@ namespace TrustedUninstaller.Shared.Actions CreateWindow = false }; - await tiDelAction.RunTask(); + tiDelAction.RunTaskOnMainThread(); if (tiDelAction.Output != null) { @@ -216,15 +221,28 @@ namespace TrustedUninstaller.Shared.Actions 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"; - await cmdAction.RunTask(); + 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"; - await cmdAction.RunTask(); + if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread(); } catch (Exception servException) { @@ -270,16 +288,19 @@ namespace TrustedUninstaller.Shared.Actions { try { - using var search = new ManagementObjectSearcher($"select * from Win32_Service where ProcessId = '{svchost.Id}'"); - - foreach (ManagementObject queryObj in search.Get()) + foreach (var serviceName in Win32.ServiceEx.GetServicesFromProcessId(svchost.Id)) { - var serviceName = (string)queryObj["Name"]; // Access service name - - var serv = ServiceController.GetServices().FirstOrDefault(x => x.ServiceName.Equals(serviceName)); - - if (serv == null) svcCount++; - else svcCount += serv.DependentServices.Length + 1; + 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) { @@ -328,7 +349,15 @@ namespace TrustedUninstaller.Shared.Actions continue; } - await taskKillAction.RunTask(); + 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. @@ -352,6 +381,11 @@ namespace TrustedUninstaller.Shared.Actions 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); @@ -419,7 +453,7 @@ namespace TrustedUninstaller.Shared.Actions }; try { - await permAction.RunTask(); + permAction.RunTaskOnMainThread(); } catch (Exception e) { @@ -466,7 +500,7 @@ namespace TrustedUninstaller.Shared.Actions } } } - else if (isFile) + if (isFile) { try { @@ -486,7 +520,7 @@ namespace TrustedUninstaller.Shared.Actions }; try { - await permAction.RunTask(); + permAction.RunTaskOnMainThread(); } catch (Exception e) { @@ -504,15 +538,30 @@ namespace TrustedUninstaller.Shared.Actions 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"; - await cmdAction.RunTask(); + 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"; - await cmdAction.RunTask(); + if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread(); } catch (Exception servException) { @@ -558,16 +607,19 @@ namespace TrustedUninstaller.Shared.Actions { try { - using var search = new ManagementObjectSearcher($"select * from Win32_Service where ProcessId = '{svchost.Id}'"); - - foreach (ManagementObject queryObj in search.Get()) + foreach (var serviceName in Win32.ServiceEx.GetServicesFromProcessId(svchost.Id)) { - var serviceName = (string)queryObj["Name"]; // Access service name - - var serv = ServiceController.GetServices().FirstOrDefault(x => x.ServiceName.Equals(serviceName)); - - if (serv == null) svcCount++; - else svcCount += serv.DependentServices.Length + 1; + 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) { @@ -645,6 +697,11 @@ namespace TrustedUninstaller.Shared.Actions 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); } } diff --git a/TrustedUninstaller.Shared/Actions/LanguageAction.cs b/TrustedUninstaller.Shared/Actions/LanguageAction.cs index 034c77a..2d82594 100644 --- a/TrustedUninstaller.Shared/Actions/LanguageAction.cs +++ b/TrustedUninstaller.Shared/Actions/LanguageAction.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using System.Runtime.InteropServices; using System.Threading.Tasks; using System.Windows.Forms; @@ -8,6 +9,7 @@ namespace TrustedUninstaller.Shared.Actions { class LanguageAction : TaskAction, ITaskAction { + public void RunTaskOnMainThread() { throw new NotImplementedException(); } public int ProgressWeight { get; set; } = 1; public int GetProgressWeight() => ProgressWeight; diff --git a/TrustedUninstaller.Shared/Actions/LineInFileAction.cs b/TrustedUninstaller.Shared/Actions/LineInFileAction.cs index fd4c053..5eee5ba 100644 --- a/TrustedUninstaller.Shared/Actions/LineInFileAction.cs +++ b/TrustedUninstaller.Shared/Actions/LineInFileAction.cs @@ -16,6 +16,7 @@ namespace TrustedUninstaller.Shared.Actions } internal class LineInFileAction : TaskAction, ITaskAction { + public void RunTaskOnMainThread() { throw new NotImplementedException(); } [YamlMember(Alias = "path")] public string RawPath { get; set; } diff --git a/TrustedUninstaller.Shared/Actions/PowershellAction.cs b/TrustedUninstaller.Shared/Actions/PowershellAction.cs index 4f91867..3c47f0f 100644 --- a/TrustedUninstaller.Shared/Actions/PowershellAction.cs +++ b/TrustedUninstaller.Shared/Actions/PowershellAction.cs @@ -14,6 +14,26 @@ namespace TrustedUninstaller.Shared.Actions { public class PowerShellAction : TaskAction, ITaskAction { + public void RunTaskOnMainThread() + { + if (InProgress) throw new TaskInProgressException("Another Powershell action was called while one was in progress."); + InProgress = true; + + Console.WriteLine($"Running PowerShell command '{Command}'..."); + + WinUtil.CheckKph(); + + if (RunAs == Privilege.TrustedInstaller) + RunAsProcess(); + else + RunAsPrivilegedProcess(); + + InProgress = false; + return; + } + [YamlMember(typeof(Privilege), Alias = "runas")] + public Privilege RunAs { get; set; } = Privilege.TrustedInstaller; + [YamlMember(typeof(string), Alias = "command")] public string Command { get; set; } @@ -50,13 +70,13 @@ namespace TrustedUninstaller.Shared.Actions return ExitCode == null ? UninstallTaskStatus.ToDo: UninstallTaskStatus.Completed; } - public async Task RunTask() + public Task RunTask() { - if (InProgress) throw new TaskInProgressException("Another Powershell action was called while one was in progress."); - InProgress = true; - - Console.WriteLine($"Running PowerShell command '{Command}'..."); - + return null; + } + + private void RunAsProcess() + { var process = new Process(); var startInfo = new ProcessStartInfo { @@ -83,7 +103,7 @@ namespace TrustedUninstaller.Shared.Actions if (!Wait) { process.Dispose(); - return true; + return; } var error = new StringBuilder(); @@ -98,6 +118,114 @@ namespace TrustedUninstaller.Shared.Actions process.BeginOutputReadLine(); process.BeginErrorReadLine(); + if (Timeout != null) + { + var exited = process.WaitForExit(Timeout.Value); + if (!exited) + { + process.Kill(); + throw new TimeoutException($"Command '{Command}' timeout exceeded."); + } + } + else + { + bool exited = process.WaitForExit(30000); + + // WaitForExit alone seems to not be entirely reliable + while (!exited && PowerShellRunning(process.Id)) + { + exited = process.WaitForExit(30000); + } + } + + StandardError = error.ToString(); + + int exitCode = 0; + try + { + exitCode = process.ExitCode; + } + catch (Exception ex) + { + ErrorLogger.WriteToErrorLog("Error fetching process exit code. (1)", null, "PowerShellAction Error", Command); + } + + if (exitCode != 0) + { + Console.WriteLine($"PowerShell instance exited with error code: {exitCode}"); + if (!String.IsNullOrEmpty(StandardError)) Console.WriteLine($"Error message: {StandardError}"); + + ErrorLogger.WriteToErrorLog("PowerShell exited with a non-zero exit code: " + exitCode, null, "PowerShellAction Error", Command); + + this.ExitCode = exitCode; + } + else + { + if (!String.IsNullOrEmpty(StandardError)) Console.WriteLine($"Error output: {StandardError}"); + ExitCode = 0; + } + + process.CancelOutputRead(); + process.CancelErrorRead(); + process.Dispose(); + } + + private static bool PowerShellRunning(int id) + { + try + { + return Process.GetProcessesByName("powershell").Any(x => x.Id == id); + } + catch (Exception) + { + return false; + } + } + + private void RunAsPrivilegedProcess() + { + + var process = new AugmentedProcess.Process(); + var startInfo = new AugmentedProcess.ProcessStartInfo + { + WindowStyle = ProcessWindowStyle.Normal, + FileName = "PowerShell.exe", + Arguments = $@"-NoP -ExecutionPolicy Bypass -NonInteractive -C ""{Command}""", + UseShellExecute = false, + RedirectStandardError = true, + RedirectStandardOutput = true, + CreateNoWindow = true + }; + if (ExeDir) startInfo.WorkingDirectory = AmeliorationUtil.Playbook.Path + "\\Executables"; + if (!Wait) + { + startInfo.RedirectStandardError = false; + startInfo.RedirectStandardOutput = false; + startInfo.WindowStyle = ProcessWindowStyle.Hidden; + startInfo.UseShellExecute = true; + } + + process.StartInfo = startInfo; + ProcessPrivilege.StartPrivilegedTask(process, RunAs); + + if (!Wait) + { + process.Dispose(); + return; + } + + var error = new StringBuilder(); + process.OutputDataReceived += PrivilegedProcOutputHandler; + process.ErrorDataReceived += delegate(object sender, AugmentedProcess.DataReceivedEventArgs args) + { + if (!String.IsNullOrEmpty(args.Data)) + error.AppendLine(args.Data); + else + error.AppendLine(); + }; + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + if (Timeout != null) { var exited = process.WaitForExit(Timeout.Value); @@ -129,16 +257,50 @@ namespace TrustedUninstaller.Shared.Actions process.CancelOutputRead(); process.CancelErrorRead(); process.Dispose(); - - InProgress = false; - return true; + } + + private static bool ExeRunning(string exe, int id) + { + try + { + return Process.GetProcessesByName(Path.GetFileNameWithoutExtension(exe)).Any(x => x.Id == id); + } + catch (Exception) + { + return false; + } } - private static void ProcOutputHandler(object sendingProcess, DataReceivedEventArgs outLine) + private void PrivilegedProcOutputHandler(object sendingProcess, AugmentedProcess.DataReceivedEventArgs outLine) { + var outputString = outLine.Data; + + // Collect the sort command output. if (!String.IsNullOrEmpty(outLine.Data)) { - Console.WriteLine(outLine.Data); + if (outputString.Contains("\\AME")) + { + outputString = outputString.Substring(outputString.IndexOf('>') + 1); + } + Console.WriteLine(outputString); + } + else + { + Console.WriteLine(); + } + } + private void ProcOutputHandler(object sendingProcess, DataReceivedEventArgs outLine) + { + var outputString = outLine.Data; + + // Collect the sort command output. + if (!String.IsNullOrEmpty(outLine.Data)) + { + if (outputString.Contains("\\AME")) + { + outputString = outputString.Substring(outputString.IndexOf('>') + 1); + } + Console.WriteLine(outputString); } else { diff --git a/TrustedUninstaller.Shared/Actions/RegistryKeyAction.cs b/TrustedUninstaller.Shared/Actions/RegistryKeyAction.cs index bfc943c..f4bb6a8 100644 --- a/TrustedUninstaller.Shared/Actions/RegistryKeyAction.cs +++ b/TrustedUninstaller.Shared/Actions/RegistryKeyAction.cs @@ -23,6 +23,7 @@ namespace TrustedUninstaller.Shared.Actions } public class RegistryKeyAction : TaskAction, ITaskAction { + public void RunTaskOnMainThread() { throw new NotImplementedException(); } [YamlMember(typeof(string), Alias = "path")] public string KeyName { get; set; } diff --git a/TrustedUninstaller.Shared/Actions/RegistryValueAction.cs b/TrustedUninstaller.Shared/Actions/RegistryValueAction.cs index 3aa3ed5..9adbd15 100644 --- a/TrustedUninstaller.Shared/Actions/RegistryValueAction.cs +++ b/TrustedUninstaller.Shared/Actions/RegistryValueAction.cs @@ -38,6 +38,7 @@ namespace TrustedUninstaller.Shared.Actions public class RegistryValueAction : TaskAction, ITaskAction { + public void RunTaskOnMainThread() { throw new NotImplementedException(); } [YamlMember(typeof(string), Alias = "path")] public string KeyName { get; set; } diff --git a/TrustedUninstaller.Shared/Actions/RunAction.cs b/TrustedUninstaller.Shared/Actions/RunAction.cs index 8b9e630..6744e55 100644 --- a/TrustedUninstaller.Shared/Actions/RunAction.cs +++ b/TrustedUninstaller.Shared/Actions/RunAction.cs @@ -2,14 +2,59 @@ using System.Diagnostics; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; +using System.Windows.Documents; using TrustedUninstaller.Shared.Tasks; using YamlDotNet.Serialization; namespace TrustedUninstaller.Shared.Actions { + public enum Privilege + { + TrustedInstaller, + System, + CurrentUserElevated, + CurrentUser, + } + public class RunAction : TaskAction, ITaskAction { + public void RunTaskOnMainThread() + { + if (RawPath != null) RawPath = Environment.ExpandEnvironmentVariables(RawPath); + InProgress = true; + + if (Arguments == null) Console.WriteLine($"Running '{Exe}'..."); + else Console.WriteLine($"Running '{Exe}' with arguments '{Arguments}'..."); + + WinUtil.CheckKph(); + + var currentDir = Directory.GetCurrentDirectory(); + + if (ExeDir) RawPath = AmeliorationUtil.Playbook.Path + "\\Executables"; + if (BaseDir) RawPath = currentDir; + + string file = null; + if (RawPath != null && File.Exists(Path.Combine(Environment.ExpandEnvironmentVariables(RawPath), Exe))) + file = Path.Combine(Environment.ExpandEnvironmentVariables(RawPath), Exe); + else if (ExistsInPath(Exe) || File.Exists(Environment.ExpandEnvironmentVariables(Exe))) + file = Environment.ExpandEnvironmentVariables(Exe); + + if (file == null) + throw new FileNotFoundException($"Executable not found."); + + if (RunAs == Privilege.TrustedInstaller) + RunAsProcess(file); + else + RunAsPrivilegedProcess(file); + + InProgress = false; + return; + } + [YamlMember(typeof(Privilege), Alias = "runas")] + public Privilege RunAs { get; set; } = Privilege.TrustedInstaller; + [YamlMember(typeof(string), Alias = "path")] public string RawPath { get; set; } = null; @@ -80,28 +125,13 @@ namespace TrustedUninstaller.Shared.Actions return HasExited || !Wait ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo; } - public async Task RunTask() + public Task RunTask() { - if (RawPath != null) RawPath = Environment.ExpandEnvironmentVariables(RawPath); - InProgress = true; - - if (Arguments == null) Console.WriteLine($"Running '{Exe}'..."); - else Console.WriteLine($"Running '{Exe}' with arguments '{Arguments}'..."); - - var currentDir = Directory.GetCurrentDirectory(); - - if (ExeDir) RawPath = AmeliorationUtil.Playbook.Path + "\\Executables"; - if (BaseDir) RawPath = currentDir; - - string file = null; - if (RawPath != null && File.Exists(Path.Combine(RawPath, Exe))) - file = Path.Combine(RawPath, Exe); - else if (ExistsInPath(Exe) || File.Exists(Environment.ExpandEnvironmentVariables(Exe))) - file = Exe; - - if (file == null) - throw new FileNotFoundException($"Executable not found."); + return null; + } + private void RunAsProcess(string file) + { var startInfo = new ProcessStartInfo { CreateNoWindow = !this.CreateWindow, @@ -138,7 +168,7 @@ namespace TrustedUninstaller.Shared.Actions if (!Wait) { exeProcess.Dispose(); - return true; + return; } if (ShowOutput) @@ -165,15 +195,24 @@ namespace TrustedUninstaller.Shared.Actions bool exited = exeProcess.WaitForExit(30000); // WaitForExit alone seems to not be entirely reliable - while (!exited && ExeRunning(exeProcess)) + while (!exited && ExeRunning(exeProcess.ProcessName, exeProcess.Id)) { exited = exeProcess.WaitForExit(30000); } } - if (exeProcess.ExitCode != 0) + int exitCode = 0; + try + { + exitCode = exeProcess.ExitCode; + } + catch (Exception ex) { - ErrorLogger.WriteToErrorLog("Process exited with a non-zero exit code: " + exeProcess.ExitCode, null, "RunAction Error", Exe + " " + Arguments); + ErrorLogger.WriteToErrorLog("Error fetching process exit code. (1)", null, "RunAction Error", Exe + " " + Arguments); + } + if (exitCode != 0) + { + ErrorLogger.WriteToErrorLog("Process exited with a non-zero exit code: " + exitCode, null, "RunAction Error", Exe + " " + Arguments); } HasExited = true; @@ -182,15 +221,116 @@ namespace TrustedUninstaller.Shared.Actions exeProcess.CancelOutputRead(); if (ShowError) exeProcess.CancelErrorRead(); + } + private void RunAsPrivilegedProcess(string file) + { + var startInfo = new AugmentedProcess.ProcessStartInfo + { + CreateNoWindow = !this.CreateWindow, + UseShellExecute = false, + WindowStyle = ProcessWindowStyle.Normal, + RedirectStandardError = true, + RedirectStandardOutput = true, + FileName = file, + }; + if (Arguments != null) startInfo.Arguments = Arguments; - InProgress = false; - return true; + if (ExeDir) startInfo.WorkingDirectory = AmeliorationUtil.Playbook.Path + "\\Executables"; + if (!Wait) + { + startInfo.RedirectStandardError = false; + startInfo.RedirectStandardOutput = false; + startInfo.WindowStyle = ProcessWindowStyle.Hidden; + startInfo.UseShellExecute = true; + } + + if (!ShowOutput) + startInfo.RedirectStandardOutput = false; + if (!ShowError) + startInfo.RedirectStandardError = false; + + var exeProcess = new AugmentedProcess.Process + { + StartInfo = startInfo, + EnableRaisingEvents = true + }; + + ProcessPrivilege.StartPrivilegedTask(exeProcess, RunAs); + + if (!Wait) + { + exeProcess.Dispose(); + return; + } + + if (ShowOutput) + exeProcess.OutputDataReceived += PrivilegedProcOutputHandler; + if (ShowError) + exeProcess.ErrorDataReceived += PrivilegedProcOutputHandler; + + if (ShowOutput) + exeProcess.BeginOutputReadLine(); + if (ShowError) + exeProcess.BeginErrorReadLine(); + + if (Timeout.HasValue) + { + var exited = exeProcess.WaitForExit(Timeout.Value); + if (!exited) + { + exeProcess.Kill(); + throw new TimeoutException($"Executable run timeout exceeded."); + } + } + else + { + bool exited = exeProcess.WaitForExit(30000); + + // WaitForExit alone seems to not be entirely reliable + while (!exited && ExeRunning(exeProcess.ProcessName, exeProcess.Id)) + { + exited = exeProcess.WaitForExit(30000); + } + } + + try + { + if (exeProcess.ExitCode != 0) + { + ErrorLogger.WriteToErrorLog("Process exited with a non-zero exit code: " + exeProcess.ExitCode, null, "RunAction Error", Exe + " " + Arguments); + } + } + catch (Exception ex) + { + ErrorLogger.WriteToErrorLog("Error fetching process exit code. (1)", null, "RunAction Error", Exe + " " + Arguments); + + Thread.Sleep(500); + try + { + if (exeProcess.ExitCode != 0) + { + ErrorLogger.WriteToErrorLog("Process exited with a non-zero exit code: " + exeProcess.ExitCode, null, "RunAction Error", Exe + " " + Arguments); + } + } + catch (Exception e) + { + ErrorLogger.WriteToErrorLog("Error fetching process exit code. (2)", null, "RunAction Error", Exe + " " + Arguments); + } + } + + HasExited = true; + + if (ShowOutput) + exeProcess.CancelOutputRead(); + if (ShowError) + exeProcess.CancelErrorRead(); } - private static bool ExeRunning(Process process) + + private static bool ExeRunning(string name, int id) { try { - return Process.GetProcessesByName(process.ProcessName).Any(x => x.Id == process.Id); + return Process.GetProcessesByName(name).Any(x => x.Id == id); } catch (Exception) { @@ -198,6 +338,25 @@ namespace TrustedUninstaller.Shared.Actions } } + private void PrivilegedProcOutputHandler(object sendingProcess, AugmentedProcess.DataReceivedEventArgs outLine) + { + // Collect the sort command output. + if (!String.IsNullOrEmpty(outLine.Data)) + { + var outputString = outLine.Data; + + if (outputString.Contains("\\AME")) + { + outputString = outputString.Substring(outputString.IndexOf('>') + 1); + } + Console.WriteLine(outputString); + Output += outputString + Environment.NewLine; + } + else + { + Console.WriteLine(); + } + } private void ProcOutputHandler(object sendingProcess, DataReceivedEventArgs outLine) { // Collect the sort command output. diff --git a/TrustedUninstaller.Shared/Actions/ScheduledTaskAction.cs b/TrustedUninstaller.Shared/Actions/ScheduledTaskAction.cs index 0847619..db74817 100644 --- a/TrustedUninstaller.Shared/Actions/ScheduledTaskAction.cs +++ b/TrustedUninstaller.Shared/Actions/ScheduledTaskAction.cs @@ -21,6 +21,7 @@ namespace TrustedUninstaller.Shared.Actions internal class ScheduledTaskAction : TaskAction, ITaskAction { + public void RunTaskOnMainThread() { throw new NotImplementedException(); } [YamlMember(typeof(ScheduledTaskOperation), Alias = "operation")] public ScheduledTaskOperation Operation { get; set; } = ScheduledTaskOperation.Delete; [YamlMember(Alias = "data")] diff --git a/TrustedUninstaller.Shared/Actions/ServiceAction.cs b/TrustedUninstaller.Shared/Actions/ServiceAction.cs index 2ae6c07..a9d5105 100644 --- a/TrustedUninstaller.Shared/Actions/ServiceAction.cs +++ b/TrustedUninstaller.Shared/Actions/ServiceAction.cs @@ -1,5 +1,7 @@ #nullable enable using System; +using System.Collections.Specialized; +using System.Configuration.Install; using System.Diagnostics; using System.IO; using System.Linq; @@ -7,6 +9,7 @@ using System.Management; using System.ServiceProcess; using System.Text.RegularExpressions; using System.Threading.Tasks; +using System.Windows; using Microsoft.Win32; using TrustedUninstaller.Shared.Exceptions; using TrustedUninstaller.Shared.Tasks; @@ -25,6 +28,7 @@ namespace TrustedUninstaller.Shared.Actions } internal class ServiceAction : TaskAction, ITaskAction { + public void RunTaskOnMainThread() { throw new NotImplementedException(); } [YamlMember(typeof(ServiceOperation), Alias = "operation")] public ServiceOperation Operation { get; set; } = ServiceOperation.Delete; @@ -94,7 +98,7 @@ namespace TrustedUninstaller.Shared.Actions return (int)value == Startup.Value ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo; } - ServiceController serviceController; + ServiceController? serviceController; if (Device) serviceController = GetDevice(); else serviceController = GetService(); @@ -129,12 +133,10 @@ namespace TrustedUninstaller.Shared.Actions || serviceController?.Status == ServiceControllerStatus.PausePending ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo, ServiceOperation.Delete => - serviceController == null ? + serviceController == null || Win32.ServiceEx.IsPendingDeleteOrDeleted(serviceController.ServiceName) ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo, _ => throw new ArgumentOutOfRangeException("Argument out of Range", new ArgumentOutOfRangeException()) }; - - } private readonly string[] RegexNoKill = { "DcomLaunch" }; @@ -191,44 +193,46 @@ namespace TrustedUninstaller.Shared.Actions foreach (ServiceController dependentService in service.DependentServices) { Console.WriteLine($"Killing dependent service {dependentService.ServiceName}..."); + + try + { + dependentService.Stop(); + } + catch (Exception e) + { + ErrorLogger.WriteToErrorLog("Service stop failed: " + e.Message, e.StackTrace, "ServiceAction Warning", ServiceName); + } + cmdAction.Command = Environment.Is64BitOperatingSystem ? - $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {dependentService.ServiceName} -caction stop" : + $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {dependentService.ServiceName} -caction stop" : $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {dependentService.ServiceName} -caction stop"; - await cmdAction.RunTask(); + if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread(); Console.WriteLine("Waiting for the service to stop..."); int delay = 100; - while (service.Status != ServiceControllerStatus.Stopped && delay <= 1000) + while (dependentService.Status != ServiceControllerStatus.Stopped && delay <= 1000) { - service.Refresh(); + dependentService.Refresh(); //Wait for the service to stop Task.Delay(delay).Wait(); delay += 100; } if (delay >= 1000) { - Console.WriteLine("\r\nService stop timeout exceeded. Trying second method..."); - - try - { - using var search = new ManagementObjectSearcher($"SELECT * FROM Win32_Service WHERE Name='{service.ServiceName}'"); - - foreach (ManagementObject queryObj in search.Get()) - { - var serviceId = (UInt32)queryObj["ProcessId"]; // Access service name - - var killServ = new TaskKillAction() - { - ProcessID = (int)serviceId - }; - await killServ.RunTask(); - } - } - catch (Exception e) + Console.WriteLine("\r\nService stop timeout exceeded."); + } + try + { + var killServ = new TaskKillAction() { - ErrorLogger.WriteToErrorLog($"Could not kill dependent service {dependentService.ServiceName}.", - e.StackTrace, "ServiceAction Error"); - } + ProcessID = Win32.ServiceEx.GetServiceProcessId(dependentService.ServiceName) + }; + await killServ.RunTask(); + } + catch (Exception e) + { + ErrorLogger.WriteToErrorLog($"Could not kill dependent service {dependentService.ServiceName}.", + e.StackTrace, "ServiceAction Error"); } } @@ -243,11 +247,19 @@ namespace TrustedUninstaller.Shared.Actions if (DeleteStop && service.Status != ServiceControllerStatus.StopPending && service.Status != ServiceControllerStatus.Stopped) { + try + { + service.Stop(); + } + catch (Exception e) + { + ErrorLogger.WriteToErrorLog("Service stop failed: " + e.Message, e.StackTrace, "ServiceAction Warning", ServiceName); + } + cmdAction.Command = Environment.Is64BitOperatingSystem ? - $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction stop" : + $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction stop" : $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction stop"; - await cmdAction.RunTask(); - + if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread(); } Console.WriteLine("Waiting for the service to stop..."); @@ -259,30 +271,21 @@ namespace TrustedUninstaller.Shared.Actions await Task.Delay(delay); delay += 100; } - if (delay >= 1500) + if (delay >= 1000) { - Console.WriteLine("\r\nService stop timeout exceeded. Trying second method..."); - - try - { - using var search = new ManagementObjectSearcher($"SELECT * FROM Win32_Service WHERE Name='{service.ServiceName}'"); - - foreach (ManagementObject queryObj in search.Get()) - { - var serviceId = (UInt32)queryObj["ProcessId"]; // Access service name - - var killServ = new TaskKillAction() - { - ProcessID = (int)serviceId - }; - await killServ.RunTask(); - } - } - catch (Exception e) + Console.WriteLine("\r\nService stop timeout exceeded."); + } + try + { + var killServ = new TaskKillAction() { - ErrorLogger.WriteToErrorLog($"Could not kill service {service.ServiceName}.", - e.StackTrace, "ServiceAction Error"); - } + ProcessID = Win32.ServiceEx.GetServiceProcessId(service.ServiceName) + }; + await killServ.RunTask(); + } + catch (Exception e) + { + ErrorLogger.WriteToErrorLog($"Could not kill service {service.ServiceName}.", e.StackTrace, "ServiceAction Error"); } if (RegistryDelete) @@ -296,18 +299,39 @@ namespace TrustedUninstaller.Shared.Actions } else { + try + { + ServiceInstaller ServiceInstallerObj = new ServiceInstaller(); + ServiceInstallerObj.Context = new InstallContext(); + ServiceInstallerObj.ServiceName = service.ServiceName; + ServiceInstallerObj.Uninstall(null); + } + catch (Exception e) + { + ErrorLogger.WriteToErrorLog("Service uninstall failed: " + e.Message, e.StackTrace, "ServiceAction Warning", ServiceName); + } cmdAction.Command = Environment.Is64BitOperatingSystem ? $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction delete" : $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction delete"; - await cmdAction.RunTask(); + if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread(); } + } else { + try + { + service.Stop(); + } + catch (Exception e) + { + ErrorLogger.WriteToErrorLog("Service stop failed: " + e.Message, e.StackTrace, "ServiceAction Warning", ServiceName); + } + cmdAction.Command = Environment.Is64BitOperatingSystem ? $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction {Operation.ToString().ToLower()}" : $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction {Operation.ToString().ToLower()}"; - await cmdAction.RunTask(); + if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread(); } service?.Dispose(); diff --git a/TrustedUninstaller.Shared/Actions/ShortcutAction.cs b/TrustedUninstaller.Shared/Actions/ShortcutAction.cs index 7a727b4..9c5912d 100644 --- a/TrustedUninstaller.Shared/Actions/ShortcutAction.cs +++ b/TrustedUninstaller.Shared/Actions/ShortcutAction.cs @@ -10,6 +10,7 @@ namespace TrustedUninstaller.Shared.Actions { class ShortcutAction : TaskAction, ITaskAction { + public void RunTaskOnMainThread() { throw new NotImplementedException(); } [YamlMember(typeof(string), Alias = "path")] public string RawPath { get; set; } diff --git a/TrustedUninstaller.Shared/Actions/SystemPackageAction.cs b/TrustedUninstaller.Shared/Actions/SystemPackageAction.cs index aeaa65c..4b5d865 100644 --- a/TrustedUninstaller.Shared/Actions/SystemPackageAction.cs +++ b/TrustedUninstaller.Shared/Actions/SystemPackageAction.cs @@ -14,6 +14,7 @@ namespace TrustedUninstaller.Shared.Actions // Integrate ame-assassin later internal class SystemPackageAction : TaskAction, ITaskAction { + public void RunTaskOnMainThread() { throw new NotImplementedException(); } public enum Architecture { amd64 = 0, @@ -77,11 +78,13 @@ namespace TrustedUninstaller.Shared.Actions } } + string kernelDriverArg = AmeliorationUtil.UseKernelDriver ? " -UseKernelDriver" : ""; + var psi = new ProcessStartInfo() { UseShellExecute = false, CreateNoWindow = true, - Arguments = $@"-SystemPackage ""{Name}"" -Arch {Arch.ToString()} -Language ""{Language}""" + excludeArgs + excludeDependsArgs, + Arguments = $@"-SystemPackage ""{Name}"" -Arch {Arch.ToString()} -Language ""{Language}""" + excludeArgs + excludeDependsArgs + kernelDriverArg, FileName = Directory.GetCurrentDirectory() + "\\ame-assassin\\ame-assassin.exe", RedirectStandardOutput = true, RedirectStandardError = true diff --git a/TrustedUninstaller.Shared/Actions/TaskKillAction.cs b/TrustedUninstaller.Shared/Actions/TaskKillAction.cs index 99bd1ea..33e316c 100644 --- a/TrustedUninstaller.Shared/Actions/TaskKillAction.cs +++ b/TrustedUninstaller.Shared/Actions/TaskKillAction.cs @@ -13,9 +13,22 @@ namespace TrustedUninstaller.Shared.Actions { class TaskKillAction : TaskAction, ITaskAction { + public void RunTaskOnMainThread() { throw new NotImplementedException(); } [DllImport("kernel32.dll", SetLastError=true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode); + [DllImport("kernel32.dll", SetLastError = true)] + private static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, + bool bInheritHandle, int dwProcessId); + + public enum ProcessAccessFlags : uint + { + QueryLimitedInformation = 0x1000 + } + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + static extern bool CloseHandle(IntPtr hObject); [YamlMember(typeof(string), Alias = "name")] public string? ProcessName { get; set; } @@ -73,18 +86,38 @@ namespace TrustedUninstaller.Shared.Actions return processToTerminate.Any() ? UninstallTaskStatus.ToDo : UninstallTaskStatus.Completed; } - private IEnumerable GetProcess() + private List GetProcess() { - if (ProcessName == null) return new List(); + if (ProcessID.HasValue) + { + var list = new List(); + try + { + var process = Process.GetProcessById(ProcessID.Value); + if (ProcessName == null || process.ProcessName.Equals(ProcessName, StringComparison.OrdinalIgnoreCase)) + list.Add(process); + else + return list; + } + catch (Exception e) + { + return list; + } + } - if (ProcessName.EndsWith("*") && ProcessName.StartsWith("*")) return Process.GetProcesses() - .Where(process => process.ProcessName.IndexOf(ProcessName.Trim('*'), StringComparison.CurrentCultureIgnoreCase) >= 0); + if (ProcessName == null) + { + return new List(); + } + + if (ProcessName.EndsWith("*") && ProcessName.StartsWith("*")) return Process.GetProcesses().ToList() + .Where(process => process.ProcessName.IndexOf(ProcessName.Trim('*'), StringComparison.CurrentCultureIgnoreCase) >= 0).ToList(); if (ProcessName.EndsWith("*")) return Process.GetProcesses() - .Where(process => process.ProcessName.StartsWith(ProcessName.TrimEnd('*'), StringComparison.CurrentCultureIgnoreCase)); + .Where(process => process.ProcessName.StartsWith(ProcessName.TrimEnd('*'), StringComparison.CurrentCultureIgnoreCase)).ToList(); if (ProcessName.StartsWith("*")) return Process.GetProcesses() - .Where(process => process.ProcessName.EndsWith(ProcessName.TrimStart('*'), StringComparison.CurrentCultureIgnoreCase)); + .Where(process => process.ProcessName.EndsWith(ProcessName.TrimStart('*'), StringComparison.CurrentCultureIgnoreCase)).ToList(); - return Process.GetProcessesByName(ProcessName); + return Process.GetProcessesByName(ProcessName).ToList(); } [DllImport("kernel32.dll", SetLastError=true)] static extern bool IsProcessCritical(IntPtr hProcess, ref bool Critical); @@ -116,85 +149,76 @@ namespace TrustedUninstaller.Shared.Actions if (ProcessName != null) { //If the service is svchost, we stop the service instead of killing it. - if (ProcessName.Contains("svchost")) + if (ProcessName.Equals("svchost", StringComparison.OrdinalIgnoreCase)) { - // bool serviceFound = false; try { - using var search = new ManagementObjectSearcher($"select * from Win32_Service where ProcessId = '{ProcessID}'"); - - foreach (ManagementObject queryObj in search.Get()) + if (ProcessID.HasValue) + { + foreach (var serviceName in Win32.ServiceEx.GetServicesFromProcessId(ProcessID.Value)) + { + try + { + var stopServ = new ServiceAction() + { + ServiceName = serviceName, + Operation = ServiceOperation.Stop + }; + await stopServ.RunTask(); + } + catch (Exception e) + { + Console.WriteLine($"Could not kill service " + serviceName + ": " + e.Message); + ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, "Could not kill service " + serviceName + ": " + e.Message); + } + } + } + else { - var serviceName = (string)queryObj["Name"]; // Access service name - - var stopServ = new ServiceAction() + foreach (var process in GetProcess()) { - ServiceName = serviceName, - Operation = ServiceOperation.Stop + foreach (var serviceName in Win32.ServiceEx.GetServicesFromProcessId(process.Id)) + { + try + { + var stopServ = new ServiceAction() + { + ServiceName = serviceName, + Operation = ServiceOperation.Stop - }; - await stopServ.RunTask(); + }; + await stopServ.RunTask(); + } + catch (Exception e) + { + Console.WriteLine($"Could not kill service " + serviceName + ": " + e.Message); + ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, "Could not kill service " + serviceName + ": " + e.Message); + } + } + } } } 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}."); + Console.WriteLine($"A service with PID: {ProcessID.Value} could not be found."); + ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, $"Could not find service with PID {ProcessID.Value}."); } - -/* foreach (var serv in servicesToDelete) + int i; + for (i = 0; i <= 6 && GetProcess().Any(); i++) { - //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) + await Task.Delay(100 * i); + } + if (i < 6) { - 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; + InProgress = false; + return true; + } } if (PathContains != null && !ProcessID.HasValue) { - var processes = GetProcess().ToList(); + var processes = GetProcess(); if (processes.Count > 0) Console.WriteLine("Processes:"); foreach (var process in processes.Where(x => x.MainModule.FileName.Contains(PathContains))) @@ -203,58 +227,74 @@ namespace TrustedUninstaller.Shared.Actions if (!RegexNotCritical.Any(x => Regex.Match(process.ProcessName, x, RegexOptions.IgnoreCase).Success)) { bool isCritical = false; - IsProcessCritical(process.Handle, ref isCritical); + IntPtr hprocess = OpenProcess(ProcessAccessFlags.QueryLimitedInformation, false, process.Id); + IsProcessCritical(hprocess, ref isCritical); + CloseHandle(hprocess); 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(); + + if (AmeliorationUtil.UseKernelDriver) + cmdAction.RunTaskOnMainThread(); + else + TerminateProcess(process.Handle, 1); int i = 0; - while (i <= 15 && GetProcess().Any(x => x.Id == process.Id && x.ProcessName == process.ProcessName)) + while (i <= 5 && GetProcess().Any(x => x.Id == process.Id && x.ProcessName == process.ProcessName)) { await Task.Delay(300); + if (AmeliorationUtil.UseKernelDriver) + cmdAction.RunTaskOnMainThread(); + else + TerminateProcess(process.Handle, 1); i++; } - if (i >= 15) ErrorLogger.WriteToErrorLog($"Task kill timeout exceeded.", Environment.StackTrace, "TaskKillAction Error"); + try { + if (!TerminateProcess(process.Handle, 1)) + ErrorLogger.WriteToErrorLog("TerminateProcess failed with error code: " + Marshal.GetLastWin32Error(), Environment.StackTrace, "TaskKill Error", ProcessName); + } + catch (Exception e) + { + ErrorLogger.WriteToErrorLog("Could not open process handle.", e.StackTrace, "TaskKillAction Error", process.ProcessName); + } + + if (i >= 5) ErrorLogger.WriteToErrorLog($"Task kill timeout exceeded.", Environment.StackTrace, "TaskKillAction Error"); } InProgress = false; return true; } } - if (ProcessID.HasValue) { + var process = Process.GetProcessById(ProcessID.Value); if (ProcessName != null && ProcessName.Equals("explorer", StringComparison.OrdinalIgnoreCase)) { - try - { - var process = Process.GetProcessById(ProcessID.Value); - TerminateProcess(process.Handle, 1); - } catch (Exception) + try { + if (!TerminateProcess(process.Handle, 1)) + ErrorLogger.WriteToErrorLog("TerminateProcess failed with error code: " + Marshal.GetLastWin32Error(), Environment.StackTrace, "TaskKill Error", ProcessName); + } + catch (Exception e) { - 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(); + ErrorLogger.WriteToErrorLog("Could not open process handle.", e.StackTrace, "TaskKillAction Error", process.ProcessName); } } else { - var process = Process.GetProcessById(ProcessID.Value); - if (!RegexNotCritical.Any(x => Regex.Match(process.ProcessName, x, RegexOptions.IgnoreCase).Success)) { bool isCritical = false; try { - IsProcessCritical(process.Handle, ref isCritical); + IntPtr hprocess = OpenProcess(ProcessAccessFlags.QueryLimitedInformation, false, process.Id); + IsProcessCritical(hprocess, ref isCritical); + CloseHandle(hprocess); } catch (InvalidOperationException e) { @@ -267,59 +307,81 @@ namespace TrustedUninstaller.Shared.Actions return false; } } + try { + if (!TerminateProcess(process.Handle, 1)) + ErrorLogger.WriteToErrorLog("TerminateProcess failed with error code: " + Marshal.GetLastWin32Error(), Environment.StackTrace, "TaskKill Error", ProcessName); + } + catch (Exception e) + { + ErrorLogger.WriteToErrorLog("Could not open process handle.", e.StackTrace, "TaskKillAction Error", process.ProcessName); + } + 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(); + $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {ProcessID.Value} -caction terminate" : + $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {ProcessID.Value} -caction terminate"; + if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread(); } await Task.Delay(100); } else { - var processes = GetProcess().ToList(); + var processes = GetProcess(); 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)) { - if (!RegexNotCritical.Any(x => Regex.Match(process.ProcessName, x, RegexOptions.IgnoreCase).Success)) + bool isCritical = false; + try { - bool isCritical = false; - try - { - IsProcessCritical(process.Handle, ref isCritical); - } - catch (InvalidOperationException e) - { - ErrorLogger.WriteToErrorLog("Could not check if process is critical.", e.StackTrace, "TaskKillAction Error", process.ProcessName); - continue; - } - if (isCritical) - { - Console.WriteLine($"{process.ProcessName} is a critical process, skipping..."); - continue; - } + IntPtr hprocess = OpenProcess(ProcessAccessFlags.QueryLimitedInformation, false, process.Id); + IsProcessCritical(hprocess, ref isCritical); + CloseHandle(hprocess); } - - await cmdAction.RunTask(); + catch (InvalidOperationException e) + { + ErrorLogger.WriteToErrorLog("Could not check if process is critical.", e.StackTrace, "TaskKillAction Error", process.ProcessName); + continue; + } + if (isCritical) + { + Console.WriteLine($"{process.ProcessName} is a critical process, skipping..."); + continue; + } + } + try + { + if (!TerminateProcess(process.Handle, 1)) + ErrorLogger.WriteToErrorLog("TerminateProcess failed with error code: " + Marshal.GetLastWin32Error(), Environment.StackTrace, "TaskKill Error", ProcessName); + } + catch (Exception e) + { + ErrorLogger.WriteToErrorLog("Could not open process handle.", e.StackTrace, "TaskKillAction Error", process.ProcessName); } + + if (process.ProcessName == "explorer") 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"; + if (AmeliorationUtil.UseKernelDriver && process.ProcessName != "explorer") cmdAction.RunTaskOnMainThread(); int i = 0; - while (i <= 15 && GetProcess().Any(x => x.Id == process.Id && x.ProcessName == process.ProcessName)) + while (i <= 5 && GetProcess().Any(x => x.Id == process.Id && x.ProcessName == process.ProcessName)) { + if (AmeliorationUtil.UseKernelDriver) + cmdAction.RunTaskOnMainThread(); + else + TerminateProcess(process.Handle, 1); await Task.Delay(300); i++; } - if (i >= 15) ErrorLogger.WriteToErrorLog($"Task kill timeout exceeded.", Environment.StackTrace, "TaskKillAction Error"); + if (i >= 5) ErrorLogger.WriteToErrorLog($"Task kill timeout exceeded.", Environment.StackTrace, "TaskKillAction Error"); } } diff --git a/TrustedUninstaller.Shared/Actions/UpdateAction.cs b/TrustedUninstaller.Shared/Actions/UpdateAction.cs index ab1edc5..582ed4b 100644 --- a/TrustedUninstaller.Shared/Actions/UpdateAction.cs +++ b/TrustedUninstaller.Shared/Actions/UpdateAction.cs @@ -10,6 +10,7 @@ namespace TrustedUninstaller.Shared.Actions { internal class UpdateAction : TaskAction, ITaskAction { + public void RunTaskOnMainThread() { throw new NotImplementedException(); } [YamlMember(typeof(string), Alias = "name")] public string PackageName { get; set; } diff --git a/TrustedUninstaller.Shared/Actions/UserAction.cs b/TrustedUninstaller.Shared/Actions/UserAction.cs index 2a51c86..9004954 100644 --- a/TrustedUninstaller.Shared/Actions/UserAction.cs +++ b/TrustedUninstaller.Shared/Actions/UserAction.cs @@ -10,6 +10,7 @@ namespace TrustedUninstaller.Shared.Actions { public class UserAction : TaskAction, ITaskAction { + public void RunTaskOnMainThread() { throw new NotImplementedException(); } [YamlMember(typeof(string), Alias = "name")] public string Username { get; set; } = ""; [YamlMember(typeof(bool), Alias = "admin")] diff --git a/TrustedUninstaller.Shared/Actions/WriteStatusAction.cs b/TrustedUninstaller.Shared/Actions/WriteStatusAction.cs index ef166ea..5b686c6 100644 --- a/TrustedUninstaller.Shared/Actions/WriteStatusAction.cs +++ b/TrustedUninstaller.Shared/Actions/WriteStatusAction.cs @@ -7,6 +7,7 @@ namespace TrustedUninstaller.Shared.Actions { public class WriteStatusAction : TaskAction, ITaskAction { + public void RunTaskOnMainThread() { throw new NotImplementedException(); } [YamlMember(typeof(string), Alias = "status")] public string Status { get; set; } private bool InProgress { get; set; } diff --git a/TrustedUninstaller.Shared/AmeliorationUtil.cs b/TrustedUninstaller.Shared/AmeliorationUtil.cs index 9d48253..531b1f3 100644 --- a/TrustedUninstaller.Shared/AmeliorationUtil.cs +++ b/TrustedUninstaller.Shared/AmeliorationUtil.cs @@ -27,8 +27,10 @@ namespace TrustedUninstaller.Shared private static readonly ConfigParser Parser = new ConfigParser(); private static readonly HttpClient Client = new HttpClient(); - - public static Playbook Playbook { set; get; } + + public static Playbook Playbook { set; get; } = new Playbook(); + + public static bool UseKernelDriver = false; public static readonly List ErrorDisplayList = new List(); @@ -37,16 +39,19 @@ namespace TrustedUninstaller.Shared return Parser.Tasks.Sum(task => task.Actions.Sum(action => { var taskAction = (TaskAction)action; - if (!String.IsNullOrEmpty(taskAction.Option) && - (taskAction.Option.StartsWith("!") && options != null && options.Contains(taskAction.Option.Substring(1), StringComparer.OrdinalIgnoreCase) || - !taskAction.Option.StartsWith("!") && options != null && !options.Contains(taskAction.Option, StringComparer.OrdinalIgnoreCase))) + if ((!IsApplicableOption(taskAction.Option, options) || !IsApplicableArch(taskAction.Arch)) || + (taskAction.Builds != null && ( + !taskAction.Builds.Where(build => !build.StartsWith("!")).Any(build => IsApplicableWindowsVersion(build)) + || + taskAction.Builds.Where(build => build.StartsWith("!")).Any(build => !IsApplicableWindowsVersion(build)))) || + (taskAction.Options != null && ( + !taskAction.Options.Where(option => !option.StartsWith("!")).Any(option => IsApplicableOption(option, Playbook.Options)) + || + taskAction.Options.Where(option => option.StartsWith("!")).Any(option => !IsApplicableOption(option, Playbook.Options))))) + { return 0; + } - if (!String.IsNullOrEmpty(taskAction.Arch) && - (taskAction.Arch.StartsWith("!") && String.Equals(taskAction.Arch, RuntimeInformation.ProcessArchitecture.ToString(), StringComparison.OrdinalIgnoreCase) || - !taskAction.Arch.StartsWith("!") && !String.Equals(taskAction.Arch, RuntimeInformation.ProcessArchitecture.ToString(), StringComparison.OrdinalIgnoreCase))) - return 0; - return action.GetProgressWeight(); })); } @@ -75,6 +80,21 @@ namespace TrustedUninstaller.Shared } } + if ((!IsApplicableOption(currentTask.Option, Playbook.Options) || !IsApplicableArch(currentTask.Arch)) || + (currentTask.Builds != null && ( + !currentTask.Builds.Where(build => !build.StartsWith("!")).Any(build => IsApplicableWindowsVersion(build)) + || + currentTask.Builds.Where(build => build.StartsWith("!")).Any(build => !IsApplicableWindowsVersion(build)))) || + (currentTask.Options != null && ( + !currentTask.Options.Where(option => !option.StartsWith("!")).Any(option => IsApplicableOption(option, Playbook.Options)) + || + currentTask.Options.Where(option => option.StartsWith("!")).Any(option => !IsApplicableOption(option, Playbook.Options))))) + { + Parser.Tasks.Remove(currentTask); + return true; + } + + //Get the features of the last added task (the task that was just added from the config file) var features = currentTask.Features; @@ -203,15 +223,18 @@ namespace TrustedUninstaller.Shared { var taskAction = (TaskAction)action; - if (!String.IsNullOrEmpty(taskAction.Option) && - (taskAction.Option.StartsWith("!") && Playbook.Options != null && Playbook.Options.Contains(taskAction.Option.Substring(1), StringComparer.OrdinalIgnoreCase) || - !taskAction.Option.StartsWith("!") && Playbook.Options != null && !Playbook.Options.Contains(taskAction.Option, StringComparer.OrdinalIgnoreCase))) - continue; - - if (!String.IsNullOrEmpty(taskAction.Arch) && - (taskAction.Arch.StartsWith("!") && String.Equals(taskAction.Arch, RuntimeInformation.ProcessArchitecture.ToString(), StringComparison.OrdinalIgnoreCase) || - !taskAction.Arch.StartsWith("!") && !String.Equals(taskAction.Arch, RuntimeInformation.ProcessArchitecture.ToString(), StringComparison.OrdinalIgnoreCase))) + if ((!IsApplicableOption(taskAction.Option, Playbook.Options) || !IsApplicableArch(taskAction.Arch)) || + (taskAction.Builds != null && ( + !taskAction.Builds.Where(build => !build.StartsWith("!")).Any(build => IsApplicableWindowsVersion(build)) + || + taskAction.Builds.Where(build => build.StartsWith("!")).Any(build => !IsApplicableWindowsVersion(build)))) || + (taskAction.Options != null && ( + !taskAction.Options.Where(option => !option.StartsWith("!")).Any(option => IsApplicableOption(option, Playbook.Options)) + || + taskAction.Options.Where(option => option.StartsWith("!")).Any(option => !IsApplicableOption(option, Playbook.Options))))) + { continue; + } int i = 0; @@ -225,7 +248,10 @@ namespace TrustedUninstaller.Shared Console.WriteLine(); try { - await action.RunTask(); + var actionTask = action.RunTask(); + if (actionTask == null) + action.RunTaskOnMainThread(); + else await actionTask; action.ResetProgress(); } catch (Exception e) @@ -275,6 +301,8 @@ namespace TrustedUninstaller.Shared } Console.WriteLine("Task completed."); + ProcessPrivilege.ResetTokens(); + File.AppendAllText("TasksAdded.txt", task.Title + Environment.NewLine); } catch (Exception e) @@ -434,20 +462,20 @@ namespace TrustedUninstaller.Shared Console.WriteLine($"{deletedItemsCount} files were deleted successfully. " + $"{failedDeletedItemsCount} files couldn't be deleted."); - //Check if the kernel driver is installed. //service = ServiceController.GetDevices() //.FirstOrDefault(s => s.DisplayName == "KProcessHacker2"); - if (true) + if (UseKernelDriver) { //Remove Process Hacker's kernel driver. await WinUtil.UninstallDriver(); + + await AmeliorationUtil.SafeRunAction(new RegistryKeyAction() + { + KeyName = @"HKLM\SYSTEM\CurrentControlSet\Services\KProcessHacker2", + }); } - await AmeliorationUtil.SafeRunAction(new RegistryKeyAction() - { - KeyName = @"HKLM\SYSTEM\CurrentControlSet\Services\KProcessHacker2", - }); - + File.Delete("TasksAdded.txt"); Console.WriteLine(); @@ -625,6 +653,95 @@ namespace TrustedUninstaller.Shared } } } + + private static bool IsApplicableWindowsVersion(string version) + { + bool negative = false; + if (version.StartsWith("!")) + { + version = version.TrimStart('!'); + negative = true; + } + + bool compareUpdateBuild = version.Contains("."); + var currentBuild = decimal.Parse(compareUpdateBuild ? Globals.WinVer + "." + Globals.WinUpdateVer : Globals.WinVer.ToString()); + + bool result = false; + if (version.StartsWith(">=")) + { + var parsed = decimal.Parse(version.Substring(2)); + if (currentBuild >= parsed) + result = true; + } else if (version.StartsWith("<=")) + { + var parsed = decimal.Parse(version.Substring(2)); + if (currentBuild <= parsed) + result = true; + } else if (version.StartsWith(">")) + { + var parsed = decimal.Parse(version.Substring(1)); + if (currentBuild > parsed) + result = true; + } else if (version.StartsWith("<")) + { + var parsed = decimal.Parse(version.Substring(1)); + if (currentBuild < parsed) + result = true; + } + else + { + var parsed = decimal.Parse(version); + if (currentBuild == parsed) + result = true; + } + + return negative ? !result : result; + } + + private static bool IsApplicableOption(string option, List options) + { + if (String.IsNullOrEmpty(option)) + return true; + + if (option.Contains("&")) + { + if (option.Contains("!")) + throw new ArgumentException("YAML options item must not contain both & and !", "options"); + + return option.Split('&').All(splitOption => IsApplicableOption(splitOption, options)); + } + + bool negative = false; + if (option.StartsWith("!")) + { + option = option.TrimStart('!'); + negative = true; + } + + if (options == null) + return negative ? true : false; + + var result = options.Contains(option, StringComparer.OrdinalIgnoreCase); + + return negative ? !result : result; + } + + private static bool IsApplicableArch(string arch) + { + if (String.IsNullOrEmpty(arch)) + return true; + + bool negative = false; + if (arch.StartsWith("!")) + { + arch = arch.TrimStart('!'); + negative = true; + } + + var result = String.Equals(arch, RuntimeInformation.ProcessArchitecture.ToString(), StringComparison.OrdinalIgnoreCase); + + return negative ? !result : result; + } public static async Task SafeRunAction(ITaskAction action) { diff --git a/TrustedUninstaller.Shared/AugmentedProcess.cs b/TrustedUninstaller.Shared/AugmentedProcess.cs new file mode 100644 index 0000000..f54d48b --- /dev/null +++ b/TrustedUninstaller.Shared/AugmentedProcess.cs @@ -0,0 +1,3340 @@ +using System.Text; +using System.Threading; +using System.Runtime.InteropServices; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Runtime.CompilerServices; +using System.Diagnostics; +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using Microsoft.Win32.SafeHandles; +using System.Collections.Specialized; +using System.Globalization; +using System.Security; +using System.Security.Permissions; +using System.Runtime.Versioning; +using System.Runtime.ConstrainedExecution; +using Microsoft.Win32; + +namespace TrustedUninstaller.Shared +{ + [DefaultEvent("Exited"), DefaultProperty("StartInfo"), HostProtection(SharedState = true, Synchronization = true, ExternalProcessMgmt = true, SelfAffectingProcessMgmt = true)] + public static class AugmentedProcess + { + public class Process : Component + { + public enum CreateType { + UserToken, + RawToken + } + // + // FIELDS + // + bool haveProcessId; + int processId; + bool haveProcessHandle; + SafeProcessHandle m_processHandle; + bool isRemoteMachine; + string machineName; + ProcessInfo processInfo; + Int32 m_processAccess; + ProcessThreadCollection threads; + ProcessModuleCollection modules; + bool haveMainWindow; + IntPtr mainWindowHandle; // no need to use SafeHandle for window + string mainWindowTitle; + bool haveWorkingSetLimits; + bool haveProcessorAffinity; + IntPtr processorAffinity; + bool havePriorityClass; + ProcessPriorityClass priorityClass; + ProcessStartInfo startInfo; + bool watchForExit; + bool watchingForExit; + EventHandler onExited; + bool exited; + int exitCode; + bool signaled; + DateTime exitTime; + bool haveExitTime; + bool responding; + bool haveResponding; + bool priorityBoostEnabled; + bool havePriorityBoostEnabled; + bool raisedOnExited; + bool expandEnvironmentVariables; + RegisteredWaitHandle registeredWaitHandle; + WaitHandle waitHandle; + ISynchronizeInvoke synchronizingObject; + StreamReader standardOutput; + StreamWriter standardInput; + StreamReader standardError; + OperatingSystem operatingSystem; + bool disposed; + static object s_CreateProcessLock = new object(); + + // This enum defines the operation mode for redirected process stream. + // We don't support switching between synchronous mode and asynchronous mode. + private enum StreamReadMode + { + undefined, + syncMode, + asyncMode + } + + StreamReadMode outputStreamReadMode; + StreamReadMode errorStreamReadMode; + public event DataReceivedEventHandler OutputDataReceived; + public event DataReceivedEventHandler ErrorDataReceived; + // Abstract the stream details + internal AsyncStreamReader output; + internal AsyncStreamReader error; + internal bool pendingOutputRead; + internal bool pendingErrorRead; + internal static TraceSwitch processTracing = null; + public Process() + { + this.machineName = "."; + this.outputStreamReadMode = StreamReadMode.undefined; + this.errorStreamReadMode = StreamReadMode.undefined; + this.m_processAccess = NativeMethods.PROCESS_ALL_ACCESS; + } + [ResourceExposure(ResourceScope.Machine)] + Process(string machineName, bool isRemoteMachine, int processId, ProcessInfo processInfo) : base() + { + this.processInfo = processInfo; + this.machineName = machineName; + this.isRemoteMachine = isRemoteMachine; + this.processId = processId; + this.haveProcessId = true; + this.outputStreamReadMode = StreamReadMode.undefined; + this.errorStreamReadMode = StreamReadMode.undefined; + this.m_processAccess = NativeMethods.PROCESS_ALL_ACCESS; + } + + // + // PROPERTIES + // + bool Associated + { + get + { + return haveProcessId || haveProcessHandle; + } + } + + public string ProcessName + { + get + { + this.EnsureState(Process.State.HaveProcessInfo); + return this.processInfo.processName; + } + } + + public int ExitCode + { + get + { + EnsureState(State.Exited); + return exitCode; + } + } + public bool HasExited + { + get + { + if (!exited) + { + EnsureState(State.Associated); + SafeProcessHandle handle = null; + try + { + handle = GetProcessHandle(NativeMethods.PROCESS_QUERY_INFORMATION | NativeMethods.SYNCHRONIZE, false); + if (handle.IsInvalid) + { + exited = true; + } + else + { + int exitCode; + + // Although this is the wrong way to check whether the process has exited, + // it was historically the way we checked for it, and a lot of code then took a dependency on + // the fact that this would always be set before the pipes were closed, so they would read + // the exit code out after calling ReadToEnd() or standard output or standard error. In order + // to allow 259 to function as a valid exit code and to break as few people as possible that + // took the ReadToEnd dependency, we check for an exit code before doing the more correct + // check to see if we have been signalled. + if (NativeMethods.GetExitCodeProcess(handle, out exitCode) && exitCode != NativeMethods.STILL_ACTIVE) + { + this.exited = true; + this.exitCode = exitCode; + } + else + { + // The best check for exit is that the kernel process object handle is invalid, + // or that it is valid and signaled. Checking if the exit code != STILL_ACTIVE + // does not guarantee the process is closed, + // since some process could return an actual STILL_ACTIVE exit code (259). + if (!signaled) // if we just came from WaitForExit, don't repeat + { + ProcessWaitHandle wh = null; + try + { + wh = new ProcessWaitHandle(handle); + this.signaled = wh.WaitOne(0, false); + } + finally + { + if (wh != null) wh.Close(); + } + } + if (signaled) + { + if (!NativeMethods.GetExitCodeProcess(handle, out exitCode)) throw new Win32Exception(); + this.exited = true; + this.exitCode = exitCode; + } + } + } + } + finally + { + ReleaseProcessHandle(handle); + } + if (exited) + { + RaiseOnExited(); + } + } + return exited; + } + } + public IntPtr Handle + { + [ResourceExposure(ResourceScope.Machine)] + [ResourceConsumption(ResourceScope.Machine)] + get + { + EnsureState(State.Associated); + return OpenProcessHandle(this.m_processAccess).DangerousGetHandle(); + } + } + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public SafeProcessHandle SafeHandle + { + get + { + EnsureState(State.Associated); + return OpenProcessHandle(this.m_processAccess); + } + } + public int Id + { + get + { + EnsureState(State.HaveId); + return processId; + } + } + public string MachineName + { + get + { + EnsureState(State.Associated); + return machineName; + } + } + [System.Runtime.InteropServices.ComVisible(false)] + public long NonpagedSystemMemorySize64 + { + get + { + EnsureState(State.HaveNtProcessInfo); + return processInfo.poolNonpagedBytes; + } + } + [System.Runtime.InteropServices.ComVisible(false)] + public long PagedMemorySize64 + { + get + { + EnsureState(State.HaveNtProcessInfo); + return processInfo.pageFileBytes; + } + } + [System.Runtime.InteropServices.ComVisible(false)] + public long PagedSystemMemorySize64 + { + get + { + EnsureState(State.HaveNtProcessInfo); + return processInfo.poolPagedBytes; + } + } + [System.Runtime.InteropServices.ComVisible(false)] + public long PeakPagedMemorySize64 + { + get + { + EnsureState(State.HaveNtProcessInfo); + return processInfo.pageFileBytesPeak; + } + } + [System.Runtime.InteropServices.ComVisible(false)] + public long PeakWorkingSet64 + { + get + { + EnsureState(State.HaveNtProcessInfo); + return processInfo.workingSetPeak; + } + } + [System.Runtime.InteropServices.ComVisible(false)] + public long PeakVirtualMemorySize64 + { + get + { + EnsureState(State.HaveNtProcessInfo); + return processInfo.virtualBytesPeak; + } + } + private OperatingSystem OperatingSystem + { + get + { + if (operatingSystem == null) + { + operatingSystem = Environment.OSVersion; + } + return operatingSystem; + } + } + [System.Runtime.InteropServices.ComVisible(false)] + public long PrivateMemorySize64 + { + get + { + EnsureState(State.HaveNtProcessInfo); + return processInfo.privateBytes; + } + } + public int SessionId + { + get + { + EnsureState(State.HaveNtProcessInfo); + return processInfo.sessionId; + } + } + public ProcessStartInfo StartInfo + { + get + { + return startInfo; + } + [ResourceExposure(ResourceScope.Machine)] + set + { + if (value == null) + { + throw new ArgumentNullException("value"); + } + startInfo = value; + } + } + + public bool ExpandEnvironmentVariables + { + get + { + return expandEnvironmentVariables; + } + set + { + expandEnvironmentVariables = value; + } + } + + public ISynchronizeInvoke SynchronizingObject + { + get + { + if (this.synchronizingObject == null && DesignMode) + { + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + if (host != null) + { + object baseComponent = host.RootComponent; + if (baseComponent != null && baseComponent is ISynchronizeInvoke) this.synchronizingObject = (ISynchronizeInvoke)baseComponent; + } + } + return this.synchronizingObject; + } + set + { + this.synchronizingObject = value; + } + } + [System.Runtime.InteropServices.ComVisible(false)] + public long VirtualMemorySize64 + { + get + { + EnsureState(State.HaveNtProcessInfo); + return processInfo.virtualBytes; + } + } + public bool EnableRaisingEvents + { + get + { + return watchForExit; + } + set + { + if (value != watchForExit) + { + if (Associated) + { + if (value) + { + OpenProcessHandle(); + EnsureWatchingForExit(); + } + else + { + StopWatchingForExit(); + } + } + watchForExit = value; + } + } + } + public StreamWriter StandardInput + { + get + { + if (standardInput == null) + { + throw new InvalidOperationException("CantGetStandardIn"); + } + return standardInput; + } + } + public StreamReader StandardOutput + { + get + { + if (standardOutput == null) + { + throw new InvalidOperationException("CantGetStandardOut"); + } + if (outputStreamReadMode == StreamReadMode.undefined) + { + outputStreamReadMode = StreamReadMode.syncMode; + } + else if (outputStreamReadMode != StreamReadMode.syncMode) + { + throw new InvalidOperationException("CantMixSyncAsyncOperation"); + } + return standardOutput; + } + } + public StreamReader StandardError + { + get + { + if (standardError == null) + { + throw new InvalidOperationException("CantGetStandardError"); + } + if (errorStreamReadMode == StreamReadMode.undefined) + { + errorStreamReadMode = StreamReadMode.syncMode; + } + else if (errorStreamReadMode != StreamReadMode.syncMode) + { + throw new InvalidOperationException("CantMixSyncAsyncOperation"); + } + return standardError; + } + } + public int WorkingSet + { + get + { + EnsureState(State.HaveNtProcessInfo); + return unchecked((int)processInfo.workingSet); + } + } + [System.Runtime.InteropServices.ComVisible(false)] + public long WorkingSet64 + { + get + { + EnsureState(State.HaveNtProcessInfo); + return processInfo.workingSet; + } + } + public event EventHandler Exited + { + add + { + onExited += value; + } + remove + { + onExited -= value; + } + } + /// + /// Release the temporary handle we used to get process information. + /// If we used the process handle stored in the process object (we have all access to the handle,) don't release it. + /// + /// + void ReleaseProcessHandle(SafeProcessHandle handle) + { + if (handle == null) + { + return; + } + if (haveProcessHandle && handle == m_processHandle) + { + return; + } + handle.Close(); + } + /// + /// This is called from the threadpool when a proces exits. + /// + /// + private void CompletionCallback(object context, bool wasSignaled) + { + StopWatchingForExit(); + RaiseOnExited(); + } + /// + /// + /// + /// Free any resources associated with this component. + /// + /// + protected override void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + //Dispose managed and unmanaged resources + Close(); + } + this.disposed = true; + base.Dispose(disposing); + } + } + /// + /// + /// Frees any resources associated with this component. + /// + /// + public void Close() + { + if (Associated) + { + if (haveProcessHandle) + { + StopWatchingForExit(); + m_processHandle.Close(); + m_processHandle = null; + haveProcessHandle = false; + } + haveProcessId = false; + isRemoteMachine = false; + machineName = "."; + raisedOnExited = false; + + //Don't call close on the Readers and writers + //since they might be referenced by somebody else while the + //process is still alive but this method called. + standardOutput = null; + standardInput = null; + standardError = null; + output = null; + error = null; + Refresh(); + } + } + /// + /// Helper method for checking preconditions when accessing properties. + /// + /// + [ResourceExposure(ResourceScope.None)] + [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] + void EnsureState(State state) + { + if ((state & State.Associated) != (State)0) + if (!Associated) + throw new InvalidOperationException("NoAssociatedProcess"); + if ((state & State.IsLocal) != (State)0 && isRemoteMachine) + { + throw new NotSupportedException("NotSupportedRemote"); + } + if ((state & Process.State.HaveProcessInfo) != (Process.State) 0 && this.processInfo == null) + { + if ((state & Process.State.HaveId) == (Process.State) 0) + this.EnsureState(Process.State.HaveId); + this.processInfo = GetProcessInfo(this.processId, this.machineName); + if (this.processInfo == null) + throw new InvalidOperationException("NoProcessInfo"); + } + if ((state & State.Exited) != (State)0) + { + if (!HasExited) + { + throw new InvalidOperationException("WaitTillExit"); + } + if (!haveProcessHandle) + { + throw new InvalidOperationException("NoProcessHandle"); + } + } + } + void EnsureWatchingForExit() + { + if (!watchingForExit) + { + lock (this) + { + if (!watchingForExit) + { + watchingForExit = true; + try + { + this.waitHandle = new ProcessWaitHandle(m_processHandle); + this.registeredWaitHandle = ThreadPool.RegisterWaitForSingleObject(this.waitHandle, new WaitOrTimerCallback(this.CompletionCallback), null, -1, true); + } + catch + { + watchingForExit = false; + throw; + } + } + } + } + } + protected void OnExited() + { + EventHandler exited = onExited; + if (exited != null) + { + if (this.SynchronizingObject != null && this.SynchronizingObject.InvokeRequired) + this.SynchronizingObject.BeginInvoke(exited, new object[] + { + this, EventArgs.Empty + }); + else exited(this, EventArgs.Empty); + } + } + [ResourceExposure(ResourceScope.None)] + [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] + SafeProcessHandle GetProcessHandle(int access, bool throwIfExited) + { + if (haveProcessHandle) + { + if (throwIfExited) + { + // Since haveProcessHandle is true, we know we have the process handle + // open with at least SYNCHRONIZE access, so we can wait on it with + // zero timeout to see if the process has exited. + ProcessWaitHandle waitHandle = null; + try + { + waitHandle = new ProcessWaitHandle(m_processHandle); + if (waitHandle.WaitOne(0, false)) + { + if (haveProcessId) throw new InvalidOperationException("Process has exited: " + processId); + else throw new InvalidOperationException("ProcessHasExitedNoId"); + } + } + finally + { + if (waitHandle != null) + { + waitHandle.Close(); + } + } + } + return m_processHandle; + } + else + { + throw new Exception("(AME) Process handle not available."); + } + } + /// + /// Gets a short-term handle to the process, with the given access. If a handle exists, + /// then it is reused. If the process has exited, it throws an exception. + /// + /// + SafeProcessHandle GetProcessHandle(int access) + { + return GetProcessHandle(access, true); + } + /// + /// Opens a long-term handle to the process, with all access. If a handle exists, + /// then it is reused. If the process has exited, it throws an exception. + /// + /// + SafeProcessHandle OpenProcessHandle() + { + return OpenProcessHandle(NativeMethods.PROCESS_ALL_ACCESS); + } + SafeProcessHandle OpenProcessHandle(Int32 access) + { + if (!haveProcessHandle) + { + //Cannot open a new process handle if the object has been disposed, since finalization has been suppressed. + if (this.disposed) + { + throw new ObjectDisposedException(GetType().Name); + } + SetProcessHandle(GetProcessHandle(access)); + } + return m_processHandle; + } + /// + /// Raise the Exited event, but make sure we don't do it more than once. + /// + /// + void RaiseOnExited() + { + if (!raisedOnExited) + { + lock (this) + { + if (!raisedOnExited) + { + raisedOnExited = true; + OnExited(); + } + } + } + } + /// + /// + /// Discards any information about the associated process + /// that has been cached inside the process component. After is called, the + /// first request for information for each property causes the process component + /// to obtain a new value from the associated process. + /// + /// + public void Refresh() + { + processInfo = null; + threads = null; + modules = null; + mainWindowTitle = null; + exited = false; + signaled = false; + haveMainWindow = false; + haveWorkingSetLimits = false; + haveProcessorAffinity = false; + havePriorityClass = false; + haveExitTime = false; + haveResponding = false; + havePriorityBoostEnabled = false; + } + /// + /// Helper to associate a process handle with this component. + /// + /// + void SetProcessHandle(SafeProcessHandle processHandle) + { + this.m_processHandle = processHandle; + this.haveProcessHandle = true; + if (watchForExit) + { + EnsureWatchingForExit(); + } + } + /// + /// Helper to associate a process id with this component. + /// + /// + [ResourceExposure(ResourceScope.Machine)] + void SetProcessId(int processId) + { + this.processId = processId; + this.haveProcessId = true; + } + /// + /// + /// Starts a process specified by the property of this + /// component and associates it with the + /// . If a process resource is reused + /// rather than started, the reused process is associated with this + /// component. + /// + /// + [ResourceExposure(ResourceScope.None)] + [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] + public bool Start(CreateType type, ref Win32.TokensEx.SafeTokenHandle token) + { + Close(); + ProcessStartInfo startInfo = StartInfo; + if (startInfo.FileName.Length == 0) throw new InvalidOperationException("FileNameMissing"); + + return StartWithCreateProcess(startInfo, type, ref token); + } + [ResourceExposure(ResourceScope.Process)] + [ResourceConsumption(ResourceScope.Process)] + private static void CreatePipeWithSecurityAttributes(out SafeFileHandle hReadPipe, out SafeFileHandle hWritePipe, NativeMethods.SECURITY_ATTRIBUTES lpPipeAttributes, int nSize) + { + bool ret = NativeMethods.CreatePipe(out hReadPipe, out hWritePipe, lpPipeAttributes, nSize); + if (!ret || hReadPipe.IsInvalid || hWritePipe.IsInvalid) + { + throw new Win32Exception(); + } + } + + // Using synchronous Anonymous pipes for process input/output redirection means we would end up + // wasting a worker threadpool thread per pipe instance. Overlapped pipe IO is desirable, since + // it will take advantage of the NT IO completion port infrastructure. But we can't really use + // Overlapped I/O for process input/output as it would break Console apps (managed Console class + // methods such as WriteLine as well as native CRT functions like printf) which are making an + // assumption that the console standard handles (obtained via GetStdHandle()) are opened + // for synchronous I/O and hence they can work fine with ReadFile/WriteFile synchrnously! + [ResourceExposure(ResourceScope.None)] + [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] + private void CreatePipe(out SafeFileHandle parentHandle, out SafeFileHandle childHandle, bool parentInputs) + { + NativeMethods.SECURITY_ATTRIBUTES securityAttributesParent = new NativeMethods.SECURITY_ATTRIBUTES(); + securityAttributesParent.bInheritHandle = true; + SafeFileHandle hTmp = null; + try + { + if (parentInputs) + { + CreatePipeWithSecurityAttributes(out childHandle, out hTmp, securityAttributesParent, 0); + } + else + { + CreatePipeWithSecurityAttributes(out hTmp, out childHandle, securityAttributesParent, 0); + } + // Duplicate the parent handle to be non-inheritable so that the child process + // doesn't have access. This is done for correctness sake, exact reason is unclear. + // One potential theory is that child process can do something brain dead like + // closing the parent end of the pipe and there by getting into a blocking situation + // as parent will not be draining the pipe at the other end anymore. + if (!NativeMethods.DuplicateHandle(new HandleRef(this, NativeMethods.GetCurrentProcess()), hTmp, new HandleRef(this, NativeMethods.GetCurrentProcess()), out parentHandle, 0, false, + NativeMethods.DUPLICATE_SAME_ACCESS)) + { + throw new Win32Exception(); + } + } + finally + { + if (hTmp != null && !hTmp.IsInvalid) + { + hTmp.Close(); + } + } + } + private static StringBuilder BuildCommandLine(string executableFileName, string arguments) + { + // Construct a StringBuilder with the appropriate command line + // to pass to CreateProcess. If the filename isn't already + // in quotes, we quote it here. This prevents some security + // problems (it specifies exactly which part of the string + // is the file to execute). + StringBuilder commandLine = new StringBuilder(); + string fileName = executableFileName.Trim(); + bool fileNameIsQuoted = (fileName.StartsWith("\"", StringComparison.Ordinal) && fileName.EndsWith("\"", StringComparison.Ordinal)); + if (!fileNameIsQuoted) + { + commandLine.Append("\""); + } + commandLine.Append(fileName); + if (!fileNameIsQuoted) + { + commandLine.Append("\""); + } + if (!String.IsNullOrEmpty(arguments)) + { + commandLine.Append(" "); + commandLine.Append(arguments); + } + return commandLine; + } + [ResourceExposure(ResourceScope.Machine)] + [ResourceConsumption(ResourceScope.Machine)] + private bool StartWithCreateProcess(ProcessStartInfo startInfo, CreateType type, ref Win32.TokensEx.SafeTokenHandle token) + { + // See knowledge base article Q190351 for an explanation of the following code. Noteworthy tricky points: + // * The handles are duplicated as non-inheritable before they are passed to CreateProcess so + // that the child process can not close them + // * CreateProcess allows you to redirect all or none of the standard IO handles, so we use + // GetStdHandle for the handles that are not being redirected + + //Cannot start a new process and store its handle if the object has been disposed, since finalization has been suppressed. + if (this.disposed) + { + throw new ObjectDisposedException(GetType().Name); + } + StringBuilder commandLine = BuildCommandLine(startInfo.FileName, startInfo.Arguments); + NativeMethods.STARTUPINFO startupInfo = new NativeMethods.STARTUPINFO(); + NativeMethods.PROCESS_INFORMATION processInfo = new NativeMethods.PROCESS_INFORMATION(); + SafeProcessHandle procSH = new SafeProcessHandle(); + SafeThreadHandle threadSH = new SafeThreadHandle(); + bool retVal; + int errorCode = 0; + // handles used in parent process + SafeFileHandle standardInputWritePipeHandle = null; + SafeFileHandle standardOutputReadPipeHandle = null; + SafeFileHandle standardErrorReadPipeHandle = null; + IntPtr environmentPtr = (IntPtr)0; + //GCHandle environmentHandle = new GCHandle(); + lock (s_CreateProcessLock) + { + try + { + // set up the streams + if (startInfo.CreateNoWindow && (startInfo.RedirectStandardInput || startInfo.RedirectStandardOutput || startInfo.RedirectStandardError)) + { + if (startInfo.StandardOutputEncoding != null && !startInfo.RedirectStandardOutput) + { + throw new InvalidOperationException("StandardOutputEncodingNotAllowed"); + } + if (startInfo.StandardErrorEncoding != null && !startInfo.RedirectStandardError) + { + throw new InvalidOperationException("StandardErrorEncodingNotAllowed"); + } + + if (startInfo.RedirectStandardInput) + { + CreatePipe(out standardInputWritePipeHandle, out startupInfo.hStdInput, true); + } + else + { + startupInfo.hStdInput = new SafeFileHandle(NativeMethods.GetStdHandle(NativeMethods.STD_INPUT_HANDLE), false); + } + if (startInfo.RedirectStandardOutput) + { + CreatePipe(out standardOutputReadPipeHandle, out startupInfo.hStdOutput, false); + } + else + { + startupInfo.hStdOutput = new SafeFileHandle(NativeMethods.GetStdHandle(NativeMethods.STD_OUTPUT_HANDLE), false); + } + if (startInfo.RedirectStandardError) + { + CreatePipe(out standardErrorReadPipeHandle, out startupInfo.hStdError, false); + } + else + { + startupInfo.hStdError = new SafeFileHandle(NativeMethods.GetStdHandle(NativeMethods.STD_ERROR_HANDLE), false); + } + startupInfo.dwFlags = NativeMethods.STARTF_USESTDHANDLES; + } + + // set up the creation flags paramater + int creationFlags = 0; + if (startInfo.CreateNoWindow) creationFlags |= NativeMethods.CREATE_NO_WINDOW; + + // set up the environment block parameterhttps://www.beyondtrust.com/assets/documents/BeyondTrust-Microsoft-Vulnerabilities-Report-2021.pdf + + //if (startInfo.environmentVariables != null) + if (true) + { + creationFlags |= NativeMethods.CREATE_UNICODE_ENVIRONMENT; + //byte[] environmentBytes = EnvironmentBlock.ToByteArray(startInfo.environmentVariables, true); + //environmentHandle = GCHandle.Alloc(environmentBytes, GCHandleType.Pinned); + //environmentPtr = environmentHandle.AddrOfPinnedObject(); + Win32.Process.CreateEnvironmentBlock(out environmentPtr, token, false); + } + + /* + if (ExpandEnvironmentVariables && startInfo.Arguments.Contains("%")) + { + Environment.ExpandEnvironmentVariables() + var envVars = new Dictionary(StringComparer.OrdinalIgnoreCase); + + IntPtr next = environmentPtr; + while (Marshal.ReadByte(next) != 0) + { + var str = Marshal.PtrToStringUni(next); + // skip first character because windows allows env vars to begin with equal sign + var splitPoint = str.IndexOf('=', 1); + var envVarName = str.Substring(0, splitPoint); + var envVarVal = str.Substring(splitPoint + 1); + envVars.Add(envVarName, envVarVal); + next = (IntPtr)((Int64)next + (str.Length * 2) + 2); + } + return envVars; + } + */ + + if (!startInfo.CreateNoWindow) + { + creationFlags |= (int)Win32.Process.ProcessCreationFlags.CREATE_DEFAULT_ERROR_MODE; + creationFlags |= (int)Win32.Process.ProcessCreationFlags.CREATE_NEW_CONSOLE; + creationFlags |= (int)Win32.Process.ProcessCreationFlags.CREATE_NEW_PROCESS_GROUP; + //startupInfo.lpDesktop = "Winsta0\\Default"; + } + + string workingDirectory = startInfo.WorkingDirectory; + if (workingDirectory == string.Empty) workingDirectory = Environment.CurrentDirectory; + RuntimeHelpers.PrepareConstrainedRegions(); + try { } + finally + { + retVal = false; + if (type == CreateType.UserToken) + { + retVal = NativeMethods.CreateProcessAsUser(token, + null, // we don't need this since all the info is in commandLine + commandLine, // pointer to the command line string + null, // pointer to process security attributes, we don't need to inheriat the handle + null, // pointer to thread security attributes + true, // handle inheritance flag + creationFlags, // creation flags + environmentPtr, // pointer to new environment block + workingDirectory, // pointer to current directory name + startupInfo, // pointer to STARTUPINFO + processInfo // pointer to PROCESS_INFORMATION + ); + } else if (type == CreateType.RawToken) + { + retVal = NativeMethods.CreateProcessWithToken(token, + NativeMethods.LogonFlags.LOGON_WITH_PROFILE, + null, // we don't need this since all the info is in commandLine + commandLine, // pointer to the command line string + creationFlags, // creation flags + environmentPtr, // pointer to new environment block + workingDirectory, // pointer to current directory name + startupInfo, // pointer to STARTUPINFO + processInfo // pointer to PROCESS_INFORMATION + ); + } + /* + retVal = NativeMethods.CreateProcess(null, // we don't need this since all the info is in commandLine + commandLine, // pointer to the command line string + null, // pointer to process security attributes, we don't need to inheriat the handle + null, // pointer to thread security attributes + true, // handle inheritance flag + creationFlags, // creation flags + environmentPtr, // pointer to new environment block + workingDirectory, // pointer to current directory name + startupInfo, // pointer to STARTUPINFO + processInfo // pointer to PROCESS_INFORMATION + ); + */ + if (!retVal) errorCode = Marshal.GetLastWin32Error(); + if (processInfo.hProcess != (IntPtr)0 && processInfo.hProcess != (IntPtr)NativeMethods.INVALID_HANDLE_VALUE) procSH.InitialSetHandle(processInfo.hProcess); + if (processInfo.hThread != (IntPtr)0 && processInfo.hThread != (IntPtr)NativeMethods.INVALID_HANDLE_VALUE) threadSH.InitialSetHandle(processInfo.hThread); + } + if (!retVal) + { + if (errorCode == NativeMethods.ERROR_BAD_EXE_FORMAT || errorCode == NativeMethods.ERROR_EXE_MACHINE_TYPE_MISMATCH) + { + throw new Win32Exception(errorCode, "InvalidApplication"); + } + throw new Win32Exception(errorCode); + } + } + finally + { + // free environment block + //if (environmentHandle.IsAllocated) + //{ + // environmentHandle.Free(); + //} + Win32.Process.DestroyEnvironmentBlock(environmentPtr); + startupInfo.Dispose(); + } + } + if (startInfo.RedirectStandardInput) + { + standardInput = new StreamWriter(new FileStream(standardInputWritePipeHandle, FileAccess.Write, 4096, false), Console.InputEncoding, 4096); + standardInput.AutoFlush = true; + } + if (startInfo.RedirectStandardOutput) + { + Encoding enc = (startInfo.StandardOutputEncoding != null) ? startInfo.StandardOutputEncoding : Console.OutputEncoding; + standardOutput = new StreamReader(new FileStream(standardOutputReadPipeHandle, FileAccess.Read, 4096, false), enc, true, 4096); + } + if (startInfo.RedirectStandardError) + { + Encoding enc = (startInfo.StandardErrorEncoding != null) ? startInfo.StandardErrorEncoding : Console.OutputEncoding; + standardError = new StreamReader(new FileStream(standardErrorReadPipeHandle, FileAccess.Read, 4096, false), enc, true, 4096); + } + bool ret = false; + if (!procSH.IsInvalid) + { + SetProcessHandle(procSH); + SetProcessId(processInfo.dwProcessId); + threadSH.Close(); + ret = true; + } + return ret; + } + +/* + private static string ExpandEnvironmentVariables(string name, Dictionary environment) + { + + switch (name) + { + case null: + case "": + return name; + default: + environment = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + {"SuSSY", "gussys"}, + {"AMoGus", "gussys"}, + {"Sussex", "gussys"} + }; + + StringBuilder result = new StringBuilder(); + + int index = name.IndexOf('%'); + if (index > 0) + result.Append(name.Substring(index)); + + int lastValidIndex = -1; + while (index != -1) + { + lastValidIndex = index; + + var end = name.IndexOf('%', index + 1); + if (end == -1) + { + result.Append('%'); + break; + } + + // Double % escape + if (end == index + 1) + { + index = name.IndexOf('%', index + 2); + result.Append(index == -1 ? "%" : "%" + name.Substring(end + 1, index - (end + 1))); + continue; + } + + lastValidIndex = end; + + if (environment.TryGetValue(name.Substring(index + 1, end - (index + 1)), out string varValue)) + result.Append(varValue); + + index = name.IndexOf('%', end + 1); + + if (index != -1) + { + result.Append(name.Substring(end + 1, index - (end + 1))); + } + } + + result.Append(lastValidIndex != -1 ? name.Substring(index + 1, name.Length - (index + 1)) : name); + + return result.ToString(); + } + + } + */ + [ResourceExposure(ResourceScope.Machine)] + [ResourceConsumption(ResourceScope.Machine)] + private bool StartWithShellExecuteEx(ProcessStartInfo startInfo) + { + //Cannot start a new process and store its handle if the object has been disposed, since finalization has been suppressed. + if (this.disposed) throw new ObjectDisposedException(GetType().Name); + if (!String.IsNullOrEmpty(startInfo.UserName) || (startInfo.Password != null)) + { + throw new InvalidOperationException("CantStartAsUser"); + } + if (startInfo.RedirectStandardInput || startInfo.RedirectStandardOutput || startInfo.RedirectStandardError) + { + throw new InvalidOperationException("CantRedirectStreams"); + } + if (startInfo.StandardErrorEncoding != null) + { + throw new InvalidOperationException("StandardErrorEncodingNotAllowed"); + } + if (startInfo.StandardOutputEncoding != null) + { + throw new InvalidOperationException("StandardOutputEncodingNotAllowed"); + } + + // can't set env vars with ShellExecuteEx... + if (startInfo.environmentVariables != null) + { + throw new InvalidOperationException("CantUseEnvVars"); + } + NativeMethods.ShellExecuteInfo shellExecuteInfo = new NativeMethods.ShellExecuteInfo(); + shellExecuteInfo.fMask = NativeMethods.SEE_MASK_NOCLOSEPROCESS; + if (startInfo.ErrorDialog) + { + shellExecuteInfo.hwnd = startInfo.ErrorDialogParentHandle; + } + else + { + shellExecuteInfo.fMask |= NativeMethods.SEE_MASK_FLAG_NO_UI; + } + switch (startInfo.WindowStyle) + { + case ProcessWindowStyle.Hidden: + shellExecuteInfo.nShow = NativeMethods.SW_HIDE; + break; + case ProcessWindowStyle.Minimized: + shellExecuteInfo.nShow = NativeMethods.SW_SHOWMINIMIZED; + break; + case ProcessWindowStyle.Maximized: + shellExecuteInfo.nShow = NativeMethods.SW_SHOWMAXIMIZED; + break; + default: + shellExecuteInfo.nShow = NativeMethods.SW_SHOWNORMAL; + break; + } + try + { + if (startInfo.FileName.Length != 0) shellExecuteInfo.lpFile = Marshal.StringToHGlobalAuto(startInfo.FileName); + if (startInfo.Verb.Length != 0) shellExecuteInfo.lpVerb = Marshal.StringToHGlobalAuto(startInfo.Verb); + if (startInfo.Arguments.Length != 0) shellExecuteInfo.lpParameters = Marshal.StringToHGlobalAuto(startInfo.Arguments); + if (startInfo.WorkingDirectory.Length != 0) shellExecuteInfo.lpDirectory = Marshal.StringToHGlobalAuto(startInfo.WorkingDirectory); + shellExecuteInfo.fMask |= NativeMethods.SEE_MASK_FLAG_DDEWAIT; + ShellExecuteHelper executeHelper = new ShellExecuteHelper(shellExecuteInfo); + if (!executeHelper.ShellExecuteOnSTAThread()) + { + int error = executeHelper.ErrorCode; + if (error == 0) + { + switch ((long)shellExecuteInfo.hInstApp) + { + case NativeMethods.SE_ERR_FNF: + error = NativeMethods.ERROR_FILE_NOT_FOUND; + break; + case NativeMethods.SE_ERR_PNF: + error = NativeMethods.ERROR_PATH_NOT_FOUND; + break; + case NativeMethods.SE_ERR_ACCESSDENIED: + error = NativeMethods.ERROR_ACCESS_DENIED; + break; + case NativeMethods.SE_ERR_OOM: + error = NativeMethods.ERROR_NOT_ENOUGH_MEMORY; + break; + case NativeMethods.SE_ERR_DDEFAIL: + case NativeMethods.SE_ERR_DDEBUSY: + case NativeMethods.SE_ERR_DDETIMEOUT: + error = NativeMethods.ERROR_DDE_FAIL; + break; + case NativeMethods.SE_ERR_SHARE: + error = NativeMethods.ERROR_SHARING_VIOLATION; + break; + case NativeMethods.SE_ERR_NOASSOC: + error = NativeMethods.ERROR_NO_ASSOCIATION; + break; + case NativeMethods.SE_ERR_DLLNOTFOUND: + error = NativeMethods.ERROR_DLL_NOT_FOUND; + break; + default: + error = (int)shellExecuteInfo.hInstApp; + break; + } + } + if (error == NativeMethods.ERROR_BAD_EXE_FORMAT || error == NativeMethods.ERROR_EXE_MACHINE_TYPE_MISMATCH) + { + throw new Win32Exception(error, "InvalidApplication"); + } + throw new Win32Exception(error); + } + } + finally + { + if (shellExecuteInfo.lpFile != (IntPtr)0) Marshal.FreeHGlobal(shellExecuteInfo.lpFile); + if (shellExecuteInfo.lpVerb != (IntPtr)0) Marshal.FreeHGlobal(shellExecuteInfo.lpVerb); + if (shellExecuteInfo.lpParameters != (IntPtr)0) Marshal.FreeHGlobal(shellExecuteInfo.lpParameters); + if (shellExecuteInfo.lpDirectory != (IntPtr)0) Marshal.FreeHGlobal(shellExecuteInfo.lpDirectory); + } + if (shellExecuteInfo.hProcess != (IntPtr)0) + { + SafeProcessHandle handle = new SafeProcessHandle(shellExecuteInfo.hProcess); + SetProcessHandle(handle); + return true; + } + return false; + } + /// + /// + /// Starts a process resource specified by the process start + /// information passed in, for example the file name of the process to start. + /// Associates the process resource with a new + /// component. + /// + /// + [ResourceExposure(ResourceScope.Machine)] + [ResourceConsumption(ResourceScope.Machine)] + public static Process Start(CreateType type, ProcessStartInfo startInfo, Win32.TokensEx.SafeTokenHandle token) + { + Process process = new Process(); + if (startInfo == null) throw new ArgumentNullException("startInfo"); + process.StartInfo = startInfo; + if (process.Start(type, ref token)) + { + return process; + } + return null; + } + /// + /// + /// Stops the + /// associated process immediately. + /// + /// + [ResourceExposure(ResourceScope.Machine)] + [ResourceConsumption(ResourceScope.Machine)] + public void Kill() + { + SafeProcessHandle handle = null; + try + { + handle = GetProcessHandle(NativeMethods.PROCESS_TERMINATE); + if (!NativeMethods.TerminateProcess(handle, -1)) throw new Win32Exception(); + } + finally + { + ReleaseProcessHandle(handle); + } + } + /// + /// Make sure we are not watching for process exit. + /// + /// + void StopWatchingForExit() + { + if (watchingForExit) + { + lock (this) + { + if (watchingForExit) + { + watchingForExit = false; + registeredWaitHandle.Unregister(null); + waitHandle.Close(); + waitHandle = null; + registeredWaitHandle = null; + } + } + } + } + /// + /// + /// Instructs the component to wait the specified number of milliseconds for the associated process to exit. + /// + /// + public bool WaitForExit(int milliseconds) + { + SafeProcessHandle handle = null; + bool exited; + ProcessWaitHandle processWaitHandle = null; + try + { + handle = GetProcessHandle(NativeMethods.SYNCHRONIZE, false); + if (handle.IsInvalid) + { + exited = true; + } + else + { + processWaitHandle = new ProcessWaitHandle(handle); + if (processWaitHandle.WaitOne(milliseconds, false)) + { + exited = true; + signaled = true; + } + else + { + exited = false; + signaled = false; + } + } + } + finally + { + if (processWaitHandle != null) + { + processWaitHandle.Close(); + } + + // If we have a hard timeout, we cannot wait for the streams + if (output != null && milliseconds == -1) + { + output.WaitUtilEOF(); + } + if (error != null && milliseconds == -1) + { + error.WaitUtilEOF(); + } + ReleaseProcessHandle(handle); + } + if (exited && watchForExit) + { + RaiseOnExited(); + } + return exited; + } + /// + /// + /// Instructs the component to wait + /// indefinitely for the associated process to exit. + /// + /// + public void WaitForExit() + { + WaitForExit(-1); + } + /// + /// + /// Causes the component to wait the + /// specified number of milliseconds for the associated process to enter an + /// idle state. + /// This is only applicable for processes with a user interface, + /// therefore a message loop. + /// + /// + public bool WaitForInputIdle(int milliseconds) + { + SafeProcessHandle handle = null; + bool idle; + try + { + handle = GetProcessHandle(NativeMethods.SYNCHRONIZE | NativeMethods.PROCESS_QUERY_INFORMATION); + int ret = NativeMethods.WaitForInputIdle(handle, milliseconds); + switch (ret) + { + case NativeMethods.WAIT_OBJECT_0: + idle = true; + break; + case NativeMethods.WAIT_TIMEOUT: + idle = false; + break; + case NativeMethods.WAIT_FAILED: + default: + throw new InvalidOperationException("InputIdleUnkownError"); + } + } + finally + { + ReleaseProcessHandle(handle); + } + return idle; + } + /// + /// + /// Instructs the component to wait + /// indefinitely for the associated process to enter an idle state. This + /// is only applicable for processes with a user interface, therefore a message loop. + /// + /// + public bool WaitForInputIdle() + { + return WaitForInputIdle(Int32.MaxValue); + } + + // Support for working asynchronously with streams + /// + /// + /// Instructs the component to start + /// reading the StandardOutput stream asynchronously. The user can register a callback + /// that will be called when a line of data terminated by \n,\r or \r\n is reached, or the end of stream is reached + /// then the remaining information is returned. The user can add an event handler to OutputDataReceived. + /// + /// + [System.Runtime.InteropServices.ComVisible(false)] + public void BeginOutputReadLine() + { + if (outputStreamReadMode == StreamReadMode.undefined) + { + outputStreamReadMode = StreamReadMode.asyncMode; + } + else if (outputStreamReadMode != StreamReadMode.asyncMode) + { + throw new InvalidOperationException("CantMixSyncAsyncOperation"); + } + if (pendingOutputRead) throw new InvalidOperationException("PendingAsyncOperation"); + pendingOutputRead = true; + // We can't detect if there's a pending sychronous read, tream also doesn't. + if (output == null) + { + if (standardOutput == null) + { + throw new InvalidOperationException("CantGetStandardOut"); + } + Stream s = standardOutput.BaseStream; + output = new AsyncStreamReader(this, s, new UserCallBack(this.OutputReadNotifyUser), standardOutput.CurrentEncoding); + } + output.BeginReadLine(); + } + /// + /// + /// Instructs the component to start + /// reading the StandardError stream asynchronously. The user can register a callback + /// that will be called when a line of data terminated by \n,\r or \r\n is reached, or the end of stream is reached + /// then the remaining information is returned. The user can add an event handler to ErrorDataReceived. + /// + /// + [System.Runtime.InteropServices.ComVisible(false)] + public void BeginErrorReadLine() + { + if (errorStreamReadMode == StreamReadMode.undefined) + { + errorStreamReadMode = StreamReadMode.asyncMode; + } + else if (errorStreamReadMode != StreamReadMode.asyncMode) + { + throw new InvalidOperationException("CantMixSyncAsyncOperation"); + } + if (pendingErrorRead) + { + throw new InvalidOperationException("PendingAsyncOperation"); + } + pendingErrorRead = true; + // We can't detect if there's a pending sychronous read, stream also doesn't. + if (error == null) + { + if (standardError == null) + { + throw new InvalidOperationException("CantGetStandardError"); + } + Stream s = standardError.BaseStream; + error = new AsyncStreamReader(this, s, new UserCallBack(this.ErrorReadNotifyUser), standardError.CurrentEncoding); + } + error.BeginReadLine(); + } + /// + /// + /// Instructs the component to cancel the asynchronous operation + /// specified by BeginOutputReadLine(). + /// + /// + [System.Runtime.InteropServices.ComVisible(false)] + public void CancelOutputRead() + { + if (output != null) + { + output.CancelOperation(); + } + else + { + throw new InvalidOperationException("NoAsyncOperation"); + } + pendingOutputRead = false; + } + /// + /// + /// Instructs the component to cancel the asynchronous operation + /// specified by BeginErrorReadLine(). + /// + /// + [System.Runtime.InteropServices.ComVisible(false)] + public void CancelErrorRead() + { + if (error != null) + { + error.CancelOperation(); + } + else + { + throw new InvalidOperationException("No async operation."); + } + pendingErrorRead = false; + } + internal void OutputReadNotifyUser(String data) + { + // To avoid ---- between remove handler and raising the event + DataReceivedEventHandler outputDataReceived = OutputDataReceived; + if (outputDataReceived != null) + { + DataReceivedEventArgs e = new DataReceivedEventArgs(data); + if (SynchronizingObject != null && SynchronizingObject.InvokeRequired) + { + SynchronizingObject.Invoke(outputDataReceived, new object[] + { + this, e + }); + } + else + { + outputDataReceived(this, e); // Call back to user informing data is available. + } + } + } + internal void ErrorReadNotifyUser(String data) + { + // To avoid ---- between remove handler and raising the event + DataReceivedEventHandler errorDataReceived = ErrorDataReceived; + if (errorDataReceived != null) + { + DataReceivedEventArgs e = new DataReceivedEventArgs(data); + if (SynchronizingObject != null && SynchronizingObject.InvokeRequired) + { + SynchronizingObject.Invoke(errorDataReceived, new object[] + { + this, e + }); + } + else + { + errorDataReceived(this, e); // Call back to user informing data is available. + } + } + } + + /// + /// A desired internal state. + /// + /// + enum State + { + HaveId = 0x1, + IsLocal = 0x2, + IsNt = 0x4, + HaveProcessInfo = 0x8, + Exited = 0x10, + Associated = 0x20, + IsWin2k = 0x40, + HaveNtProcessInfo = HaveProcessInfo | IsNt + } + } + + /// + /// This data structure contains information about a process that is collected + /// in bulk by querying the operating system. The reason to make this a separate + /// structure from the process component is so that we can throw it away all at once + /// when Refresh is called on the component. + /// + /// + internal class ProcessInfo + { + public ArrayList threadInfoList = new ArrayList(); + public int basePriority; + public string processName; + public int processId; + public int handleCount; + public long poolPagedBytes; + public long poolNonpagedBytes; + public long virtualBytes; + public long virtualBytesPeak; + public long workingSetPeak; + public long workingSet; + public long pageFileBytesPeak; + public long pageFileBytes; + public long privateBytes; + public int mainModuleId; // used only for win9x - id is only for use with CreateToolHelp32 + public int sessionId; + } + + /// + /// This data structure contains information about a thread in a process that + /// is collected in bulk by querying the operating system. The reason to + /// make this a separate structure from the ProcessThread component is so that we + /// can throw it away all at once when Refresh is called on the component. + /// + /// + internal class ThreadInfo + { + public int threadId; + public int processId; + public int basePriority; + public int currentPriority; + public IntPtr startAddress; + public System.Diagnostics.ThreadState threadState; + public ThreadWaitReason threadWaitReason; + } + + /// + /// This data structure contains information about a module in a process that + /// is collected in bulk by querying the operating system. The reason to + /// make this a separate structure from the ProcessModule component is so that we + /// can throw it away all at once when Refresh is called on the component. + /// + /// + internal class ModuleInfo + { + public string baseName; + public string fileName; + public IntPtr baseOfDll; + public IntPtr entryPoint; + public int sizeOfImage; + public int Id; // used only on win9x - for matching up with ProcessInfo.mainModuleId + } + + internal static class EnvironmentBlock + { + public static byte[] ToByteArray(StringDictionary sd, bool unicode) + { + // get the keys + string[] keys = new string[sd.Count]; + byte[] envBlock = null; + sd.Keys.CopyTo(keys, 0); + + // get the values + string[] values = new string[sd.Count]; + sd.Values.CopyTo(values, 0); + + // sort both by the keys + // Windows 2000 requires the environment block to be sorted by the key + // It will first converting the case the strings and do ordinal comparison. + Array.Sort(keys, values, OrdinalCaseInsensitiveComparer.Default); + + // create a list of null terminated "key=val" strings + StringBuilder stringBuff = new StringBuilder(); + for (int i = 0; i < sd.Count; ++i) + { + stringBuff.Append(keys[i]); + stringBuff.Append('='); + stringBuff.Append(values[i]); + stringBuff.Append('\0'); + } + // an extra null at the end indicates end of list. + stringBuff.Append('\0'); + if (unicode) + { + envBlock = Encoding.Unicode.GetBytes(stringBuff.ToString()); + } + else + { + envBlock = Encoding.Default.GetBytes(stringBuff.ToString()); + if (envBlock.Length > UInt16.MaxValue) throw new InvalidOperationException("Environment block is too long."); + } + return envBlock; + } + } + + internal class OrdinalCaseInsensitiveComparer : IComparer + { + internal static readonly OrdinalCaseInsensitiveComparer Default = new OrdinalCaseInsensitiveComparer(); + public int Compare(Object a, Object b) + { + String sa = a as String; + String sb = b as String; + if (sa != null && sb != null) + { + return String.Compare(sa, sb, StringComparison.OrdinalIgnoreCase); + } + return Comparer.Default.Compare(a, b); + } + } + + internal class ShellExecuteHelper + { + private NativeMethods.ShellExecuteInfo _executeInfo; + private int _errorCode; + private bool _succeeded; + public ShellExecuteHelper(NativeMethods.ShellExecuteInfo executeInfo) + { + _executeInfo = executeInfo; + } + [ResourceExposure(ResourceScope.None)] + [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] + public void ShellExecuteFunction() + { + if (!(_succeeded = NativeMethods.ShellExecuteEx(_executeInfo))) + { + _errorCode = Marshal.GetLastWin32Error(); + } + } + public bool ShellExecuteOnSTAThread() + { + // + // SHELL API ShellExecute() requires STA in order to work correctly. + // If current thread is not a STA thread, we need to call ShellExecute on a new thread. + // + if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA) + { + ThreadStart threadStart = new ThreadStart(this.ShellExecuteFunction); + Thread executionThread = new Thread(threadStart); + executionThread.SetApartmentState(ApartmentState.STA); + executionThread.Start(); + executionThread.Join(); + } + else + { + ShellExecuteFunction(); + } + return _succeeded; + } + public int ErrorCode + { + get + { + return _errorCode; + } + } + } + + private static long[] CachedBuffer; + + private static int GetNewBufferSize(int existingBufferSize, int requiredSize) + { + if (requiredSize == 0) + { + int num = existingBufferSize * 2; + return num >= existingBufferSize ? num : throw new OutOfMemoryException(); + } + int num1 = requiredSize + 10240; + return num1 >= requiredSize ? num1 : throw new OutOfMemoryException(); + } + + internal static ProcessInfo GetProcessInfo(int processId, string machineName) + { + ProcessInfo[] processInfos = GetProcessInfos((Predicate)(pid => pid == processId)); + if (processInfos.Length == 1) return processInfos[0]; + return (ProcessInfo)null; + } + internal static ProcessInfo[] GetProcessInfos(Predicate processIdFilter = null) + { + int returnedSize = 0; + GCHandle gcHandle = new GCHandle(); + int num = 131072; + long[] numArray = Interlocked.Exchange(ref CachedBuffer, (long[])null); + try + { + int error; + do + { + if (numArray == null) numArray = new long[(num + 7) / 8]; + else num = numArray.Length * 8; + gcHandle = GCHandle.Alloc((object)numArray, GCHandleType.Pinned); + error = NativeMethods.NtQuerySystemInformation(5, gcHandle.AddrOfPinnedObject(), num, out returnedSize); + if (error == -1073741820) + { + if (gcHandle.IsAllocated) gcHandle.Free(); + numArray = (long[])null; + num = GetNewBufferSize(num, returnedSize); + } + } while (error == -1073741820); + if (error < 0) throw new InvalidOperationException("CouldntGetProcessInfos", (Exception)new Win32Exception(error)); + return GetProcessInfos(gcHandle.AddrOfPinnedObject(), processIdFilter); + } + finally + { + Interlocked.Exchange(ref CachedBuffer, numArray); + if (gcHandle.IsAllocated) gcHandle.Free(); + } + } + private static ProcessInfo[] GetProcessInfos(IntPtr dataPtr, Predicate processIdFilter) + { + Hashtable hashtable = new Hashtable(60); + long num = 0; + while (true) + { + IntPtr ptr1 = (IntPtr)((long)dataPtr + num); + NativeMethods.SystemProcessInformation structure1 = new NativeMethods.SystemProcessInformation(); + Marshal.PtrToStructure(ptr1, (object)structure1); + int int32 = structure1.UniqueProcessId.ToInt32(); + if (processIdFilter == null || processIdFilter(int32)) + { + ProcessInfo processInfo = new ProcessInfo(); + processInfo.processId = int32; + processInfo.handleCount = (int)structure1.HandleCount; + processInfo.sessionId = (int)structure1.SessionId; + processInfo.poolPagedBytes = (long)(ulong)structure1.QuotaPagedPoolUsage; + processInfo.poolNonpagedBytes = (long)(ulong)structure1.QuotaNonPagedPoolUsage; + processInfo.virtualBytes = (long)(ulong)structure1.VirtualSize; + processInfo.virtualBytesPeak = (long)(ulong)structure1.PeakVirtualSize; + processInfo.workingSetPeak = (long)(ulong)structure1.PeakWorkingSetSize; + processInfo.workingSet = (long)(ulong)structure1.WorkingSetSize; + processInfo.pageFileBytesPeak = (long)(ulong)structure1.PeakPagefileUsage; + processInfo.pageFileBytes = (long)(ulong)structure1.PagefileUsage; + processInfo.privateBytes = (long)(ulong)structure1.PrivatePageCount; + processInfo.basePriority = structure1.BasePriority; + if (structure1.NamePtr == IntPtr.Zero) + { + processInfo.processName = processInfo.processId != 4 ? + (processInfo.processId != 0 ? processInfo.processId.ToString((IFormatProvider)CultureInfo.InvariantCulture) : "Idle") : "System"; + } + else + { + string str = GetProcessShortName(Marshal.PtrToStringUni(structure1.NamePtr, (int)structure1.NameLength / 2)); + processInfo.processName = str; + } + hashtable[(object)processInfo.processId] = (object)processInfo; + IntPtr ptr2 = (IntPtr)((long)ptr1 + (long)Marshal.SizeOf((object)structure1)); + for (int index = 0; (long)index < (long)structure1.NumberOfThreads; ++index) + { + NativeMethods.SystemThreadInformation structure2 = new NativeMethods.SystemThreadInformation(); + Marshal.PtrToStructure(ptr2, (object)structure2); + processInfo.threadInfoList.Add((object)new ThreadInfo() + { + processId = (int)structure2.UniqueProcess, + threadId = (int)structure2.UniqueThread, + basePriority = structure2.BasePriority, + currentPriority = structure2.Priority, + startAddress = structure2.StartAddress, + threadState = (System.Diagnostics.ThreadState)structure2.ThreadState, + threadWaitReason = GetThreadWaitReason((int)structure2.WaitReason) + }); + ptr2 = (IntPtr)((long)ptr2 + (long)Marshal.SizeOf((object)structure2)); + } + } + if (structure1.NextEntryOffset != 0U) num += (long)structure1.NextEntryOffset; + else break; + } + ProcessInfo[] processInfos = new ProcessInfo[hashtable.Values.Count]; + hashtable.Values.CopyTo((Array)processInfos, 0); + return processInfos; + } + + internal static ThreadWaitReason GetThreadWaitReason(int value) + { + switch (value) + { + case 0: + case 7: + return ThreadWaitReason.Executive; + case 1: + case 8: + return ThreadWaitReason.FreePage; + case 2: + case 9: + return ThreadWaitReason.PageIn; + case 3: + case 10: + return ThreadWaitReason.SystemAllocation; + case 4: + case 11: + return ThreadWaitReason.ExecutionDelay; + case 5: + case 12: + return ThreadWaitReason.Suspended; + case 6: + case 13: + return ThreadWaitReason.UserRequest; + case 14: + return ThreadWaitReason.EventPairHigh; + case 15: + return ThreadWaitReason.EventPairLow; + case 16: + return ThreadWaitReason.LpcReceive; + case 17: + return ThreadWaitReason.LpcReply; + case 18: + return ThreadWaitReason.VirtualMemory; + case 19: + return ThreadWaitReason.PageOut; + default: + return ThreadWaitReason.Unknown; + } + } + + internal static string GetProcessShortName(string name) + { + if (string.IsNullOrEmpty(name)) + return string.Empty; + int num1 = -1; + int startIndex1 = -1; + for (int index = 0; index < name.Length; ++index) + { + if (name[index] == '\\') + num1 = index; + else if (name[index] == '.') + startIndex1 = index; + } + int num2 = startIndex1 != -1 ? (!string.Equals(".exe", name.Substring(startIndex1), StringComparison.OrdinalIgnoreCase) ? name.Length - 1 : startIndex1 - 1) : name.Length - 1; + int startIndex2 = num1 != -1 ? num1 + 1 : 0; + return name.Substring(startIndex2, num2 - startIndex2 + 1); + } + + [HostProtection(MayLeakOnAbort = true)] + internal static class NativeMethods + { + public static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); + public const int STARTF_USESTDHANDLES = 0x00000100; + public const int STD_INPUT_HANDLE = -10; + public const int STD_OUTPUT_HANDLE = -11; + public const int STD_ERROR_HANDLE = -12; + public const int STILL_ACTIVE = 0x00000103; + public const int SW_HIDE = 0; + public const int WAIT_OBJECT_0 = 0x00000000; + public const int WAIT_FAILED = unchecked((int)0xFFFFFFFF); + public const int WAIT_TIMEOUT = 0x00000102; + public const int WAIT_ABANDONED = 0x00000080; + public const int ERROR_BAD_EXE_FORMAT = 193; + public const int ERROR_EXE_MACHINE_TYPE_MISMATCH = 216; + + [DllImport("ntdll.dll", CharSet = CharSet.Auto)] + public static extern int NtQuerySystemInformation( + int query, + IntPtr dataPtr, + int size, + out int returnedSize); + + [StructLayout(LayoutKind.Sequential)] + internal class SystemProcessInformation + { + internal uint NextEntryOffset; + internal uint NumberOfThreads; + private long SpareLi1; + private long SpareLi2; + private long SpareLi3; + private long CreateTime; + private long UserTime; + private long KernelTime; + internal ushort NameLength; + internal ushort MaximumNameLength; + internal IntPtr NamePtr; + internal int BasePriority; + internal IntPtr UniqueProcessId; + internal IntPtr InheritedFromUniqueProcessId; + internal uint HandleCount; + internal uint SessionId; + internal UIntPtr PageDirectoryBase; + internal UIntPtr PeakVirtualSize; + internal UIntPtr VirtualSize; + internal uint PageFaultCount; + internal UIntPtr PeakWorkingSetSize; + internal UIntPtr WorkingSetSize; + internal UIntPtr QuotaPeakPagedPoolUsage; + internal UIntPtr QuotaPagedPoolUsage; + internal UIntPtr QuotaPeakNonPagedPoolUsage; + internal UIntPtr QuotaNonPagedPoolUsage; + internal UIntPtr PagefileUsage; + internal UIntPtr PeakPagefileUsage; + internal UIntPtr PrivatePageCount; + private long ReadOperationCount; + private long WriteOperationCount; + private long OtherOperationCount; + private long ReadTransferCount; + private long WriteTransferCount; + private long OtherTransferCount; + } + + [StructLayout(LayoutKind.Sequential)] + internal class SystemThreadInformation + { + private long KernelTime; + private long UserTime; + private long CreateTime; + private uint WaitTime; + internal IntPtr StartAddress; + internal IntPtr UniqueProcess; + internal IntPtr UniqueThread; + internal int Priority; + internal int BasePriority; + internal uint ContextSwitches; + internal uint ThreadState; + internal uint WaitReason; + } + + [StructLayout(LayoutKind.Sequential)] + internal class STARTUPINFO + { + public int cb; + public IntPtr lpReserved = IntPtr.Zero; + public string lpDesktop = null; + public IntPtr lpTitle = IntPtr.Zero; + public int dwX = 0; + public int dwY = 0; + public int dwXSize = 0; + public int dwYSize = 0; + public int dwXCountChars = 0; + public int dwYCountChars = 0; + public int dwFillAttribute = 0; + public int dwFlags; + public short wShowWindow = 0; + public short cbReserved2 = 0; + public IntPtr lpReserved2 = IntPtr.Zero; + public SafeFileHandle hStdInput = new SafeFileHandle(IntPtr.Zero, false); + public SafeFileHandle hStdOutput = new SafeFileHandle(IntPtr.Zero, false); + public SafeFileHandle hStdError = new SafeFileHandle(IntPtr.Zero, false); + public STARTUPINFO() + { + cb = Marshal.SizeOf(this); + } + public void Dispose() + { + // close the handles created for child process + if (hStdInput != null && !hStdInput.IsInvalid) + { + hStdInput.Close(); + hStdInput = null; + } + if (hStdOutput != null && !hStdOutput.IsInvalid) + { + hStdOutput.Close(); + hStdOutput = null; + } + if (hStdError != null && !hStdError.IsInvalid) + { + hStdError.Close(); + hStdError = null; + } + } + } + + [StructLayout(LayoutKind.Sequential)] + internal class SECURITY_ATTRIBUTES + { + public int nLength = 12; + public SafeLocalMemHandle lpSecurityDescriptor = new SafeLocalMemHandle(IntPtr.Zero, false); + public bool bInheritHandle; + } + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [ResourceExposure(ResourceScope.None)] + public static extern bool GetExitCodeProcess(SafeProcessHandle processHandle, out int exitCode); + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [ResourceExposure(ResourceScope.None)] + public static extern bool GetProcessTimes(SafeProcessHandle handle, out long creation, out long exit, out long kernel, out long user); + [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)] + [ResourceExposure(ResourceScope.Process)] + public static extern IntPtr GetStdHandle(int whichHandle); + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [ResourceExposure(ResourceScope.Process)] + public static extern bool CreatePipe(out SafeFileHandle hReadPipe, out SafeFileHandle hWritePipe, SECURITY_ATTRIBUTES lpPipeAttributes, int nSize); + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false)] + [ResourceExposure(ResourceScope.Process)] + public static extern bool CreateProcess([MarshalAs(UnmanagedType.LPTStr)] string lpApplicationName, // LPCTSTR + StringBuilder lpCommandLine, // LPTSTR - note: CreateProcess might insert a null somewhere in this string + SECURITY_ATTRIBUTES lpProcessAttributes, // LPSECURITY_ATTRIBUTES + SECURITY_ATTRIBUTES lpThreadAttributes, // LPSECURITY_ATTRIBUTES + bool bInheritHandles, // BOOL + int dwCreationFlags, // DWORD + IntPtr lpEnvironment, // LPVOID + [MarshalAs(UnmanagedType.LPTStr)] string lpCurrentDirectory, // LPCTSTR + STARTUPINFO lpStartupInfo, // LPSTARTUPINFO + PROCESS_INFORMATION lpProcessInformation // LPPROCESS_INFORMATION + ); + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [ResourceExposure(ResourceScope.Machine)] + public static extern bool TerminateProcess(SafeProcessHandle processHandle, int exitCode); + [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)] + [ResourceExposure(ResourceScope.Process)] + public static extern IntPtr GetCurrentProcess(); + [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false)] + [SuppressUnmanagedCodeSecurityAttribute] + [ResourceExposure(ResourceScope.Machine)] + public static extern bool CreateProcessAsUser(Win32.TokensEx.SafeTokenHandle hToken, string lpApplicationName, StringBuilder lpCommandLine, SECURITY_ATTRIBUTES lpProcessAttributes, SECURITY_ATTRIBUTES lpThreadAttributes, + bool bInheritHandles, int dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, STARTUPINFO lpStartupInfo, PROCESS_INFORMATION lpProcessInformation); + + [DllImport("advapi32", SetLastError = true, CharSet = CharSet.Auto)] + internal static extern bool CreateProcessWithToken( + Win32.TokensEx.SafeTokenHandle hToken, + LogonFlags dwLogonFlags, + string lpApplicationName, + StringBuilder lpCommandLine, + int dwCreationFlags, + IntPtr lpEnvironment, + string lpCurrentDirectory, + STARTUPINFO lpStartupInfo, + PROCESS_INFORMATION lpProcessInformation); + + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true, BestFitMapping = false)] + [ResourceExposure(ResourceScope.Machine)] + internal static extern bool CreateProcessWithLogonW(string userName, string domain, IntPtr password, LogonFlags logonFlags, [MarshalAs(UnmanagedType.LPTStr)] string appName, + StringBuilder cmdLine, int creationFlags, IntPtr environmentBlock, [MarshalAs(UnmanagedType.LPTStr)] string lpCurrentDirectory, // LPCTSTR + STARTUPINFO lpStartupInfo, PROCESS_INFORMATION lpProcessInformation); + + //TODO: TOKEN + + [StructLayout(LayoutKind.Sequential)] + internal class PROCESS_INFORMATION + { + public IntPtr hProcess = IntPtr.Zero; + public IntPtr hThread = IntPtr.Zero; + public int dwProcessId = 0; + public int dwThreadId = 0; + } + + [Flags] + internal enum LogonFlags + { + LOGON_WITH_PROFILE = 0x00000001, + LOGON_NETCREDENTIALS_ONLY = 0x00000002 + } + + public const int QS_KEY = 0x0001, + QS_MOUSEMOVE = 0x0002, + QS_MOUSEBUTTON = 0x0004, + QS_POSTMESSAGE = 0x0008, + QS_TIMER = 0x0010, + QS_PAINT = 0x0020, + QS_SENDMESSAGE = 0x0040, + QS_HOTKEY = 0x0080, + QS_ALLPOSTMESSAGE = 0x0100, + QS_MOUSE = QS_MOUSEMOVE | QS_MOUSEBUTTON, + QS_INPUT = QS_MOUSE | QS_KEY, + QS_ALLEVENTS = QS_INPUT | QS_POSTMESSAGE | QS_TIMER | QS_PAINT | QS_HOTKEY, + QS_ALLINPUT = QS_INPUT | QS_POSTMESSAGE | QS_TIMER | QS_PAINT | QS_HOTKEY | QS_SENDMESSAGE; + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [ResourceExposure(ResourceScope.None)] + public static extern int WaitForInputIdle(SafeProcessHandle handle, int milliseconds); + [DllImport("shell32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [ResourceExposure(ResourceScope.Machine)] + public static extern bool ShellExecuteEx(ShellExecuteInfo info); + [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true, BestFitMapping = false)] + [ResourceExposure(ResourceScope.Machine)] + public static extern bool DuplicateHandle(HandleRef hSourceProcessHandle, SafeHandle hSourceHandle, HandleRef hTargetProcess, out SafeFileHandle targetHandle, int dwDesiredAccess, + bool bInheritHandle, int dwOptions); + [DllImport("kernel32.dll", CharSet = System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true, BestFitMapping = false)] + [ResourceExposure(ResourceScope.Machine)] + public static extern bool DuplicateHandle(HandleRef hSourceProcessHandle, SafeHandle hSourceHandle, HandleRef hTargetProcess, out SafeWaitHandle targetHandle, int dwDesiredAccess, + bool bInheritHandle, int dwOptions); + [DllImport("user32.dll", CharSet = CharSet.Auto, BestFitMapping = true)] + [ResourceExposure(ResourceScope.None)] + public static extern int GetWindowText(HandleRef hWnd, StringBuilder lpString, int nMaxCount); + [DllImport("user32.dll", CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern int GetWindowTextLength(HandleRef hWnd); + [DllImport("user32.dll", CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern IntPtr SendMessageTimeout(HandleRef hWnd, int msg, IntPtr wParam, IntPtr lParam, int flags, int timeout, out IntPtr pdwResult); + [DllImport("user32.dll", CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern int GetWindowLong(HandleRef hWnd, int nIndex); + [DllImport("user32.dll", CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern int PostMessage(HandleRef hwnd, int msg, IntPtr wparam, IntPtr lparam); + + [StructLayout(LayoutKind.Sequential)] + internal class ShellExecuteInfo + { + public int cbSize; + public int fMask; + public IntPtr hwnd = (IntPtr)0; + public IntPtr lpVerb = (IntPtr)0; + public IntPtr lpFile = (IntPtr)0; + public IntPtr lpParameters = (IntPtr)0; + public IntPtr lpDirectory = (IntPtr)0; + public int nShow; + public IntPtr hInstApp = (IntPtr)0; + public IntPtr lpIDList = (IntPtr)0; + public IntPtr lpClass = (IntPtr)0; + public IntPtr hkeyClass = (IntPtr)0; + public int dwHotKey = 0; + public IntPtr hIcon = (IntPtr)0; + public IntPtr hProcess = (IntPtr)0; + [ResourceExposure(ResourceScope.Machine)] + public ShellExecuteInfo() + { + cbSize = Marshal.SizeOf(this); + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct LUID + { + public int LowPart; + public int HighPart; + } + + public const int SEE_MASK_NOCLOSEPROCESS = 0x00000040; + public const int SEE_MASK_CONNECTNETDRV = 0x00000080; + public const int SEE_MASK_FLAG_DDEWAIT = 0x00000100; + public const int SEE_MASK_DOENVSUBST = 0x00000200; + public const int SEE_MASK_FLAG_NO_UI = 0x00000400; + public const int PROCESS_TERMINATE = 0x0001; + public const int PROCESS_QUERY_INFORMATION = 0x0400; + public const int PROCESS_QUERY_LIMITED_INFORMATION = 0x1000; + public const int STANDARD_RIGHTS_REQUIRED = 0x000F0000; + public const int SYNCHRONIZE = 0x00100000; + public const int PROCESS_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFF; + public const int READ_CONTROL = 0x00020000; + public const int STANDARD_RIGHTS_READ = READ_CONTROL; + public const int KEY_QUERY_VALUE = 0x0001; + public const int KEY_ENUMERATE_SUB_KEYS = 0x0008; + public const int KEY_NOTIFY = 0x0010; + public const int ERROR_BROKEN_PIPE = 109; + public const int ERROR_NO_DATA = 232; + public const int ERROR_HANDLE_EOF = 38; + public const int ERROR_IO_INCOMPLETE = 996; + public const int ERROR_IO_PENDING = 997; + public const int ERROR_FILE_EXISTS = 0x50; + public const int ERROR_FILENAME_EXCED_RANGE = 0xCE; // filename too long. + public const int ERROR_MORE_DATA = 234; + public const int ERROR_CANCELLED = 1223; + public const int ERROR_FILE_NOT_FOUND = 2; + public const int ERROR_PATH_NOT_FOUND = 3; + public const int ERROR_ACCESS_DENIED = 5; + public const int ERROR_INVALID_HANDLE = 6; + public const int ERROR_NOT_ENOUGH_MEMORY = 8; + public const int ERROR_BAD_COMMAND = 22; + public const int ERROR_SHARING_VIOLATION = 32; + public const int ERROR_OPERATION_ABORTED = 995; + public const int ERROR_NO_ASSOCIATION = 1155; + public const int ERROR_DLL_NOT_FOUND = 1157; + public const int ERROR_DDE_FAIL = 1156; + public const int ERROR_INVALID_PARAMETER = 87; + public const int ERROR_PARTIAL_COPY = 299; + public const int ERROR_SUCCESS = 0; + public const int ERROR_ALREADY_EXISTS = 183; + public const int ERROR_COUNTER_TIMEOUT = 1121; + public const int DUPLICATE_CLOSE_SOURCE = 1; + public const int DUPLICATE_SAME_ACCESS = 2; + public const int SE_ERR_FNF = 2; + public const int SE_ERR_PNF = 3; + public const int SE_ERR_ACCESSDENIED = 5; + public const int SE_ERR_OOM = 8; + public const int SE_ERR_DLLNOTFOUND = 32; + public const int SE_ERR_SHARE = 26; + public const int SE_ERR_ASSOCINCOMPLETE = 27; + public const int SE_ERR_DDETIMEOUT = 28; + public const int SE_ERR_DDEFAIL = 29; + public const int SE_ERR_DDEBUSY = 30; + public const int SE_ERR_NOASSOC = 31; + public const int CREATE_NO_WINDOW = 0x08000000; + public const int CREATE_SUSPENDED = 0x00000004; + public const int CREATE_UNICODE_ENVIRONMENT = 0x00000400; + public const int SMTO_ABORTIFHUNG = 0x0002; + public const int GWL_STYLE = -16; + public const int GCL_WNDPROC = -24; + public const int GWL_WNDPROC = -4; + public const int WS_DISABLED = 0x08000000; + public const int WM_NULL = 0x0000; + public const int WM_CLOSE = 0x0010; + public const int SW_SHOWNORMAL = 1; + public const int SW_NORMAL = 1; + public const int SW_SHOWMINIMIZED = 2; + public const int SW_SHOWMAXIMIZED = 3; + public const int SW_MAXIMIZE = 3; + public const int SW_SHOWNOACTIVATE = 4; + public const int SW_SHOW = 5; + public const int SW_MINIMIZE = 6; + public const int SW_SHOWMINNOACTIVE = 7; + public const int SW_SHOWNA = 8; + public const int SW_RESTORE = 9; + public const int SW_SHOWDEFAULT = 10; + public const int SW_MAX = 10; + public const int GW_OWNER = 4; + public const int WHITENESS = 0x00FF0062; + } + + internal delegate void UserCallBack(String data); + + internal class AsyncStreamReader : IDisposable + { + internal const int DefaultBufferSize = 1024; // Byte buffer size + private const int MinBufferSize = 128; + private Stream stream; + private Encoding encoding; + private Decoder decoder; + private byte[] byteBuffer; + private char[] charBuffer; + // Record the number of valid bytes in the byteBuffer, for a few checks. + + // This is the maximum number of chars we can get from one call to + // ReadBuffer. Used so ReadBuffer can tell when to copy data into + // a user's char[] directly, instead of our internal char[]. + private int _maxCharsPerBuffer; + + // Store a backpointer to the process class, to check for user callbacks + private Process process; + + // Delegate to call user function. + private UserCallBack userCallBack; + + // Internal Cancel operation + private bool cancelOperation; + private ManualResetEvent eofEvent; + private Queue messageQueue; + private StringBuilder sb; + private bool bLastCarriageReturn; + + // Cache the last position scanned in sb when searching for lines. + private int currentLinePos; + internal AsyncStreamReader(Process process, Stream stream, UserCallBack callback, Encoding encoding) : this(process, stream, callback, encoding, DefaultBufferSize) { } + + // Creates a new AsyncStreamReader for the given stream. The + // character encoding is set by encoding and the buffer size, + // in number of 16-bit characters, is set by bufferSize. + // + internal AsyncStreamReader(Process process, Stream stream, UserCallBack callback, Encoding encoding, int bufferSize) + { + Init(process, stream, callback, encoding, bufferSize); + messageQueue = new Queue(); + } + private void Init(Process process, Stream stream, UserCallBack callback, Encoding encoding, int bufferSize) + { + this.process = process; + this.stream = stream; + this.encoding = encoding; + this.userCallBack = callback; + decoder = encoding.GetDecoder(); + if (bufferSize < MinBufferSize) bufferSize = MinBufferSize; + byteBuffer = new byte[bufferSize]; + _maxCharsPerBuffer = encoding.GetMaxCharCount(bufferSize); + charBuffer = new char[_maxCharsPerBuffer]; + cancelOperation = false; + eofEvent = new ManualResetEvent(false); + sb = null; + this.bLastCarriageReturn = false; + } + public virtual void Close() + { + Dispose(true); + } + void IDisposable.Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + if (stream != null) stream.Close(); + } + if (stream != null) + { + stream = null; + encoding = null; + decoder = null; + byteBuffer = null; + charBuffer = null; + } + if (eofEvent != null) + { + eofEvent.Close(); + eofEvent = null; + } + } + public virtual Encoding CurrentEncoding + { + get + { + return encoding; + } + } + public virtual Stream BaseStream + { + get + { + return stream; + } + } + + // User calls BeginRead to start the asynchronous read + internal void BeginReadLine() + { + if (cancelOperation) + { + cancelOperation = false; + } + if (sb == null) + { + sb = new StringBuilder(DefaultBufferSize); + stream.BeginRead(byteBuffer, 0, byteBuffer.Length, new AsyncCallback(ReadBuffer), null); + } + else + { + FlushMessageQueue(); + } + } + internal void CancelOperation() + { + cancelOperation = true; + } + + // This is the async callback function. Only one thread could/should call this. + private void ReadBuffer(IAsyncResult ar) + { + int byteLen; + try + { + byteLen = stream.EndRead(ar); + } + catch (IOException) + { + // We should ideally consume errors from operations getting cancelled + // so that we don't crash the unsuspecting parent with an unhandled exc. + // This seems to come in 2 forms of exceptions (depending on platform and scenario), + // namely OperationCanceledException and IOException (for errorcode that we don't + // map explicitly). + byteLen = 0; // Treat this as EOF + } + catch (OperationCanceledException) + { + // We should consume any OperationCanceledException from child read here + // so that we don't crash the parent with an unhandled exc + byteLen = 0; // Treat this as EOF + } + if (byteLen == 0) + { + // We're at EOF, we won't call this function again from here on. + lock (messageQueue) + { + if (sb.Length != 0) + { + messageQueue.Enqueue(sb.ToString()); + sb.Length = 0; + } + messageQueue.Enqueue(null); + } + try + { + // UserCallback could throw, we should still set the eofEvent + FlushMessageQueue(); + } + finally + { + eofEvent.Set(); + } + } + else + { + int charLen = decoder.GetChars(byteBuffer, 0, byteLen, charBuffer, 0); + sb.Append(charBuffer, 0, charLen); + GetLinesFromStringBuilder(); + stream.BeginRead(byteBuffer, 0, byteBuffer.Length, new AsyncCallback(ReadBuffer), null); + } + } + + // Read lines stored in StringBuilder and the buffer we just read into. + // A line is defined as a sequence of characters followed by + // a carriage return ('\r'), a line feed ('\n'), or a carriage return + // immediately followed by a line feed. The resulting string does not + // contain the terminating carriage return and/or line feed. The returned + // value is null if the end of the input stream has been reached. + // + private void GetLinesFromStringBuilder() + { + int currentIndex = currentLinePos; + int lineStart = 0; + int len = sb.Length; + + // skip a beginning '\n' character of new block if last block ended + // with '\r' + if (bLastCarriageReturn && (len > 0) && sb[0] == '\n') + { + currentIndex = 1; + lineStart = 1; + bLastCarriageReturn = false; + } + while (currentIndex < len) + { + char ch = sb[currentIndex]; + // Note the following common line feed chars: + // \n - UNIX \r\n - DOS \r - Mac + if (ch == '\r' || ch == '\n') + { + string s = sb.ToString(lineStart, currentIndex - lineStart); + lineStart = currentIndex + 1; + // skip the "\n" character following "\r" character + if ((ch == '\r') && (lineStart < len) && (sb[lineStart] == '\n')) + { + lineStart++; + currentIndex++; + } + lock (messageQueue) + { + messageQueue.Enqueue(s); + } + } + currentIndex++; + } + // Protect length as IndexOutOfRangeException was being thrown when less than a + // character's worth of bytes was read at the beginning of a line. + if (len > 0 && sb[len - 1] == '\r') + { + bLastCarriageReturn = true; + } + // Keep the rest characaters which can't form a new line in string builder. + if (lineStart < len) + { + if (lineStart == 0) + { + // we found no breaklines, in this case we cache the position + // so next time we don't have to restart from the beginning + currentLinePos = currentIndex; + } + else + { + sb.Remove(0, lineStart); + currentLinePos = 0; + } + } + else + { + sb.Length = 0; + currentLinePos = 0; + } + FlushMessageQueue(); + } + private void FlushMessageQueue() + { + while (true) + { + // When we call BeginReadLine, we also need to flush the queue + // So there could be a ---- between the ReadBuffer and BeginReadLine + // We need to take lock before DeQueue. + if (messageQueue.Count > 0) + { + lock (messageQueue) + { + if (messageQueue.Count > 0) + { + string s = (string)messageQueue.Dequeue(); + // skip if the read is the read is cancelled + // this might happen inside UserCallBack + // However, continue to drain the queue + if (!cancelOperation) + { + userCallBack(s); + } + } + } + } + else + { + break; + } + } + } + + // Wait until we hit EOF. This is called from Process.WaitForExit + // We will lose some information if we don't do this. + internal void WaitUtilEOF() + { + if (eofEvent != null) + { + eofEvent.WaitOne(); + eofEvent.Close(); + eofEvent = null; + } + } + } + + public delegate void DataReceivedEventHandler(Object sender, DataReceivedEventArgs e); + + public class DataReceivedEventArgs : EventArgs + { + internal string _data; + internal DataReceivedEventArgs(string data) => this._data = data; + public string Data => this._data; + } + + internal class ProcessWaitHandle : WaitHandle + { + [ResourceExposure(ResourceScope.None)] + [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] + internal ProcessWaitHandle(SafeProcessHandle processHandle) : base() + { + SafeWaitHandle waitHandle = null; + bool succeeded = NativeMethods.DuplicateHandle(new HandleRef(this, NativeMethods.GetCurrentProcess()), processHandle, new HandleRef(this, NativeMethods.GetCurrentProcess()), out waitHandle, + 0, false, NativeMethods.DUPLICATE_SAME_ACCESS); + if (!succeeded) + { + Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); + } + this.SafeWaitHandle = waitHandle; + } + } + + [SuppressUnmanagedCodeSecurityAttribute] + internal sealed class SafeThreadHandle : SafeHandleZeroOrMinusOneIsInvalid + { + internal SafeThreadHandle() : base(true) { } + internal void InitialSetHandle(IntPtr h) + { + Debug.Assert(base.IsInvalid, "Safe handle should only be set once"); + base.SetHandle(h); + } + override protected bool ReleaseHandle() + { + return CloseHandle(handle); + } + [DllImport("kernel32.dll", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Auto, SetLastError = true)] + public static extern bool CloseHandle(IntPtr handle); + } + + [HostProtectionAttribute(MayLeakOnAbort = true)] + [SuppressUnmanagedCodeSecurityAttribute] + internal sealed class SafeLocalMemHandle : SafeHandleZeroOrMinusOneIsInvalid + { + [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] + internal SafeLocalMemHandle(IntPtr existingHandle, bool ownsHandle) : base(ownsHandle) + { + SetHandle(existingHandle); + } + [DllImport("kernel32.dll")] + [ResourceExposure(ResourceScope.None)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + private static extern IntPtr LocalFree(IntPtr hMem); + override protected bool ReleaseHandle() + { + return LocalFree(handle) == IntPtr.Zero; + } + } + + [SuppressUnmanagedCodeSecurityAttribute] + public sealed class SafeProcessHandle : SafeHandleZeroOrMinusOneIsInvalid + { + internal static SafeProcessHandle InvalidHandle = new SafeProcessHandle(IntPtr.Zero); + + // Note that OpenProcess returns 0 on failure + internal SafeProcessHandle() : base(true) { } + internal SafeProcessHandle(IntPtr handle) : base(true) + { + SetHandle(handle); + } + [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] + public SafeProcessHandle(IntPtr existingHandle, bool ownsHandle) : base(ownsHandle) + { + SetHandle(existingHandle); + } + [DllImport("kernel32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto, SetLastError = true)] + [ResourceExposure(ResourceScope.Machine)] + internal static extern SafeProcessHandle OpenProcess(int access, bool inherit, int processId); + internal void InitialSetHandle(IntPtr h) + { + Debug.Assert(base.IsInvalid, "Safe handle should only be set once"); + base.handle = h; + } + override protected bool ReleaseHandle() + { + return CloseHandle(handle); + } + [DllImport("kernel32.dll", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Auto, SetLastError = true)] + public static extern bool CloseHandle(IntPtr handle); + } + + [TypeConverter(typeof(ExpandableObjectConverter)), + // Disabling partial trust scenarios + PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust"), HostProtection(SharedState = true, SelfAffectingProcessMgmt = true)] + public sealed class ProcessStartInfo + { + string fileName; + string arguments; + string directory; + string verb; + ProcessWindowStyle windowStyle; + bool errorDialog; + IntPtr errorDialogParentHandle; + bool useShellExecute = false; + string userName; + string domain; + SecureString password; + string passwordInClearText; + bool loadUserProfile; + bool redirectStandardInput = false; + bool redirectStandardOutput = false; + bool redirectStandardError = false; + Encoding standardOutputEncoding; + Encoding standardErrorEncoding; + bool createNoWindow = false; + WeakReference weakParentProcess; + internal StringDictionary environmentVariables; + /// + /// Default constructor. At least the + /// property must be set before starting the process. + /// + public ProcessStartInfo() { } + internal ProcessStartInfo(Process parent) + { + this.weakParentProcess = new WeakReference(parent); + } + /// + /// Specifies the name of the application or document that is to be started. + /// + [ResourceExposure(ResourceScope.Machine)] + public ProcessStartInfo(string fileName) + { + this.fileName = fileName; + } + /// + /// Specifies the name of the application that is to be started, as well as a set + /// of command line arguments to pass to the application. + /// + [ResourceExposure(ResourceScope.Machine)] + public ProcessStartInfo(string fileName, string arguments) + { + this.fileName = fileName; + this.arguments = arguments; + } + /// + /// + /// Specifies the verb to use when opening the filename. For example, the "print" + /// verb will print a document specified using . + /// Each file extension has it's own set of verbs, which can be obtained using the + /// property. + /// The default verb can be specified using "". + /// + /// + /// Discuss 'opening' vs. 'starting.' I think the part about the + /// default verb was a dev comment. + /// Find out what + /// that means. + /// + /// + public string Verb + { + get + { + if (verb == null) return string.Empty; + return verb; + } + set + { + verb = value; + } + } + public string Arguments + { + get + { + if (arguments == null) return string.Empty; + return arguments; + } + set + { + arguments = value; + } + } + public bool CreateNoWindow + { + get + { + return createNoWindow; + } + set + { + createNoWindow = value; + } + } + public StringDictionary EnvironmentVariables + { + [ResourceExposure(ResourceScope.Machine)] + [ResourceConsumption(ResourceScope.Machine)] + get + { + // Note: + // Creating a detached ProcessStartInfo will pre-populate the environment + // with current environmental variables. + + // When used with an existing Process.ProcessStartInfo the following behavior + // * Desktop - Populates with current Environment (rather than that of the process) + if (environmentVariables == null) + { + environmentVariables = new StringDictionaryWithComparer(); + + // if not in design mode, initialize the child environment block with all the parent variables + if (!(this.weakParentProcess != null && this.weakParentProcess.IsAlive && ((Component)this.weakParentProcess.Target).Site != null && + ((Component)this.weakParentProcess.Target).Site.DesignMode)) + { + foreach (DictionaryEntry entry in System.Environment.GetEnvironmentVariables()) environmentVariables.Add((string)entry.Key, (string)entry.Value); + } + } + return environmentVariables; + } + } + private IDictionary environment; + public IDictionary Environment + { + get + { + if (environment == null) + { + environment = this.EnvironmentVariables.AsGenericDictionary(); + } + return environment; + } + } + public bool RedirectStandardInput + { + get + { + return redirectStandardInput; + } + set + { + redirectStandardInput = value; + } + } + public bool RedirectStandardOutput + { + get + { + return redirectStandardOutput; + } + set + { + redirectStandardOutput = value; + } + } + public bool RedirectStandardError + { + get + { + return redirectStandardError; + } + set + { + redirectStandardError = value; + } + } + public Encoding StandardErrorEncoding + { + get + { + return standardErrorEncoding; + } + set + { + standardErrorEncoding = value; + } + } + public Encoding StandardOutputEncoding + { + get + { + return standardOutputEncoding; + } + set + { + standardOutputEncoding = value; + } + } + public bool UseShellExecute + { + get + { + return useShellExecute; + } + set + { + useShellExecute = value; + } + } + /// + /// Returns the set of verbs associated with the file specified by the + /// property. + /// + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public string[] Verbs + { + [ResourceExposure(ResourceScope.None)] + [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] + get + { + ArrayList verbs = new ArrayList(); + RegistryKey key = null; + string extension = Path.GetExtension(FileName); + try + { + if (extension != null && extension.Length > 0) + { + key = Registry.ClassesRoot.OpenSubKey(extension); + if (key != null) + { + string value = (string)key.GetValue(String.Empty); + key.Close(); + key = Registry.ClassesRoot.OpenSubKey(value + "\\shell"); + if (key != null) + { + string[] names = key.GetSubKeyNames(); + for (int i = 0; i < names.Length; i++) + if (string.Compare(names[i], "new", StringComparison.OrdinalIgnoreCase) != 0) + verbs.Add(names[i]); + key.Close(); + key = null; + } + } + } + } + finally + { + if (key != null) key.Close(); + } + string[] temp = new string[verbs.Count]; + verbs.CopyTo(temp, 0); + return temp; + } + } + public string UserName + { + get + { + if (userName == null) + { + return string.Empty; + } + else + { + return userName; + } + } + set + { + userName = value; + } + } + public SecureString Password + { + get + { + return password; + } + set + { + password = value; + } + } + public string PasswordInClearText + { + get + { + return passwordInClearText; + } + set + { + passwordInClearText = value; + } + } + public string Domain + { + get + { + if (domain == null) + { + return string.Empty; + } + else + { + return domain; + } + } + set + { + domain = value; + } + } + public bool LoadUserProfile + { + get + { + return loadUserProfile; + } + set + { + loadUserProfile = value; + } + } + public string FileName + { + [ResourceExposure(ResourceScope.Machine)] + get + { + if (fileName == null) return string.Empty; + return fileName; + } + [ResourceExposure(ResourceScope.Machine)] + set + { + fileName = value; + } + } + public string WorkingDirectory + { + [ResourceExposure(ResourceScope.Machine)] + get + { + if (directory == null) return string.Empty; + return directory; + } + [ResourceExposure(ResourceScope.Machine)] + set + { + directory = value; + } + } + public bool ErrorDialog + { + get + { + return errorDialog; + } + set + { + errorDialog = value; + } + } + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public IntPtr ErrorDialogParentHandle + { + get + { + return errorDialogParentHandle; + } + set + { + errorDialogParentHandle = value; + } + } + public ProcessWindowStyle WindowStyle + { + get + { + return windowStyle; + } + set + { + if (!Enum.IsDefined(typeof(ProcessWindowStyle), value)) throw new InvalidEnumArgumentException("value", (int)value, typeof(ProcessWindowStyle)); + windowStyle = value; + } + } + } + + [Serializable] + internal class StringDictionaryWithComparer : StringDictionary + { + public StringDictionaryWithComparer() : this((IEqualityComparer)StringComparer.OrdinalIgnoreCase) { } + public StringDictionaryWithComparer(IEqualityComparer comparer) => this.ReplaceHashtable(new Hashtable(comparer)); + public override string this[string key] + { + get => key != null ? (string)this.contents[(object)key] : throw new ArgumentNullException(nameof(key)); + set + { + if (key == null) throw new ArgumentNullException(nameof(key)); + this.contents[(object)key] = (object)value; + } + } + public override void Add(string key, string value) + { + if (key == null) throw new ArgumentNullException(nameof(key)); + this.contents.Add((object)key, (object)value); + } + public override bool ContainsKey(string key) => key != null ? this.contents.ContainsKey((object)key) : throw new ArgumentNullException(nameof(key)); + public override void Remove(string key) + { + if (key == null) throw new ArgumentNullException(nameof(key)); + this.contents.Remove((object)key); + } + } + + [Serializable] + public class StringDictionary : IEnumerable + { + internal Hashtable contents = new Hashtable(); + /// Gets the number of key/value pairs in the . + /// The number of key/value pairs in the . + /// Retrieving the value of this property is an O(1) operation. + public virtual int Count => this.contents.Count; + /// Gets a value indicating whether access to the is synchronized (thread safe). + /// + /// if access to the is synchronized (thread safe); otherwise, . + public virtual bool IsSynchronized => this.contents.IsSynchronized; + /// Gets or sets the value associated with the specified key. + /// The key whose value to get or set. + /// The value associated with the specified key. If the specified key is not found, Get returns , and Set creates a new entry with the specified key. + /// + /// is . + public virtual string this[string key] + { + get + { + if (key == null) throw new ArgumentNullException(nameof(key)); + return (string)this.contents[(object)key.ToLower(CultureInfo.InvariantCulture)]; + } + set + { + if (key == null) throw new ArgumentNullException(nameof(key)); + this.contents[(object)key.ToLower(CultureInfo.InvariantCulture)] = (object)value; + } + } + /// Gets a collection of keys in the . + /// An that provides the keys in the . + public virtual ICollection Keys => this.contents.Keys; + /// Gets an object that can be used to synchronize access to the . + /// An that can be used to synchronize access to the . + public virtual object SyncRoot => this.contents.SyncRoot; + /// Gets a collection of values in the . + /// An that provides the values in the . + public virtual ICollection Values => this.contents.Values; + /// Adds an entry with the specified key and value into the . + /// The key of the entry to add. + /// The value of the entry to add. The value can be . + /// + /// is . + /// An entry with the same key already exists in the . + /// The is read-only. + public virtual void Add(string key, string value) + { + if (key == null) throw new ArgumentNullException(nameof(key)); + this.contents.Add((object)key.ToLower(CultureInfo.InvariantCulture), (object)value); + } + /// Removes all entries from the . + /// The is read-only. + public virtual void Clear() => this.contents.Clear(); + /// Determines if the contains a specific key. + /// The key to locate in the . + /// + /// if the contains an entry with the specified key; otherwise, . + /// The key is . + public virtual bool ContainsKey(string key) + { + if (key == null) throw new ArgumentNullException(nameof(key)); + return this.contents.ContainsKey((object)key.ToLower(CultureInfo.InvariantCulture)); + } + /// Determines if the contains a specific value. + /// The value to locate in the . The value can be . + /// + /// if the contains an element with the specified value; otherwise, . + public virtual bool ContainsValue(string value) => this.contents.ContainsValue((object)value); + /// Copies the string dictionary values to a one-dimensional instance at the specified index. + /// The one-dimensional that is the destination of the values copied from the . + /// The index in the array where copying begins. + /// + /// is multidimensional. + /// -or- + /// The number of elements in the is greater than the available space from to the end of . + /// + /// is . + /// + /// is less than the lower bound of . + public virtual void CopyTo(Array array, int index) => this.contents.CopyTo(array, index); + /// Returns an enumerator that iterates through the string dictionary. + /// An that iterates through the string dictionary. + public virtual IEnumerator GetEnumerator() => (IEnumerator)this.contents.GetEnumerator(); + /// Removes the entry with the specified key from the string dictionary. + /// The key of the entry to remove. + /// The key is . + /// The is read-only. + public virtual void Remove(string key) + { + if (key == null) throw new ArgumentNullException(nameof(key)); + this.contents.Remove((object)key.ToLower(CultureInfo.InvariantCulture)); + } + internal void ReplaceHashtable(Hashtable useThisHashtableInstead) => this.contents = useThisHashtableInstead; + internal IDictionary AsGenericDictionary() => (IDictionary)new StringDictionary.GenericAdapter(this); + + private class GenericAdapter : IDictionary, ICollection>, IEnumerable>, IEnumerable + { + private StringDictionary m_stringDictionary; + private StringDictionary.GenericAdapter.ICollectionToGenericCollectionAdapter _values; + private StringDictionary.GenericAdapter.ICollectionToGenericCollectionAdapter _keys; + internal GenericAdapter(StringDictionary stringDictionary) => this.m_stringDictionary = stringDictionary; + public void Add(string key, string value) => this[key] = value; + public bool ContainsKey(string key) => this.m_stringDictionary.ContainsKey(key); + public void Clear() => this.m_stringDictionary.Clear(); + public int Count => this.m_stringDictionary.Count; + public string this[string key] + { + get + { + if (key == null) throw new ArgumentNullException(nameof(key)); + return this.m_stringDictionary.ContainsKey(key) ? this.m_stringDictionary[key] : throw new KeyNotFoundException(); + } + set + { + if (key == null) throw new ArgumentNullException(nameof(key)); + this.m_stringDictionary[key] = value; + } + } + public ICollection Keys + { + get + { + if (this._keys == null) + this._keys = new StringDictionary.GenericAdapter.ICollectionToGenericCollectionAdapter(this.m_stringDictionary, StringDictionary.GenericAdapter.KeyOrValue.Key); + return (ICollection)this._keys; + } + } + public ICollection Values + { + get + { + if (this._values == null) + this._values = new StringDictionary.GenericAdapter.ICollectionToGenericCollectionAdapter(this.m_stringDictionary, StringDictionary.GenericAdapter.KeyOrValue.Value); + return (ICollection)this._values; + } + } + public bool Remove(string key) + { + if (!this.m_stringDictionary.ContainsKey(key)) return false; + this.m_stringDictionary.Remove(key); + return true; + } + public bool TryGetValue(string key, out string value) + { + if (!this.m_stringDictionary.ContainsKey(key)) + { + value = (string)null; + return false; + } + value = this.m_stringDictionary[key]; + return true; + } + void ICollection>.Add(KeyValuePair item) => this.m_stringDictionary.Add(item.Key, item.Value); + bool ICollection>.Contains(KeyValuePair item) + { + string str; + return this.TryGetValue(item.Key, out str) && str.Equals(item.Value); + } + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + { + if (array == null) throw new ArgumentNullException(nameof(array), "ArgumentNull_Array"); + if (arrayIndex < 0) throw new ArgumentOutOfRangeException(nameof(arrayIndex), "ArgumentOutOfRange_NeedNonNegNum"); + if (array.Length - arrayIndex < this.Count) throw new ArgumentException("Arg_ArrayPlusOffTooSmall"); + int num = arrayIndex; + foreach (DictionaryEntry dictionaryEntry in this.m_stringDictionary) array[num++] = new KeyValuePair((string)dictionaryEntry.Key, (string)dictionaryEntry.Value); + } + bool ICollection>.IsReadOnly => false; + bool ICollection>.Remove(KeyValuePair item) + { + if (!((ICollection>)this).Contains(item)) return false; + this.m_stringDictionary.Remove(item.Key); + return true; + } + IEnumerator IEnumerable.GetEnumerator() => (IEnumerator)this.GetEnumerator(); + public IEnumerator> GetEnumerator() + { + foreach (DictionaryEntry dictionaryEntry in this.m_stringDictionary) yield return new KeyValuePair((string)dictionaryEntry.Key, (string)dictionaryEntry.Value); + } + + internal enum KeyOrValue + { + Key, + Value, + } + + private class ICollectionToGenericCollectionAdapter : ICollection, IEnumerable, IEnumerable + { + private StringDictionary _internal; + private StringDictionary.GenericAdapter.KeyOrValue _keyOrValue; + public ICollectionToGenericCollectionAdapter(StringDictionary source, StringDictionary.GenericAdapter.KeyOrValue keyOrValue) + { + this._internal = source != null ? source : throw new ArgumentNullException(nameof(source)); + this._keyOrValue = keyOrValue; + } + public void Add(string item) => this.ThrowNotSupportedException(); + public void Clear() => this.ThrowNotSupportedException(); + public void ThrowNotSupportedException() + { + if (this._keyOrValue == StringDictionary.GenericAdapter.KeyOrValue.Key) throw new NotSupportedException("NotSupported_KeyCollectionSet"); + throw new NotSupportedException("NotSupported_ValueCollectionSet"); + } + public bool Contains(string item) => this._keyOrValue == StringDictionary.GenericAdapter.KeyOrValue.Key ? this._internal.ContainsKey(item) : this._internal.ContainsValue(item); + public void CopyTo(string[] array, int arrayIndex) => this.GetUnderlyingCollection().CopyTo((Array)array, arrayIndex); + public int Count => this._internal.Count; + public bool IsReadOnly => true; + public bool Remove(string item) + { + this.ThrowNotSupportedException(); + return false; + } + private ICollection GetUnderlyingCollection() => this._keyOrValue == StringDictionary.GenericAdapter.KeyOrValue.Key ? this._internal.Keys : this._internal.Values; + public IEnumerator GetEnumerator() + { + foreach (string underlying in (IEnumerable)this.GetUnderlyingCollection()) yield return underlying; + } + IEnumerator IEnumerable.GetEnumerator() => this.GetUnderlyingCollection().GetEnumerator(); + } + } + } + } +} diff --git a/TrustedUninstaller.Shared/Debug.cs b/TrustedUninstaller.Shared/Debug.cs new file mode 100644 index 0000000..b633fda --- /dev/null +++ b/TrustedUninstaller.Shared/Debug.cs @@ -0,0 +1,22 @@ +using System; +using System.Diagnostics; + +namespace TrustedUninstaller.Shared +{ + public static class Testing + { + [Conditional("DEBUG")] + public static void WriteLine(object text) + { + Console.WriteLine(text.ToString()); + } + public static void WriteLine(Exception exception, string shortTrace) + { + Console.WriteLine(exception.GetType() + " at " + shortTrace + ":" + exception.Message); + } + public static void WriteLine(Exception exception, string shortTrace, string item) + { + Console.WriteLine(exception.GetType() + " at " + shortTrace + $" ({item}):" + exception.Message); + } + } +} diff --git a/TrustedUninstaller.Shared/Defender.cs b/TrustedUninstaller.Shared/Defender.cs index 1de627e..cadf45e 100644 --- a/TrustedUninstaller.Shared/Defender.cs +++ b/TrustedUninstaller.Shared/Defender.cs @@ -132,7 +132,7 @@ namespace TrustedUninstaller.Shared Arguments = "-U:T -P:E -M:S -ShowWindowMode:Hide -Priority:RealTime -Wait cmd /c \"reg delete \"HKLM\\SOFTWARE\\Microsoft\\Windows Defender\" /v \"ProductAppDataPath\" /f &" + " reg delete \"HKLM\\SOFTWARE\\Microsoft\\Windows Defender\" /v \"InstallLocation\" /f\"", CreateWindow = false - }.RunTask().Wait(); + }.RunTaskOnMainThread(); if (new RegistryValueAction() { KeyName = "HKLM\\SOFTWARE\\Microsoft\\Windows Defender", Value = "InstallLocation", Operation = RegistryValueOperation.Delete }.GetStatus() != UninstallTaskStatus.Completed) @@ -157,7 +157,7 @@ namespace TrustedUninstaller.Shared Exe = $"NSudoLC.exe", Arguments = "-U:T -P:E -M:S -ShowWindowMode:Hide -Priority:RealTime -Wait reg delete \"HKLM\\SYSTEM\\CurrentControlSet\\Services\\WinDefend\" /f", CreateWindow = false - }.RunTask().Wait(); + }.RunTaskOnMainThread(); if (new RegistryKeyAction() { KeyName = "HKLM\\SYSTEM\\CurrentControlSet\\Services\\WinDefend" }.GetStatus() != UninstallTaskStatus.Completed) @@ -183,7 +183,7 @@ namespace TrustedUninstaller.Shared Exe = $"NSudoLC.exe", Arguments = "-U:T -P:E -M:S -ShowWindowMode:Hide -Priority:RealTime -Wait reg add \"HKLM\\SYSTEM\\CurrentControlSet\\Services\\WinDefend\" /v \"Start\" /t REG_DWORD /d 4 /f", CreateWindow = false - }.RunTask().Wait(); + }.RunTaskOnMainThread(); if (new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Services\WinDefend", Value = "Start", Data = 4, Type = RegistryValueType.REG_DWORD }.GetStatus() != UninstallTaskStatus.Completed) @@ -211,7 +211,7 @@ namespace TrustedUninstaller.Shared Exe = $"NSudoLC.exe", Arguments = @"-U:T -P:E -M:S -Priority:RealTime -ShowWindowMode:Hide -Wait reg delete ""HKCR\CLSID\{2781761E-28E0-4109-99FE-B9D127C57AFE}\InprocServer32"" /f", CreateWindow = false - }.RunTask().Wait(); + }.RunTaskOnMainThread(); if (new RegistryKeyAction() { KeyName = @"HKCR\CLSID\{2781761E-28E0-4109-99FE-B9D127C57AFE}\InprocServer32" }.GetStatus() != UninstallTaskStatus.Completed) @@ -247,7 +247,7 @@ namespace TrustedUninstaller.Shared "reg delete \"HKLM\\SOFTWARE\\Microsoft\\WindowsRuntime\\ActivatableClassId\\Windows.Internal.Security.SmartScreen.EventLogger\" /f &" + "reg delete \"HKLM\\SOFTWARE\\Microsoft\\WindowsRuntime\\ActivatableClassId\\Windows.Internal.Security.SmartScreen.UriReputationService\" /f\"", CreateWindow = false - }.RunTask().Wait(); + }.RunTaskOnMainThread(); if (new RegistryKeyAction() { KeyName = @"HKCR\CLSID\{a463fcb9-6b1c-4e0d-a80b-a2ca7999e25d}\InprocServer32" }.GetStatus() != UninstallTaskStatus.Completed) @@ -275,7 +275,7 @@ namespace TrustedUninstaller.Shared Arguments = @"-U:T -P:E -M:S -ShowWindowMode:Hide -Priority:RealTime -Wait reg add ""HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity"" /v Enabled /d 0 /f", CreateWindow = false - }.RunTask().Wait(); + }.RunTaskOnMainThread(); if (new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity", Value = "Enabled", Data = 0, }.GetStatus() != UninstallTaskStatus.Completed) @@ -302,19 +302,39 @@ namespace TrustedUninstaller.Shared Arguments = @"-U:T -P:E -M:S -ShowWindowMode:Hide -Priority:RealTime -Wait reg add ""HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity"" /v Enabled /d 0 /f", CreateWindow = false - }.RunTask().Wait(); + }.RunTaskOnMainThread(); if (new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity", Value = "Enabled", Data = 0, }.GetStatus() != UninstallTaskStatus.Completed) ErrorLogger.WriteToErrorLog("Could not disable memory integrity.", null, "Defender disable warning"); } - AmeliorationUtil.SafeRunAction(new RegistryValueAction() + try + { + // Can cause ProcessHacker driver warnings without this + new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Control\CI\Config", Value = "VulnerableDriverBlocklistEnable", Data = 0, }.RunTask().Wait(); + + if (new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Control\CI\Config", Value = "VulnerableDriverBlocklistEnable", Data = 0, }.GetStatus() + != UninstallTaskStatus.Completed) + throw new Exception("Unknown error"); + } + catch (Exception e) { - KeyName = @"HKLM\SYSTEM\CurrentControlSet\Control\CI\Config", - Value = "VulnerableDriverBlocklistEnable", - Data = 0, - }).Wait(); + ErrorLogger.WriteToErrorLog("First blocklist disable failed: " + e.GetType() + " " + e.Message, null, "Kernel driver preparation warning"); + + new RunAction() + { + RawPath = Directory.GetCurrentDirectory(), + Exe = $"NSudoLC.exe", + Arguments = + @"-U:T -P:E -M:S -ShowWindowMode:Hide -Priority:RealTime -Wait reg add ""HKLM\SYSTEM\CurrentControlSet\Control\CI\Config"" /v VulnerableDriverBlocklistEnable /d 0 /f", + CreateWindow = false + }.RunTaskOnMainThread(); + + if (new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Control\CI\Config", Value = "VulnerableDriverBlocklistEnable", Data = 0, }.GetStatus() + != UninstallTaskStatus.Completed) + ErrorLogger.WriteToErrorLog("Could not disable blocklist.", null, "Kernel driver preparation error"); + } return restartRequired; } @@ -491,7 +511,7 @@ namespace TrustedUninstaller.Shared "net stop WinDefend\"", CreateWindow = false, Timeout = 7500, - }.RunTask().Wait(); + }.RunTaskOnMainThread(); } return !Process.GetProcessesByName("MsMpEng").Any(); @@ -597,7 +617,7 @@ namespace TrustedUninstaller.Shared tokenGroups.Groups[idx].Attributes &= ~(uint)PInvoke.SE_GROUP_ATTRIBUTES.SE_GROUP_OWNER; } } - Console.WriteLine(tokenGroups.GroupCount); + tokenGroups.Groups[tokenGroups.GroupCount].Sid = tiSid; tokenGroups.Groups[tokenGroups.GroupCount].Attributes = (uint)PInvoke.SE_GROUP_ATTRIBUTES.SE_GROUP_ENABLED | (uint)PInvoke.SE_GROUP_ATTRIBUTES.SE_GROUP_ENABLED_BY_DEFAULT; tokenGroups.GroupCount++; diff --git a/TrustedUninstaller.Shared/Globals.cs b/TrustedUninstaller.Shared/Globals.cs index 9ee744c..fc98d44 100644 --- a/TrustedUninstaller.Shared/Globals.cs +++ b/TrustedUninstaller.Shared/Globals.cs @@ -9,12 +9,27 @@ namespace TrustedUninstaller.Shared { public class Globals { - public const string CurrentVersion = "0.7.2"; - public const double CurrentVersionNumber = 0.72; -#if DEBUG - public static readonly int WinVer = 19045; -#else + public const string CurrentVersion = "0.7.3"; + public const double CurrentVersionNumber = 0.73; + public static readonly int WinVer = Int32.Parse(Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion").GetValue("CurrentBuildNumber").ToString()); -#endif + + private static int _winUpdateVer = -1; + public static int WinUpdateVer + { + get + { + if (_winUpdateVer != -1) + return _winUpdateVer; + + try + { + _winUpdateVer = (int)Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion").GetValue("UBR"); + } + catch { _winUpdateVer = 0; } + + return _winUpdateVer; + } + } } } diff --git a/TrustedUninstaller.Shared/Playbook.cs b/TrustedUninstaller.Shared/Playbook.cs index 2f05939..181e34d 100644 --- a/TrustedUninstaller.Shared/Playbook.cs +++ b/TrustedUninstaller.Shared/Playbook.cs @@ -5,7 +5,6 @@ using System.Drawing; using System.Globalization; using System.IO; using System.Linq; -using System.Management.Automation.Runspaces; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -53,6 +52,8 @@ namespace TrustedUninstaller.Shared public bool Overhaul { get; set; } = false; public string Path { get; set; } + + public bool? UseKernelDriver { get; set; } = null; public List Options { get; set; } = null; diff --git a/TrustedUninstaller.Shared/ProcessPrivilege.cs b/TrustedUninstaller.Shared/ProcessPrivilege.cs new file mode 100644 index 0000000..6a75791 --- /dev/null +++ b/TrustedUninstaller.Shared/ProcessPrivilege.cs @@ -0,0 +1,507 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; +using System.Security; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Win32.SafeHandles; +using TrustedUninstaller.Shared.Actions; + +namespace TrustedUninstaller.Shared +{ + public class ProcessPrivilege + { + private static Win32.TokensEx.SafeTokenHandle userToken = new Win32.TokensEx.SafeTokenHandle(IntPtr.Zero); + private static Win32.TokensEx.SafeTokenHandle elevatedUserToken = new Win32.TokensEx.SafeTokenHandle(IntPtr.Zero); + private static Win32.TokensEx.SafeTokenHandle systemToken = new Win32.TokensEx.SafeTokenHandle(IntPtr.Zero); + private static Win32.TokensEx.SafeTokenHandle impsersonatedSystemToken = new Win32.TokensEx.SafeTokenHandle(IntPtr.Zero); + private static Win32.TokensEx.SafeTokenHandle lsassToken = new Win32.TokensEx.SafeTokenHandle(IntPtr.Zero); + + internal static void ResetTokens() + { + elevatedUserToken = new Win32.TokensEx.SafeTokenHandle(IntPtr.Zero); + lsassToken = new Win32.TokensEx.SafeTokenHandle(IntPtr.Zero); + systemToken = new Win32.TokensEx.SafeTokenHandle(IntPtr.Zero); + impsersonatedSystemToken = new Win32.TokensEx.SafeTokenHandle(IntPtr.Zero); + userToken = new Win32.TokensEx.SafeTokenHandle(IntPtr.Zero); + } + + public static void StartPrivilegedTask(AugmentedProcess.Process process, Privilege privilege) + { + var tcs = StartThread(process, privilege); + tcs.Task.Wait(); + for (int i = 0; tcs.Task.Result != null && i <= 3; i++) + { + ErrorLogger.WriteToErrorLog("Error launching privileged process: " + tcs.Task.Result.Message, tcs.Task.Result.StackTrace, "PrivilegedProcess Warning", + Path.GetFileName(process.StartInfo.FileName)); + ResetTokens(); + Thread.Sleep(500 * i); + tcs = StartThread(process, privilege); + tcs.Task.Wait(); + } + + if (tcs.Task.Result != null) + throw new SecurityException("Error launching privileged process.", tcs.Task.Result); + } + + private static TaskCompletionSource StartThread(AugmentedProcess.Process process, Privilege privilege) + { + var tcs = new TaskCompletionSource(); + var thread = new Thread(() => + { + try + { + switch (privilege) + { + case (Privilege.System): + GetSystemToken(); + process.Start(AugmentedProcess.Process.CreateType.RawToken, ref systemToken); + break; + case (Privilege.CurrentUser): + GetUserToken(true); + process.Start(AugmentedProcess.Process.CreateType.UserToken, ref userToken); + break; + case (Privilege.CurrentUserElevated): + GetElevatedUserToken(); + process.Start(AugmentedProcess.Process.CreateType.RawToken, ref elevatedUserToken); + break; + default: + throw new ArgumentException("Unexpected."); + } + } + catch (Exception e) + { + tcs.SetResult(e); + return; + } + + tcs.SetResult(null); + }); + thread.Start(); + return tcs; + } + + private static uint GetUserSession() + { + var sessionId = Win32.WTS.WTSGetActiveConsoleSessionId(); + if (sessionId != 0xFFFFFFFF) return sessionId; + IntPtr pSessionInfo = IntPtr.Zero; + Int32 count = 0; + if (Win32.WTS.WTSEnumerateSessions((IntPtr)null, 0, 1, ref pSessionInfo, ref count) == 0) + throw new Win32Exception(Marshal.GetLastWin32Error(), "Error enumerating user sessions."); + Int32 dataSize = Marshal.SizeOf(typeof(Win32.WTS.WTS_SESSION_INFO)); + Int64 current = (Int64)pSessionInfo; + for (int i = 0; i < count; i++) + { + Win32.WTS.WTS_SESSION_INFO si = + (Win32.WTS.WTS_SESSION_INFO)Marshal.PtrToStructure((System.IntPtr)current, + typeof(Win32.WTS.WTS_SESSION_INFO)); + current += dataSize; + if (si.State == Win32.WTS.WTS_CONNECTSTATE_CLASS.WTSActive) + { + sessionId = (uint)si.SessionID; + break; + } + } + Win32.WTS.WTSFreeMemory(pSessionInfo); + return sessionId; + } + + private static void GetUserToken(bool getPrivileges) + { + if (getPrivileges) + { + GetSystemToken(); + var result = Win32.Tokens.ImpersonateLoggedOnUser(systemToken); + if (!result) + throw new Win32Exception(Marshal.GetLastWin32Error(), "Error impersonating system process token."); + + Win32.TokensEx.AdjustCurrentPrivilege(Win32.Tokens.SE_ASSIGNPRIMARYTOKEN_NAME); + Win32.TokensEx.AdjustCurrentPrivilege(Win32.Tokens.SE_INCREASE_QUOTA_NAME); + } + + if (userToken.DangerousGetHandle() != IntPtr.Zero) + return; + + var sessionId = GetUserSession(); + + if (Win32.WTS.WTSQueryUserToken(sessionId, out Win32.TokensEx.SafeTokenHandle wtsToken)) + { + if (!Win32.Tokens.DuplicateTokenEx(wtsToken, Win32.Tokens.TokenAccessFlags.TOKEN_ALL_ACCESS, + IntPtr.Zero, + Win32.Tokens.SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, + Win32.Tokens.TOKEN_TYPE.TokenPrimary, out userToken)) + { + throw new Win32Exception(Marshal.GetLastWin32Error(), + "Failed to duplicate process token for lsass."); + } + return; + } + throw new Win32Exception(Marshal.GetLastWin32Error(), "Error fetching active user session token."); + } + + private static void GetElevatedUserToken() + { + GetSystemToken(); + var result = Win32.Tokens.ImpersonateLoggedOnUser(systemToken); + + if (!result) + throw new Win32Exception(Marshal.GetLastWin32Error(), "Error impersonating system process token."); + + if (lsassToken.DangerousGetHandle() == IntPtr.Zero) + { + + var processHandle = Process.GetProcessesByName("lsass").First().Handle; + if (!Win32.Tokens.OpenProcessToken(processHandle, + Win32.Tokens.TokenAccessFlags.TOKEN_DUPLICATE | + Win32.Tokens.TokenAccessFlags.TOKEN_ASSIGN_PRIMARY | + Win32.Tokens.TokenAccessFlags.TOKEN_QUERY | Win32.Tokens.TokenAccessFlags.TOKEN_IMPERSONATE, + out var tokenHandle)) + { + Win32.CloseHandle(processHandle); + throw new Win32Exception(Marshal.GetLastWin32Error(), "Failed to open process token for lsass."); + } + + if (!Win32.Tokens.DuplicateTokenEx(tokenHandle, Win32.Tokens.TokenAccessFlags.TOKEN_ALL_ACCESS, + IntPtr.Zero, + Win32.Tokens.SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, + Win32.Tokens.TOKEN_TYPE.TokenImpersonation, out lsassToken)) + { + Win32.CloseHandle(processHandle); + throw new Win32Exception(Marshal.GetLastWin32Error(), + "Failed to duplicate process token for lsass."); + } + + Win32.CloseHandle(processHandle); + } + + + result = Win32.Tokens.ImpersonateLoggedOnUser(lsassToken); + if (!result) + throw new Win32Exception(Marshal.GetLastWin32Error(), "Error impersonating lsass process token."); + + + Win32.TokensEx.AdjustCurrentPrivilege(Win32.Tokens.SE_ASSIGNPRIMARYTOKEN_NAME); + Win32.TokensEx.AdjustCurrentPrivilege(Win32.Tokens.SE_INCREASE_QUOTA_NAME); + + + if (elevatedUserToken.DangerousGetHandle() != IntPtr.Zero) + return; + + + var privileges = new[] + { + Win32.Tokens.SE_INCREASE_QUOTA_NAME, + Win32.Tokens.SE_MACHINE_ACCOUNT_NAME, Win32.Tokens.SE_SECURITY_NAME, + Win32.Tokens.SE_TAKE_OWNERSHIP_NAME, Win32.Tokens.SE_LOAD_DRIVER_NAME, + Win32.Tokens.SE_SYSTEM_PROFILE_NAME, Win32.Tokens.SE_SYSTEMTIME_NAME, + Win32.Tokens.SE_PROFILE_SINGLE_PROCESS_NAME, Win32.Tokens.SE_INCREASE_BASE_PRIORITY_NAME, + Win32.Tokens.SE_CREATE_PERMANENT_NAME, + Win32.Tokens.SE_BACKUP_NAME, Win32.Tokens.SE_RESTORE_NAME, Win32.Tokens.SE_SHUTDOWN_NAME, + Win32.Tokens.SE_DEBUG_NAME, Win32.Tokens.SE_AUDIT_NAME, Win32.Tokens.SE_SYSTEM_ENVIRONMENT_NAME, + Win32.Tokens.SE_CHANGE_NOTIFY_NAME, + Win32.Tokens.SE_UNDOCK_NAME, Win32.Tokens.SE_SYNC_AGENT_NAME, + Win32.Tokens.SE_ENABLE_DELEGATION_NAME, Win32.Tokens.SE_MANAGE_VOLUME_NAME, + Win32.Tokens.SE_IMPERSONATE_NAME, Win32.Tokens.SE_CREATE_GLOBAL_NAME, + Win32.Tokens.SE_TRUSTED_CREDMAN_ACCESS_NAME, Win32.Tokens.SE_RELABEL_NAME, + Win32.Tokens.SE_TIME_ZONE_NAME, + Win32.Tokens.SE_CREATE_SYMBOLIC_LINK_NAME, Win32.Tokens.SE_DELEGATE_SESSION_USER_IMPERSONATE_NAME + }; + var authId = Win32.Tokens.SYSTEM_LUID; + + GetUserToken(false); + + Win32.Tokens.DuplicateTokenEx(userToken, + Win32.Tokens.TokenAccessFlags.TOKEN_ALL_ACCESS, IntPtr.Zero, + Win32.Tokens.SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, Win32.Tokens.TOKEN_TYPE.TokenPrimary, + out Win32.TokensEx.SafeTokenHandle dupedUserToken); + + Win32.SID.AllocateAndInitializeSid( + ref Win32.SID.SECURITY_MANDATORY_LABEL_AUTHORITY, + 1, + (int)Win32.SID.SECURITY_MANDATORY_LABEL.High, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + out IntPtr integritySid); + + var tokenMandatoryLabel = new Win32.Tokens.TOKEN_MANDATORY_LABEL() { + Label = default(Win32.SID.SID_AND_ATTRIBUTES) + }; + + tokenMandatoryLabel.Label.Attributes = (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_INTEGRITY; + tokenMandatoryLabel.Label.Sid = integritySid; + + var integritySize = Marshal.SizeOf(tokenMandatoryLabel); + var tokenInfo = Marshal.AllocHGlobal(integritySize); + + Marshal.StructureToPtr(tokenMandatoryLabel, tokenInfo, false); + + Win32.Tokens.SetTokenInformation( + dupedUserToken, + Win32.Tokens.TOKEN_INFORMATION_CLASS.TokenIntegrityLevel, + tokenInfo, + integritySize + Win32.SID.GetLengthSid(integritySid)); + + + var pTokenUser = Win32.TokensEx.GetInfoFromToken(dupedUserToken, Win32.Tokens.TOKEN_INFORMATION_CLASS.TokenUser); + var pTokenOwner = + Win32.TokensEx.GetInfoFromToken(dupedUserToken, Win32.Tokens.TOKEN_INFORMATION_CLASS.TokenOwner); + var pTokenPrivileges = + Win32.TokensEx.GetInfoFromToken(dupedUserToken, Win32.Tokens.TOKEN_INFORMATION_CLASS.TokenPrivileges); + var pTokenGroups = + Win32.TokensEx.GetInfoFromToken(dupedUserToken, Win32.Tokens.TOKEN_INFORMATION_CLASS.TokenGroups); + var pTokenPrimaryGroup = + Win32.TokensEx.GetInfoFromToken(dupedUserToken, Win32.Tokens.TOKEN_INFORMATION_CLASS.TokenPrimaryGroup); + var pTokenDefaultDacl = + Win32.TokensEx.GetInfoFromToken(dupedUserToken, Win32.Tokens.TOKEN_INFORMATION_CLASS.TokenDefaultDacl); + var pTokenSource = + Win32.TokensEx.GetInfoFromToken(dupedUserToken, Win32.Tokens.TOKEN_INFORMATION_CLASS.TokenSource); + + var tokenUser = + (Win32.Tokens.TOKEN_USER)Marshal.PtrToStructure(pTokenUser, typeof(Win32.Tokens.TOKEN_USER)); + if (!Win32.TokensEx.CreateTokenPrivileges(privileges, out var tokenPrivileges)) + tokenPrivileges = + (Win32.Tokens.TOKEN_PRIVILEGES)Marshal.PtrToStructure(pTokenPrivileges, + typeof(Win32.Tokens.TOKEN_PRIVILEGES)); + var tokenGroups = (Win32.Tokens.TOKEN_GROUPS)Marshal.PtrToStructure( + pTokenGroups, typeof(Win32.Tokens.TOKEN_GROUPS)); + var tokenOwner = + (Win32.Tokens.TOKEN_OWNER)Marshal.PtrToStructure(pTokenOwner, typeof(Win32.Tokens.TOKEN_OWNER)); + var tokenPrimaryGroup = + (Win32.Tokens.TOKEN_PRIMARY_GROUP)Marshal.PtrToStructure(pTokenPrimaryGroup, + typeof(Win32.Tokens.TOKEN_PRIMARY_GROUP)); + var tokenDefaultDacl = (Win32.Tokens.TOKEN_DEFAULT_DACL)Marshal.PtrToStructure( + pTokenDefaultDacl, typeof(Win32.Tokens.TOKEN_DEFAULT_DACL)); + var tokenSource = (Win32.Tokens.TOKEN_SOURCE)Marshal.PtrToStructure( + pTokenSource, typeof(Win32.Tokens.TOKEN_SOURCE)); + /* + for (var idx = 0; idx < tokenPrivileges.PrivilegeCount - 1; idx++) + { + if ((tokenPrivileges.Privileges[idx].Attributes & + (uint)Win32.Tokens.SE_PRIVILEGE_ATTRIBUTES.SE_PRIVILEGE_ENABLED) != 0) + { + } + + if ((tokenPrivileges.Privileges[idx].Attributes & + (uint)Win32.Tokens.SE_PRIVILEGE_ATTRIBUTES.SE_PRIVILEGE_ENABLED_BY_DEFAULT) != 0) + { + } + } + */ + + IntPtr adminsSid = IntPtr.Zero; + IntPtr localAndAdminSid = IntPtr.Zero; + + bool adminsFound = false; + bool localAndAdminFound = false; + + for (var idx = 0; idx < tokenGroups.GroupCount - 1; idx++) + { + Win32.SID.ConvertSidToStringSid(tokenGroups.Groups[idx].Sid, out string strSid); + if (string.Compare(strSid, Win32.SID.DOMAIN_ALIAS_RID_ADMINS, StringComparison.OrdinalIgnoreCase) == 0) + { + adminsFound = true; + tokenGroups.Groups[idx].Attributes = (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_ENABLED | + (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES + .SE_GROUP_ENABLED_BY_DEFAULT | (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_MANDATORY | (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_OWNER; + } else if (string.Compare(strSid, Win32.SID.DOMAIN_ALIAS_RID_LOCAL_AND_ADMIN_GROUP, StringComparison.OrdinalIgnoreCase) == 0) + { + localAndAdminFound = true; + tokenGroups.Groups[idx].Attributes = (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_ENABLED | + (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES + .SE_GROUP_ENABLED_BY_DEFAULT | (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_MANDATORY; + } + } + + if (!adminsFound) + { + Win32.SID.ConvertStringSidToSid(Win32.SID.DOMAIN_ALIAS_RID_ADMINS, out adminsSid); + tokenGroups.Groups[tokenGroups.GroupCount].Sid = adminsSid; + tokenGroups.Groups[tokenGroups.GroupCount].Attributes = + (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_ENABLED | + (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_ENABLED_BY_DEFAULT; + tokenGroups.GroupCount++; + } + if (!localAndAdminFound) + { + Win32.SID.ConvertStringSidToSid(Win32.SID.DOMAIN_ALIAS_RID_LOCAL_AND_ADMIN_GROUP, out localAndAdminSid); + tokenGroups.Groups[tokenGroups.GroupCount].Sid = localAndAdminSid; + tokenGroups.Groups[tokenGroups.GroupCount].Attributes = + (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_ENABLED | + (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_ENABLED_BY_DEFAULT; + tokenGroups.GroupCount++; + } + + var expirationTime = new Win32.LARGE_INTEGER() { QuadPart = -1L }; + var sqos = new Win32.Tokens.SECURITY_QUALITY_OF_SERVICE( + Win32.Tokens.SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, Win32.Tokens.SECURITY_STATIC_TRACKING, + 0); + var oa = new Win32.Tokens.OBJECT_ATTRIBUTES(string.Empty, 0) { }; + var pSqos = Marshal.AllocHGlobal(Marshal.SizeOf(sqos)); + Marshal.StructureToPtr(sqos, pSqos, true); + oa.SecurityQualityOfService = pSqos; + + var status = Win32.Tokens.ZwCreateToken(out elevatedUserToken, + Win32.Tokens.TokenAccessFlags.TOKEN_ALL_ACCESS, ref oa, Win32.Tokens.TOKEN_TYPE.TokenPrimary, + ref authId, ref expirationTime, ref tokenUser, ref tokenGroups, ref tokenPrivileges, ref tokenOwner, + ref tokenPrimaryGroup, ref tokenDefaultDacl, ref tokenSource); + + Win32.LocalFree(pTokenUser); + Win32.LocalFree(pTokenOwner); + Win32.LocalFree(pTokenGroups); + Win32.LocalFree(pTokenDefaultDacl); + Win32.LocalFree(pTokenPrivileges); + Win32.LocalFree(pTokenPrimaryGroup); + + if (adminsSid != IntPtr.Zero) + Win32.SID.FreeSid(adminsSid); + if (localAndAdminSid != IntPtr.Zero) + Win32.SID.FreeSid(localAndAdminSid); + if (integritySid != IntPtr.Zero) + Win32.SID.FreeSid(integritySid); + + if (status != 0) + throw new Win32Exception(Marshal.GetLastWin32Error(), "Error creating elevated user token: " + status); + } + + public static void GetSystemToken() + { + if (systemToken.DangerousGetHandle() != IntPtr.Zero) + return; + + try + { + var processHandle = Process.GetProcessesByName("winlogon").First().Handle; + if (!Win32.Tokens.OpenProcessToken(processHandle, + Win32.Tokens.TokenAccessFlags.TOKEN_DUPLICATE | Win32.Tokens.TokenAccessFlags.TOKEN_ASSIGN_PRIMARY | + Win32.Tokens.TokenAccessFlags.TOKEN_QUERY | Win32.Tokens.TokenAccessFlags.TOKEN_IMPERSONATE, + out var tokenHandle)) + { + Win32.CloseHandle(processHandle); + throw new Win32Exception(Marshal.GetLastWin32Error(), "Failed to open process token for winlogon."); + } + + if (!Win32.Tokens.DuplicateTokenEx(tokenHandle, Win32.Tokens.TokenAccessFlags.TOKEN_ALL_ACCESS, IntPtr.Zero, + Win32.Tokens.SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, + Win32.Tokens.TOKEN_TYPE.TokenPrimary, out systemToken)) + { + Win32.CloseHandle(processHandle); + throw new Win32Exception(Marshal.GetLastWin32Error(), + "Failed to duplicate process token for winlogon."); + } + + Win32.CloseHandle(processHandle); + } + catch (Exception e) + { + var sessionId = GetUserSession(); + int dwLsassPID = -1; + int dwWinLogonPID = -1; + Win32.WTS.WTS_PROCESS_INFO[] pProcesses; + IntPtr pProcessInfo = IntPtr.Zero; + int dwProcessCount = 0; + if (Win32.WTS.WTSEnumerateProcesses((IntPtr)null, 0, 1, ref pProcessInfo, ref dwProcessCount)) + { + IntPtr pMemory = pProcessInfo; + pProcesses = new Win32.WTS.WTS_PROCESS_INFO[dwProcessCount]; + for (int i = 0; i < dwProcessCount; i++) + { + pProcesses[i] = + (Win32.WTS.WTS_PROCESS_INFO)Marshal.PtrToStructure(pProcessInfo, + typeof(Win32.WTS.WTS_PROCESS_INFO)); + pProcessInfo = (IntPtr)((long)pProcessInfo + Marshal.SizeOf(pProcesses[i])); + var processName = Marshal.PtrToStringAnsi(pProcesses[i].ProcessName); + Win32.SID.ConvertSidToStringSid(pProcesses[i].UserSid, out string sid); + string strSid; + if (processName == null || pProcesses[i].UserSid == default || sid != "S-1-5-18") continue; + if ((-1 == dwLsassPID) && (0 == pProcesses[i].SessionID) && (processName == "lsass.exe")) + { + dwLsassPID = pProcesses[i].ProcessID; + continue; + } + + if ((-1 == dwWinLogonPID) && (sessionId == pProcesses[i].SessionID) && + (processName == "winlogon.exe")) + { + dwWinLogonPID = pProcesses[i].ProcessID; + continue; + } + } + + Win32.WTS.WTSFreeMemory(pMemory); + } + + IntPtr systemProcessHandle = IntPtr.Zero; + try + { + systemProcessHandle = Process.GetProcessById(dwLsassPID).Handle; + } + catch + { + systemProcessHandle = Process.GetProcessById(dwWinLogonPID).Handle; + } + + if (!Win32.Tokens.OpenProcessToken(systemProcessHandle, Win32.Tokens.TokenAccessFlags.TOKEN_DUPLICATE, + out Win32.TokensEx.SafeTokenHandle token)) + { + Win32.CloseHandle(systemProcessHandle); + throw new Win32Exception(Marshal.GetLastWin32Error(), "Failed to open process token."); + } + + if (!Win32.Tokens.DuplicateTokenEx(token, Win32.Tokens.TokenAccessFlags.MAXIMUM_ALLOWED, IntPtr.Zero, + Win32.Tokens.SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, + Win32.Tokens.TOKEN_TYPE.TokenPrimary, out systemToken)) + { + Win32.CloseHandle(systemProcessHandle); + throw new Win32Exception(Marshal.GetLastWin32Error(), "Failed to duplicate process token."); + } + + Win32.CloseHandle(systemProcessHandle); + } + } + + public static Win32.TokensEx.SafeTokenHandle GetCurrentProcessToken() + { + if (!Win32.Tokens.OpenProcessToken(Win32.Process.GetCurrentProcess(), + Win32.Tokens.TokenAccessFlags.TOKEN_READ, out Win32.TokensEx.SafeTokenHandle token)) + throw new Win32Exception(Marshal.GetLastWin32Error(), "Error opening token for current process."); + return token; + } + + private static Win32.TokensEx.SafeTokenHandle GetProcessTokenByName(string name, bool impersonation) + { + var processHandle = Process.GetProcessesByName(name).First().Handle; + if (!Win32.Tokens.OpenProcessToken(processHandle, + Win32.Tokens.TokenAccessFlags.TOKEN_DUPLICATE | Win32.Tokens.TokenAccessFlags.TOKEN_ASSIGN_PRIMARY | + Win32.Tokens.TokenAccessFlags.TOKEN_QUERY | Win32.Tokens.TokenAccessFlags.TOKEN_IMPERSONATE, + out var tokenHandle)) + { + Win32.CloseHandle(processHandle); + throw new Win32Exception(Marshal.GetLastWin32Error(), "Failed to open process token for " + name + "."); + } + + if (!Win32.Tokens.DuplicateTokenEx(tokenHandle, Win32.Tokens.TokenAccessFlags.TOKEN_ALL_ACCESS, IntPtr.Zero, + impersonation ? Win32.Tokens.SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation : Win32.Tokens.SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, + impersonation ? Win32.Tokens.TOKEN_TYPE.TokenImpersonation : Win32.Tokens.TOKEN_TYPE.TokenPrimary, out Win32.TokensEx.SafeTokenHandle handle)) + { + Win32.CloseHandle(processHandle); + throw new Win32Exception(Marshal.GetLastWin32Error(), + "Failed to duplicate process token for " + name + "."); + } + + Win32.CloseHandle(processHandle); + return handle; + } } +} \ No newline at end of file diff --git a/TrustedUninstaller.Shared/Tasks/ITaskAction.cs b/TrustedUninstaller.Shared/Tasks/ITaskAction.cs index dff1cd6..adf3423 100644 --- a/TrustedUninstaller.Shared/Tasks/ITaskAction.cs +++ b/TrustedUninstaller.Shared/Tasks/ITaskAction.cs @@ -20,5 +20,6 @@ namespace TrustedUninstaller.Shared.Tasks public string ErrorString(); public UninstallTaskStatus GetStatus(); public Task RunTask(); + public void RunTaskOnMainThread(); } } \ No newline at end of file diff --git a/TrustedUninstaller.Shared/Tasks/TaskAction.cs b/TrustedUninstaller.Shared/Tasks/TaskAction.cs index e720b40..d380e1b 100644 --- a/TrustedUninstaller.Shared/Tasks/TaskAction.cs +++ b/TrustedUninstaller.Shared/Tasks/TaskAction.cs @@ -6,8 +6,16 @@ namespace TrustedUninstaller.Shared.Tasks { [YamlMember(typeof(bool), Alias = "ignoreErrors")] public bool IgnoreErrors { get; set; } = false; + [YamlMember(typeof(string), Alias = "option")] public string Option { get; set; } = null; + + [YamlMember(typeof(string[]), Alias = "options")] + public string[] Options { get; set; } = null; + + [YamlMember(typeof(string[]), Alias = "builds")] + public string[] Builds { get; set; } = null; + [YamlMember(typeof(string), Alias = "cpuArch")] public string Arch { get; set; } = null; } diff --git a/TrustedUninstaller.Shared/Tasks/UninstallTask.cs b/TrustedUninstaller.Shared/Tasks/UninstallTask.cs index fa2ae20..d43b128 100644 --- a/TrustedUninstaller.Shared/Tasks/UninstallTask.cs +++ b/TrustedUninstaller.Shared/Tasks/UninstallTask.cs @@ -24,6 +24,15 @@ namespace TrustedUninstaller.Shared.Tasks public int Priority { get; set; } = 1; public UninstallTaskPrivilege Privilege { get; set; } = UninstallTaskPrivilege.Admin; + + [YamlMember(typeof(string), Alias = "option")] + public string Option { get; set; } = null; + [YamlMember(typeof(string[]), Alias = "options")] + public string[] Options { get; set; } = null; + [YamlMember(typeof(string[]), Alias = "builds")] + public string[] Builds { get; set; } = null; + [YamlMember(typeof(string), Alias = "cpuArch")] + public string Arch { get; set; } = null; public List Features { get; set; } = new List(); diff --git a/TrustedUninstaller.Shared/TrustedUninstaller.Shared.csproj b/TrustedUninstaller.Shared/TrustedUninstaller.Shared.csproj index 857980a..e294537 100644 --- a/TrustedUninstaller.Shared/TrustedUninstaller.Shared.csproj +++ b/TrustedUninstaller.Shared/TrustedUninstaller.Shared.csproj @@ -51,14 +51,21 @@ true SINGLE + + bin\x64\Release\ + x64 + true + embedded + true + SINGLE;DEBUG + - + - False True @@ -67,10 +74,7 @@ - - - ..\TrustedUninstaller.GUI\bin\x64\Release\YamlDotNet.dll @@ -95,10 +99,13 @@ + + + @@ -113,6 +120,7 @@ + @@ -131,11 +139,11 @@ - + @@ -156,16 +164,16 @@ true true - + for /f "usebackq delims=" %%A in (`DIR /B /S /A:d "$(SolutionDir)" ^| FINDSTR /R /c:".*\\bin\\.*\\de$" /c:".*\\bin\\.*\\en$" /c:".*\\bin\\.*\\es$" /c:".*\\bin\\.*\\fr$" /c:".*\\bin\\.*\\it$" /c:".*\\bin\\.*\\ja$" /c:".*\\bin\\.*\\ko$" /c:".*\\bin\\.*\\ru$" /c:".*\\bin\\.*\\zh-Hans$" /c:".*\\bin\\.*\\zh-Hant$" /c:".*\\bin\\.*\\pl$" /c:".*\\bin\\.*\\zh-CN$"`) do (RMDIR /Q /S "%%A" & cmd /c "exit /b 0") - cmd /c "echo Compiled> "$(SolutionDir)\TrustedUninstaller.GUI\gui-builder\Compiled.txt"" + PowerShell -NoP -C "Start-Process '$(SolutionDir)\TrustedUninstaller.GUI\gui-builder\ame-builder.exe' -ArgumentList 'Shared','""""x64\$(Configuration)""""' -NoNewWindow -Wait" + + + .allowedextension + - - PowerShell -NoP -C "Get-Process 'ame-builder' -EA SilentlyContinue | Wait-Process -EA SilentlyContinue; EXIT 0" -PowerShell -NoP -C "Start-Process '$(SolutionDir)\TrustedUninstaller.GUI\gui-builder\ame-builder.exe' -ArgumentList 'Shared' -WindowStyle Hidden" - - + for /f "usebackq delims=" %%A in (`DIR /B /S /A:d "$(SolutionDir)" ^| FINDSTR /R /c:".*\\bin\\.*\\de$" /c:".*\\bin\\.*\\en$" /c:".*\\bin\\.*\\es$" /c:".*\\bin\\.*\\fr$" /c:".*\\bin\\.*\\it$" /c:".*\\bin\\.*\\ja$" /c:".*\\bin\\.*\\ko$" /c:".*\\bin\\.*\\ru$" /c:".*\\bin\\.*\\zh-Hans$" /c:".*\\bin\\.*\\zh-Hant$" /c:".*\\bin\\.*\\pl$" /c:".*\\bin\\.*\\zh-CN$"`) do (RMDIR /Q /S "%%A" & cmd /c "exit /b 0") \ No newline at end of file diff --git a/TrustedUninstaller.Shared/Win32.cs b/TrustedUninstaller.Shared/Win32.cs new file mode 100644 index 0000000..7dbc1ab --- /dev/null +++ b/TrustedUninstaller.Shared/Win32.cs @@ -0,0 +1,1699 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Security; +using System.Security.Permissions; +using System.Text; +using Microsoft.Win32.SafeHandles; +using TrustedUninstaller.Shared; +using TrustedUninstaller.Shared.Actions; +using TrustedUninstaller.Shared.Tasks; + +namespace TrustedUninstaller.Shared +{ + public static class Win32 + { + public static class Service + { + [DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Auto)] + public static extern IntPtr CreateService( + IntPtr hSCManager, + string lpServiceName, + string lpDisplayName, + uint dwDesiredAccess, + uint dwServiceType, + uint dwStartType, + uint dwErrorControl, + string lpBinaryPathName, + [Optional] string lpLoadOrderGroup, + [Optional] string lpdwTagId, // only string so we can pass null + [Optional] string lpDependencies, + [Optional] string lpServiceStartName, + [Optional] string lpPassword); + + [DllImport("advapi32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool DeleteService(IntPtr hService); + + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)] + public static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, SERVICE_ACCESS dwDesiredAccess); + + [DllImport("advapi32.dll", EntryPoint = "OpenSCManagerW", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)] + public static extern IntPtr OpenSCManager(string machineName, string databaseName, SCM_ACCESS dwDesiredAccess); + + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern bool QueryServiceStatusEx(IntPtr Service, int InfoLevel, + ref SERVICE_STATUS_PROCESS ServiceStatus, int BufSize, out int BytesNeeded); + + [DllImport("advapi32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CloseServiceHandle(IntPtr hSCObject); + + [DllImport("Advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern bool EnumServicesStatusEx( + IntPtr hSCManager, + uint InfoLevel, + SERVICE_TYPE dwServiceType, + uint dwServiceState, + IntPtr lpServices, + int cbBufSize, + out int pcbBytesNeeded, + out int lpServicesReturned, + ref int lpResumeHandle, + string pszGroupName + ); + + public struct ENUM_SERVICE_STATUS_PROCESS { + [MarshalAs(UnmanagedType.LPWStr)] + public string lpServiceName; + [MarshalAs(UnmanagedType.LPWStr)] + public string lpDisplayName; + public SERVICE_STATUS_PROCESS ServiceStatusProcess; + } + + [StructLayout(LayoutKind.Sequential)] + public struct SERVICE_STATUS_PROCESS + { + public SERVICE_TYPE ServiceType; + public SERVICE_STATE CurrentState; + public SERVICE_ACCEPT ControlsAccepted; + public int Win32ExitCode; + public int ServiceSpecificExitCode; + public int CheckPoint; + public int WaitHint; + public int ProcessID; + public SERVICE_FLAGS ServiceFlags; + } + + public enum SERVICE_STATE : int + { + ContinuePending = 0x5, + PausePending = 0x6, + Paused = 0x7, + Running = 0x4, + StartPending = 0x2, + StopPending = 0x3, + Stopped = 0x1 + } + + public enum SERVICE_ACCEPT : int + { + NetBindChange = 0x10, + ParamChange = 0x8, + PauseContinue = 0x2, + PreShutdown = 0x100, + Shutdown = 0x4, + Stop = 0x1, + HardwareProfileChange = 0x20, + PowerEvent = 0x40, + SessionChange = 0x80 + } + + public enum SERVICE_FLAGS : int + { + None = 0, + RunsInSystemProcess = 0x1 + } + + /// + /// Service types. + /// + [Flags] + public enum SERVICE_TYPE : uint + { + /// + /// Driver service. + /// + SERVICE_KERNEL_DRIVER = 0x00000001, + /// + /// File system driver service. + /// + SERVICE_FILE_SYSTEM_DRIVER = 0x00000002, + /// + /// Service that runs in its own process. + /// + SERVICE_WIN32_OWN_PROCESS = 0x00000010, + /// + /// Service that shares a process with one or more other services. + /// + SERVICE_WIN32_SHARE_PROCESS = 0x00000020, + /// + /// The service can interact with the desktop. + /// + SERVICE_INTERACTIVE_PROCESS = 0x00000100, + } + + /// + /// Service start options + /// + public enum SERVICE_START : uint + { + /// + /// A device driver started by the system loader. This value is valid + /// only for driver services. + /// + SERVICE_BOOT_START = 0x00000000, + /// + /// A device driver started by the IoInitSystem function. This value + /// is valid only for driver services. + /// + SERVICE_SYSTEM_START = 0x00000001, + /// + /// A service started automatically by the service control manager + /// during system startup. For more information, see Automatically + /// Starting Services. + /// + SERVICE_AUTO_START = 0x00000002, + /// + /// A service started by the service control manager when a process + /// calls the StartService function. For more information, see + /// Starting Services on Demand. + /// + SERVICE_DEMAND_START = 0x00000003, + /// + /// A service that cannot be started. Attempts to start the service + /// result in the error code ERROR_SERVICE_DISABLED. + /// + SERVICE_DISABLED = 0x00000004, + } + + /// + /// Severity of the error, and action taken, if this service fails + /// to start. + /// + public enum SERVICE_ERROR + { + /// + /// The startup program ignores the error and continues the startup + /// operation. + /// + SERVICE_ERROR_IGNORE = 0x00000000, + /// + /// The startup program logs the error in the event log but continues + /// the startup operation. + /// + SERVICE_ERROR_NORMAL = 0x00000001, + /// + /// The startup program logs the error in the event log. If the + /// last-known-good configuration is being started, the startup + /// operation continues. Otherwise, the system is restarted with + /// the last-known-good configuration. + /// + SERVICE_ERROR_SEVERE = 0x00000002, + /// + /// The startup program logs the error in the event log, if possible. + /// If the last-known-good configuration is being started, the startup + /// operation fails. Otherwise, the system is restarted with the + /// last-known good configuration. + /// + SERVICE_ERROR_CRITICAL = 0x00000003, + } + + [Flags] + public enum SERVICE_ACCESS : uint + { + STANDARD_RIGHTS_REQUIRED = 0xF0000, + SERVICE_QUERY_CONFIG = 0x00001, + SERVICE_CHANGE_CONFIG = 0x00002, + SERVICE_QUERY_STATUS = 0x00004, + SERVICE_ENUMERATE_DEPENDENTS = 0x00008, + SERVICE_START = 0x00010, + SERVICE_STOP = 0x00020, + SERVICE_PAUSE_CONTINUE = 0x00040, + SERVICE_INTERROGATE = 0x00080, + SERVICE_USER_DEFINED_CONTROL = 0x00100, + SERVICE_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG | SERVICE_QUERY_STATUS | SERVICE_ENUMERATE_DEPENDENTS | SERVICE_START | SERVICE_STOP | + SERVICE_PAUSE_CONTINUE | SERVICE_INTERROGATE | SERVICE_USER_DEFINED_CONTROL) + } + + [Flags] + public enum SCM_ACCESS : uint + { + STANDARD_RIGHTS_REQUIRED = 0xF0000, + SC_MANAGER_CONNECT = 0x00001, + SC_MANAGER_CREATE_SERVICE = 0x00002, + SC_MANAGER_ENUMERATE_SERVICE = 0x00004, + SC_MANAGER_LOCK = 0x00008, + SC_MANAGER_QUERY_LOCK_STATUS = 0x00010, + SC_MANAGER_MODIFY_BOOT_CONFIG = 0x00020, + SC_MANAGER_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE | SC_MANAGER_ENUMERATE_SERVICE | SC_MANAGER_LOCK | SC_MANAGER_QUERY_LOCK_STATUS | + SC_MANAGER_MODIFY_BOOT_CONFIG + } + } + public static class ServiceEx { + public static bool IsPendingDeleteOrDeleted(string serviceName) + { + var manager = Service.OpenSCManager(null, null, Service.SCM_ACCESS.SC_MANAGER_ALL_ACCESS); + if (manager == IntPtr.Zero) + return new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Services\" + serviceName, Value = "DeleteFlag", Type = RegistryValueType.REG_DWORD, Data = 1 }.GetStatus() == UninstallTaskStatus.Completed || + new RegistryKeyAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Services\" + serviceName, Operation = RegistryKeyOperation.Delete }.GetStatus() == UninstallTaskStatus.Completed; + + var handle = Service.CreateService(manager, serviceName, "AME Deletion Check", + (uint)Service.SERVICE_ACCESS.SERVICE_ALL_ACCESS, + (uint)Service.SERVICE_TYPE.SERVICE_WIN32_OWN_PROCESS, (uint)Service.SERVICE_START.SERVICE_DISABLED, + (uint)Service.SERVICE_ERROR.SERVICE_ERROR_IGNORE, @"ame-deletion-check"); + if (handle == IntPtr.Zero) + { + if (Marshal.GetLastWin32Error() == 0x00000430) + { + return true; + } + return new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Services\" + serviceName, Value = "DeleteFlag", Type = RegistryValueType.REG_DWORD, Data = 1 }.GetStatus() == UninstallTaskStatus.Completed || + new RegistryKeyAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Services\" + serviceName, Operation = RegistryKeyOperation.Delete }.GetStatus() == UninstallTaskStatus.Completed; + } + Service.DeleteService(handle); + Service.CloseServiceHandle(handle); + Service.CloseServiceHandle(manager); + return true; + } + public static bool IsPendingStopOrStopped(string serviceName, out bool pending) + { + pending = false; + var manager = Service.OpenSCManager(null, null, Service.SCM_ACCESS.SC_MANAGER_ENUMERATE_SERVICE); + if (manager == IntPtr.Zero) + return false; + + var handle = Service.OpenService(manager, serviceName, + Service.SERVICE_ACCESS.SERVICE_QUERY_STATUS); + + if (handle == IntPtr.Zero) + return true; + + Service.SERVICE_STATUS_PROCESS status = new Service.SERVICE_STATUS_PROCESS(); + + if (!Service.QueryServiceStatusEx(handle, 0, ref status, Marshal.SizeOf(status), out int retLen)) + { + Service.CloseServiceHandle(handle); + Service.CloseServiceHandle(manager); + return false; + } + + Service.CloseServiceHandle(handle); + Service.CloseServiceHandle(manager); + + pending = status.CurrentState == Service.SERVICE_STATE.StopPending; + return pending || status.CurrentState == Service.SERVICE_STATE.Stopped; + } + + public static int GetServiceProcessId(string serviceName) + { + var manager = Service.OpenSCManager(null, null, Service.SCM_ACCESS.SC_MANAGER_ENUMERATE_SERVICE); + if (manager == IntPtr.Zero) + throw new Exception("Error opening service manager."); + + var handle = Service.OpenService(manager, serviceName, + Service.SERVICE_ACCESS.SERVICE_QUERY_STATUS); + + Service.SERVICE_STATUS_PROCESS status = new Service.SERVICE_STATUS_PROCESS(); + + if (!Service.QueryServiceStatusEx(handle, 0, ref status, Marshal.SizeOf(status), out int retLen)) + { + Service.CloseServiceHandle(handle); + Service.CloseServiceHandle(manager); + throw new Exception("Error querying service ProcessId: " + Marshal.GetLastWin32Error()); + } + Service.CloseServiceHandle(handle); + Service.CloseServiceHandle(manager); + + return status.ProcessID == 0 ? -1 : status.ProcessID; + } + public static IEnumerable GetServicesFromProcessId(int processId) + { + return GetServices(Service.SERVICE_TYPE.SERVICE_WIN32_OWN_PROCESS | + Service.SERVICE_TYPE.SERVICE_WIN32_SHARE_PROCESS).Where(x => x.ServiceStatusProcess.ProcessID == processId).Select(x => x.lpServiceName); + } + private static Service.ENUM_SERVICE_STATUS_PROCESS[] GetServices(Service.SERVICE_TYPE serviceTypes) + { + IntPtr handle = Service.OpenSCManager(null, null, Service.SCM_ACCESS.SC_MANAGER_ENUMERATE_SERVICE); + if (handle == IntPtr.Zero) + throw new Exception("Could not open service manager."); + + int iBytesNeeded = 0; + int iServicesReturned = 0; + int iResumeHandle = 0; + + Service.EnumServicesStatusEx(handle, + 0, + Service.SERVICE_TYPE.SERVICE_WIN32_OWN_PROCESS | Service.SERVICE_TYPE.SERVICE_WIN32_SHARE_PROCESS, + 1, + IntPtr.Zero, + 0, + out iBytesNeeded, + out iServicesReturned, + ref iResumeHandle, + null); + + IntPtr buffer = Marshal.AllocHGlobal((int)iBytesNeeded); + + Service.EnumServicesStatusEx(handle, + 0, + Service.SERVICE_TYPE.SERVICE_WIN32_OWN_PROCESS | Service.SERVICE_TYPE.SERVICE_WIN32_SHARE_PROCESS, + 1, + buffer, + iBytesNeeded, + out iBytesNeeded, + out iServicesReturned, + ref iResumeHandle, + null); + + var services = new Service.ENUM_SERVICE_STATUS_PROCESS[iServicesReturned]; + var serviceSize = Marshal.SizeOf(typeof(Service.ENUM_SERVICE_STATUS_PROCESS)); + for (int i = 0; i < iServicesReturned; i++) + { + var servicePtr = new IntPtr(buffer.ToInt64() + i * serviceSize); + services[i] = Marshal.PtrToStructure(servicePtr); + } + + Marshal.FreeHGlobal(buffer); + + return services; + } + } + + public static class Tokens + { + [DllImport("advapi32.dll", SetLastError = true)] + public static extern bool OpenProcessToken(IntPtr hProcess, TokenAccessFlags DesiredAccess, + out TokensEx.SafeTokenHandle hToken); + + [DllImport("advapi32.dll", SetLastError = true)] + public static extern bool GetTokenInformation(TokensEx.SafeTokenHandle TokenHandle, + TOKEN_INFORMATION_CLASS TokenInformationClass, IntPtr TokenInformation, int TokenInformationLength, + out int ReturnLength); + + [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool SetTokenInformation( + TokensEx.SafeTokenHandle hToken, + TOKEN_INFORMATION_CLASS tokenInfoClass, + IntPtr pTokenInfo, + int tokenInfoLength); + + [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool SetTokenInformation( + TokensEx.SafeTokenHandle hToken, + TOKEN_INFORMATION_CLASS tokenInfoClass, + ref uint pTokenInfo, + int tokenInfoLength); + + [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool DuplicateTokenEx(TokensEx.SafeTokenHandle hExistingToken, TokenAccessFlags dwDesiredAccess, + IntPtr lpTokenAttributes, SECURITY_IMPERSONATION_LEVEL ImpersonationLevel, TOKEN_TYPE TokenType, + out TokensEx.SafeTokenHandle phNewToken); + + [DllImport("advapi32.dll", SetLastError = true)] + public static extern bool ImpersonateLoggedOnUser(TokensEx.SafeTokenHandle hToken); + + [DllImport("advapi32.dll", SetLastError = true)] + public static extern bool LookupPrivilegeValue(IntPtr lpSystemName, string lpName, out LUID lpLuid); + + [DllImport("ntdll.dll", SetLastError = true)] + public static extern IntPtr RtlAdjustPrivilege(LUID privilege, bool bEnablePrivilege, bool isThreadPrivilege, + out bool previousValue); + + [DllImport("ntdll.dll")] + public static extern int ZwCreateToken(out TokensEx.SafeTokenHandle TokenHandle, TokenAccessFlags DesiredAccess, + ref OBJECT_ATTRIBUTES ObjectAttributes, TOKEN_TYPE TokenType, ref LUID AuthenticationId, + ref LARGE_INTEGER ExpirationTime, ref TOKEN_USER TokenUser, ref TOKEN_GROUPS TokenGroups, + ref TOKEN_PRIVILEGES TokenPrivileges, ref TOKEN_OWNER TokenOwner, + ref TOKEN_PRIMARY_GROUP TokenPrimaryGroup, ref TOKEN_DEFAULT_DACL TokenDefaultDacl, + ref TOKEN_SOURCE TokenSource); + + [StructLayout(LayoutKind.Sequential)] + public struct OBJECT_ATTRIBUTES : IDisposable + { + public int Length; + public IntPtr RootDirectory; + private IntPtr objectName; + public uint Attributes; + public IntPtr SecurityDescriptor; + public IntPtr SecurityQualityOfService; + + public OBJECT_ATTRIBUTES(string name, uint attrs) + { + Length = 0; + RootDirectory = IntPtr.Zero; + objectName = IntPtr.Zero; + Attributes = attrs; + SecurityDescriptor = IntPtr.Zero; + SecurityQualityOfService = IntPtr.Zero; + Length = Marshal.SizeOf(this); + ObjectName = new UNICODE_STRING(name); + } + + public UNICODE_STRING ObjectName + { + get => (UNICODE_STRING)Marshal.PtrToStructure(objectName, typeof(UNICODE_STRING)); + set + { + var fDeleteOld = objectName != IntPtr.Zero; + if (!fDeleteOld) objectName = Marshal.AllocHGlobal(Marshal.SizeOf(value)); + Marshal.StructureToPtr(value, objectName, fDeleteOld); + } + } + + public void Dispose() + { + if (objectName != IntPtr.Zero) + { + Marshal.DestroyStructure(objectName, typeof(UNICODE_STRING)); + Marshal.FreeHGlobal(objectName); + objectName = IntPtr.Zero; + } + } + } + + [Flags] + public enum TokenAccessFlags : uint + { + TOKEN_ADJUST_DEFAULT = 0x0080, + TOKEN_ADJUST_GROUPS = 0x0040, + TOKEN_ADJUST_PRIVILEGES = 0x0020, + TOKEN_ADJUST_SESSIONID = 0x0100, + TOKEN_ASSIGN_PRIMARY = 0x0001, + TOKEN_DUPLICATE = 0x0002, + TOKEN_EXECUTE = 0x00020000, + TOKEN_IMPERSONATE = 0x0004, + TOKEN_QUERY = 0x0008, + TOKEN_QUERY_SOURCE = 0x0010, + TOKEN_READ = 0x00020008, + TOKEN_WRITE = 0x000200E0, + TOKEN_ALL_ACCESS = 0x000F01FF, + MAXIMUM_ALLOWED = 0x02000000 + } + + public enum TOKEN_TYPE + { + TokenPrimary = 1, + TokenImpersonation + } + + public enum TOKEN_INFORMATION_CLASS + { + /// + /// The buffer receives a TOKEN_USER structure that contains the user account of the token. + /// + TokenUser = 1, + + /// + /// The buffer receives a TOKEN_GROUPS structure that contains the group accounts associated with the token. + /// + TokenGroups, + + /// + /// The buffer receives a TOKEN_PRIVILEGES structure that contains the privileges of the token. + /// + TokenPrivileges, + + /// + /// The buffer receives a TOKEN_OWNER structure that contains the default owner security identifier (SID) for newly created objects. + /// + TokenOwner, + + /// + /// The buffer receives a TOKEN_PRIMARY_GROUP structure that contains the default primary group SID for newly created objects. + /// + TokenPrimaryGroup, + + /// + /// The buffer receives a TOKEN_DEFAULT_DACL structure that contains the default DACL for newly created objects. + /// + TokenDefaultDacl, + + /// + /// The buffer receives a TOKEN_SOURCE structure that contains the source of the token. TOKEN_QUERY_SOURCE access is needed to retrieve this information. + /// + TokenSource, + + /// + /// The buffer receives a TOKEN_TYPE value that indicates whether the token is a primary or impersonation token. + /// + TokenType, + + /// + /// The buffer receives a SECURITY_IMPERSONATION_LEVEL value that indicates the impersonation level of the token. If the access token is not an impersonation token, the function fails. + /// + TokenImpersonationLevel, + + /// + /// The buffer receives a TOKEN_STATISTICS structure that contains various token statistics. + /// + TokenStatistics, + + /// + /// The buffer receives a TOKEN_GROUPS structure that contains the list of restricting SIDs in a restricted token. + /// + TokenRestrictedSids, + + /// + /// The buffer receives a DWORD value that indicates the Terminal Services session identifier that is associated with the token. + /// + TokenSessionId, + + /// + /// The buffer receives a TOKEN_GROUPS_AND_PRIVILEGES structure that contains the user SID, the group accounts, the restricted SIDs, and the authentication ID associated with the token. + /// + TokenGroupsAndPrivileges, + + /// + /// Reserved. + /// + TokenSessionReference, + + /// + /// The buffer receives a DWORD value that is nonzero if the token includes the SANDBOX_INERT flag. + /// + TokenSandBoxInert, + + /// + /// Reserved. + /// + TokenAuditPolicy, + + /// + /// The buffer receives a TOKEN_ORIGIN value. + /// + TokenOrigin, + + /// + /// The buffer receives a TOKEN_ELEVATION_TYPE value that specifies the elevation level of the token. + /// + TokenElevationType, + + /// + /// The buffer receives a TOKEN_LINKED_TOKEN structure that contains a handle to another token that is linked to this token. + /// + TokenLinkedToken, + + /// + /// The buffer receives a TOKEN_ELEVATION structure that specifies whether the token is elevated. + /// + TokenElevation, + + /// + /// The buffer receives a DWORD value that is nonzero if the token has ever been filtered. + /// + TokenHasRestrictions, + + /// + /// The buffer receives a TOKEN_ACCESS_INFORMATION structure that specifies security information contained in the token. + /// + TokenAccessInformation, + + /// + /// The buffer receives a DWORD value that is nonzero if virtualization is allowed for the token. + /// + TokenVirtualizationAllowed, + + /// + /// The buffer receives a DWORD value that is nonzero if virtualization is enabled for the token. + /// + TokenVirtualizationEnabled, + + /// + /// The buffer receives a TOKEN_MANDATORY_LABEL structure that specifies the token's integrity level. + /// + TokenIntegrityLevel, + + /// + /// The buffer receives a DWORD value that is nonzero if the token has the UIAccess flag set. + /// + TokenUIAccess, + + /// + /// The buffer receives a TOKEN_MANDATORY_POLICY structure that specifies the token's mandatory integrity policy. + /// + TokenMandatoryPolicy, + + /// + /// The buffer receives the token's logon security identifier (SID). + /// + TokenLogonSid, + + /// + /// The maximum value for this enumeration + /// + MaxTokenInfoClass + } + + [StructLayout(LayoutKind.Sequential)] + public struct TOKEN_GROUPS + { + public int GroupCount; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] + public SID.SID_AND_ATTRIBUTES[] Groups; + + public TOKEN_GROUPS(int privilegeCount) + { + GroupCount = privilegeCount; + Groups = new SID.SID_AND_ATTRIBUTES[32]; + } + } + + public struct TOKEN_PRIMARY_GROUP + { + public IntPtr PrimaryGroup; // PSID + + public TOKEN_PRIMARY_GROUP(IntPtr _sid) + { + PrimaryGroup = _sid; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct TOKEN_SOURCE + { + public TOKEN_SOURCE(string name) + { + SourceName = new byte[8]; + Encoding.GetEncoding(1252).GetBytes(name, 0, name.Length, SourceName, 0); + if (!SID.AllocateLocallyUniqueId(out SourceIdentifier)) throw new Win32Exception(); + } + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] SourceName; + + public LUID SourceIdentifier; + } + + [StructLayout(LayoutKind.Sequential)] + public struct TOKEN_USER + { + public SID.SID_AND_ATTRIBUTES User; + + public TOKEN_USER(IntPtr _sid) + { + User = new SID.SID_AND_ATTRIBUTES { Sid = _sid, Attributes = 0 }; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct TOKEN_OWNER + { + public IntPtr Owner; // PSID + + public TOKEN_OWNER(IntPtr _owner) + { + Owner = _owner; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct TOKEN_DEFAULT_DACL + { + public IntPtr DefaultDacl; // PACL + } + + [StructLayout(LayoutKind.Sequential)] + public struct TOKEN_PRIVILEGES + { + public int PrivilegeCount; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 36)] + public LUID_AND_ATTRIBUTES[] Privileges; + + public TOKEN_PRIVILEGES(int privilegeCount) + { + PrivilegeCount = privilegeCount; + Privileges = new LUID_AND_ATTRIBUTES[36]; + } + } + + public enum SECURITY_IMPERSONATION_LEVEL + { + SecurityAnonymous, + SecurityIdentification, + SecurityImpersonation, + SecurityDelegation + } + + [StructLayout(LayoutKind.Sequential)] + public struct SECURITY_QUALITY_OF_SERVICE + { + public int Length; + public SECURITY_IMPERSONATION_LEVEL ImpersonationLevel; + public byte ContextTrackingMode; + public byte EffectiveOnly; + + public SECURITY_QUALITY_OF_SERVICE(SECURITY_IMPERSONATION_LEVEL _impersonationLevel, + byte _contextTrackingMode, byte _effectiveOnly) + { + Length = 0; + ImpersonationLevel = _impersonationLevel; + ContextTrackingMode = _contextTrackingMode; + EffectiveOnly = _effectiveOnly; + Length = Marshal.SizeOf(this); + } + } + + [Flags] + public enum SE_PRIVILEGE_ATTRIBUTES : uint + { + SE_PRIVILEGE_ENABLED_BY_DEFAULT = 0x00000001, + SE_PRIVILEGE_ENABLED = 0x00000002, + SE_PRIVILEGE_USED_FOR_ACCESS = 0x80000000 + } + + [Flags] + public enum SE_GROUP_ATTRIBUTES : uint + { + SE_GROUP_MANDATORY = 0x00000001, + SE_GROUP_ENABLED_BY_DEFAULT = 0x00000002, + SE_GROUP_ENABLED = 0x00000004, + SE_GROUP_OWNER = 0x00000008, + SE_GROUP_USE_FOR_DENY_ONLY = 0x00000010, + SE_GROUP_INTEGRITY = 0x00000020, + SE_GROUP_INTEGRITY_ENABLED = 0x00000040, + SE_GROUP_RESOURCE = 0x20000000, + SE_GROUP_LOGON_ID = 0xC0000000, + } + + [StructLayout(LayoutKind.Sequential)] + public struct TOKEN_MANDATORY_LABEL + { + public SID.SID_AND_ATTRIBUTES Label; + } + + public const byte SECURITY_STATIC_TRACKING = 0; + public static readonly LUID ANONYMOUS_LOGON_LUID = new LUID { LowPart = 0x3e6, HighPart = 0 }; + public static readonly LUID SYSTEM_LUID = new LUID { LowPart = 0x3e7, HighPart = 0 }; + public const string SE_CREATE_TOKEN_NAME = "SeCreateTokenPrivilege"; + public const string SE_ASSIGNPRIMARYTOKEN_NAME = "SeAssignPrimaryTokenPrivilege"; + public const string SE_LOCK_MEMORY_NAME = "SeLockMemoryPrivilege"; + public const string SE_INCREASE_QUOTA_NAME = "SeIncreaseQuotaPrivilege"; + public const string SE_MACHINE_ACCOUNT_NAME = "SeMachineAccountPrivilege"; + public const string SE_TCB_NAME = "SeTcbPrivilege"; + public const string SE_SECURITY_NAME = "SeSecurityPrivilege"; + public const string SE_TAKE_OWNERSHIP_NAME = "SeTakeOwnershipPrivilege"; + public const string SE_LOAD_DRIVER_NAME = "SeLoadDriverPrivilege"; + public const string SE_SYSTEM_PROFILE_NAME = "SeSystemProfilePrivilege"; + public const string SE_SYSTEMTIME_NAME = "SeSystemtimePrivilege"; + public const string SE_PROFILE_SINGLE_PROCESS_NAME = "SeProfileSingleProcessPrivilege"; + public const string SE_INCREASE_BASE_PRIORITY_NAME = "SeIncreaseBasePriorityPrivilege"; + public const string SE_CREATE_PAGEFILE_NAME = "SeCreatePagefilePrivilege"; + public const string SE_CREATE_PERMANENT_NAME = "SeCreatePermanentPrivilege"; + public const string SE_BACKUP_NAME = "SeBackupPrivilege"; + public const string SE_RESTORE_NAME = "SeRestorePrivilege"; + public const string SE_SHUTDOWN_NAME = "SeShutdownPrivilege"; + public const string SE_DEBUG_NAME = "SeDebugPrivilege"; + public const string SE_AUDIT_NAME = "SeAuditPrivilege"; + public const string SE_SYSTEM_ENVIRONMENT_NAME = "SeSystemEnvironmentPrivilege"; + public const string SE_CHANGE_NOTIFY_NAME = "SeChangeNotifyPrivilege"; + public const string SE_REMOTE_SHUTDOWN_NAME = "SeRemoteShutdownPrivilege"; + public const string SE_UNDOCK_NAME = "SeUndockPrivilege"; + public const string SE_SYNC_AGENT_NAME = "SeSyncAgentPrivilege"; + public const string SE_ENABLE_DELEGATION_NAME = "SeEnableDelegationPrivilege"; + public const string SE_MANAGE_VOLUME_NAME = "SeManageVolumePrivilege"; + public const string SE_IMPERSONATE_NAME = "SeImpersonatePrivilege"; + public const string SE_CREATE_GLOBAL_NAME = "SeCreateGlobalPrivilege"; + public const string SE_TRUSTED_CREDMAN_ACCESS_NAME = "SeTrustedCredManAccessPrivilege"; + public const string SE_RELABEL_NAME = "SeRelabelPrivilege"; + public const string SE_INCREASE_WORKING_SET_NAME = "SeIncreaseWorkingSetPrivilege"; + public const string SE_TIME_ZONE_NAME = "SeTimeZonePrivilege"; + public const string SE_CREATE_SYMBOLIC_LINK_NAME = "SeCreateSymbolicLinkPrivilege"; + + public const string SE_DELEGATE_SESSION_USER_IMPERSONATE_NAME = + "SeDelegateSessionUserImpersonatePrivilege"; + } + + public static class TokensEx + { + public sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid + { + // A default constructor is required for P/Invoke to instantiate the class + public SafeTokenHandle(IntPtr preexistingHandle) + : base(ownsHandle: true) + { + this.SetHandle(preexistingHandle); + } + public SafeTokenHandle() + : base(ownsHandle: true) + { + } + + protected override bool ReleaseHandle() + { + return Win32.CloseHandle(handle); + } + } + + public static bool CreateTokenPrivileges(string[] privs, out Tokens.TOKEN_PRIVILEGES tokenPrivileges) + { + var sizeOfStruct = Marshal.SizeOf(typeof(Tokens.TOKEN_PRIVILEGES)); + var pPrivileges = Marshal.AllocHGlobal(sizeOfStruct); + tokenPrivileges = (Tokens.TOKEN_PRIVILEGES)Marshal.PtrToStructure( + pPrivileges, typeof(Tokens.TOKEN_PRIVILEGES)); + tokenPrivileges.PrivilegeCount = privs.Length; + for (var idx = 0; idx < tokenPrivileges.PrivilegeCount; idx++) + { + if (!Win32.Tokens.LookupPrivilegeValue(IntPtr.Zero, privs[idx], out var luid)) return false; + tokenPrivileges.Privileges[idx].Attributes = + (uint)(Tokens.SE_PRIVILEGE_ATTRIBUTES.SE_PRIVILEGE_ENABLED | + Tokens.SE_PRIVILEGE_ATTRIBUTES.SE_PRIVILEGE_ENABLED_BY_DEFAULT); + tokenPrivileges.Privileges[idx].Luid = luid; + } + + return true; + } + + public static void AdjustCurrentPrivilege(string privilege) + { + Win32.Tokens.LookupPrivilegeValue(IntPtr.Zero, privilege, out LUID luid); + Win32.Tokens.RtlAdjustPrivilege(luid, true, true, out _); + } + + public static IntPtr GetInfoFromToken(SafeTokenHandle token, Win32.Tokens.TOKEN_INFORMATION_CLASS information) + { + Win32.Tokens.GetTokenInformation(token, information, IntPtr.Zero, 0, out int length); + var result = Marshal.AllocHGlobal(length); + Win32.Tokens.GetTokenInformation(token, information, result, length, out length); + return result; + } + } + + public static class Process + { + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr GetCurrentProcess(); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GetExitCodeProcess(IntPtr hProcess, out uint lpExitCode); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds); + + [DllImport("userenv.dll", SetLastError = true)] + public static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, TokensEx.SafeTokenHandle hToken, bool bInherit); + [DllImport("userenv.dll", SetLastError = true)] + public static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment); + + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern bool CreateProcessAsUser(Win32.TokensEx.SafeTokenHandle hToken, string lpApplicationName, StringBuilder lpCommandLine, SECURITY_ATTRIBUTES lpProcessAttributes, SECURITY_ATTRIBUTES lpThreadAttributes, + bool bInheritHandles, int dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, STARTUPINFO lpStartupInfo, PROCESS_INFORMATION lpProcessInformation); + + [DllImport("advapi32", SetLastError = true, CharSet = CharSet.Auto)] + public static extern bool CreateProcessWithToken(TokensEx.SafeTokenHandle hToken, LogonFlags dwLogonFlags, + string lpApplicationName, string lpCommandLine, ProcessCreationFlags dwCreationFlags, + IntPtr lpEnvironment, string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, + out PROCESS_INFORMATION lpProcessInformation); + + public enum LogonFlags + { + WithProfile = 1, + NetCredentialsOnly + } + + [StructLayout(LayoutKind.Sequential)] + public struct PROCESS_INFORMATION + { + public IntPtr hProcess; + public IntPtr hThread; + public int dwProcessId; + public int dwThreadId; + } + + [Flags] + public enum ProcessCreationFlags : uint + { + DEBUG_PROCESS = 0x00000001, + DEBUG_ONLY_THIS_PROCESS = 0x00000002, + CREATE_SUSPENDED = 0x00000004, + DETACHED_PROCESS = 0x00000008, + CREATE_NEW_CONSOLE = 0x00000010, + CREATE_NEW_PROCESS_GROUP = 0x00000200, + CREATE_UNICODE_ENVIRONMENT = 0x00000400, + CREATE_SEPARATE_WOW_VDM = 0x00000800, + CREATE_SHARED_WOW_VDM = 0x00001000, + INHERIT_PARENT_AFFINITY = 0x00010000, + CREATE_PROTECTED_PROCESS = 0x00040000, + EXTENDED_STARTUPINFO_PRESENT = 0x00080000, + CREATE_BREAKAWAY_FROM_JOB = 0x01000000, + CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000, + CREATE_DEFAULT_ERROR_MODE = 0x04000000, + CREATE_NO_WINDOW = 0x08000000 + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct STARTUPINFO + { + public int cb; + public string lpReserved; + public string lpDesktop; + public string lpTitle; + public int dwX; + public int dwY; + public int dwXSize; + public int dwYSize; + public int dwXCountChars; + public int dwYCountChars; + public int dwFillAttribute; + public int dwFlags; + public short wShowWindow; + public short cbReserved2; + public IntPtr lpReserved2; + public IntPtr hStdInput; + public IntPtr hStdOutput; + public IntPtr hStdError; + } + } + + public static class IO + { + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern IntPtr CreateFile([MarshalAs(UnmanagedType.LPTStr)] string filename, + [MarshalAs(UnmanagedType.U4)] FileAccess access, [MarshalAs(UnmanagedType.U4)] FileShare share, + IntPtr securityAttributes, // optional SECURITY_ATTRIBUTES struct or IntPtr.Zero + [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition, + [MarshalAs(UnmanagedType.U4)] FileAttributes flagsAndAttributes, IntPtr templateFile); + + [Flags] + public enum FileAccess : uint + { + AccessSystemSecurity = 0x1000000, + MaximumAllowed = 0x2000000, + Delete = 0x10000, + ReadControl = 0x20000, + WriteDAC = 0x40000, + WriteOwner = 0x80000, + Synchronize = 0x100000, + StandardRightsRequired = 0xF0000, + StandardRightsRead = ReadControl, + StandardRightsWrite = ReadControl, + StandardRightsExecute = ReadControl, + StandardRightsAll = 0x1F0000, + SpecificRightsAll = 0xFFFF, + FILE_READ_DATA = 0x0001, // file & pipe + FILE_LIST_DIRECTORY = 0x0001, // directory + FILE_WRITE_DATA = 0x0002, // file & pipe + FILE_ADD_FILE = 0x0002, // directory + FILE_APPEND_DATA = 0x0004, // file + FILE_ADD_SUBDIRECTORY = 0x0004, // directory + FILE_CREATE_PIPE_INSTANCE = 0x0004, // named pipe + FILE_READ_EA = 0x0008, // file & directory + FILE_WRITE_EA = 0x0010, // file & directory + FILE_EXECUTE = 0x0020, // file + FILE_TRAVERSE = 0x0020, // directory + FILE_DELETE_CHILD = 0x0040, // directory + FILE_READ_ATTRIBUTES = 0x0080, // all + FILE_WRITE_ATTRIBUTES = 0x0100, // all + + // + // Generic Section + // + GenericRead = 0x80000000, + GenericWrite = 0x40000000, + GenericExecute = 0x20000000, + GenericAll = 0x10000000, + SPECIFIC_RIGHTS_ALL = 0x00FFFF, + FILE_ALL_ACCESS = StandardRightsRequired | Synchronize | 0x1FF, + + FILE_GENERIC_READ = StandardRightsRead | FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA | + Synchronize, + + FILE_GENERIC_WRITE = StandardRightsWrite | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | + FILE_APPEND_DATA | Synchronize, + FILE_GENERIC_EXECUTE = StandardRightsExecute | FILE_READ_ATTRIBUTES | FILE_EXECUTE | Synchronize + } + + [Flags] + public enum FileShare : uint + { + None = 0x00000000, + Read = 0x00000001, + Write = 0x00000002, + Delete = 0x00000004 + } + + public enum FileMode : uint + { + New = 1, + CreateAlways = 2, + OpenExisting = 3, + OpenAlways = 4, + TruncateExisting = 5 + } + + [Flags] + public enum FileAttributes : uint + { + Readonly = 0x00000001, + Hidden = 0x00000002, + System = 0x00000004, + Directory = 0x00000010, + Archive = 0x00000020, + Device = 0x00000040, + Normal = 0x00000080, + Temporary = 0x00000100, + SparseFile = 0x00000200, + ReparsePoint = 0x00000400, + Compressed = 0x00000800, + Offline = 0x00001000, + NotContentIndexed = 0x00002000, + Encrypted = 0x00004000, + Write_Through = 0x80000000, + Overlapped = 0x40000000, + NoBuffering = 0x20000000, + RandomAccess = 0x10000000, + SequentialScan = 0x08000000, + DeleteOnClose = 0x04000000, + BackupSemantics = 0x02000000, + PosixSemantics = 0x01000000, + OpenReparsePoint = 0x00200000, + OpenNoRecall = 0x00100000, + FirstPipeInstance = 0x00080000 + } + } + + public static class SID + { + [DllImport("advapi32.dll", SetLastError = true)] + public static extern bool AllocateAndInitializeSid(ref SID_IDENTIFIER_AUTHORITY pIdentifierAuthority, + byte nSubAuthorityCount, int dwSubAuthority0, int dwSubAuthority1, int dwSubAuthority2, + int dwSubAuthority3, int dwSubAuthority4, int dwSubAuthority5, int dwSubAuthority6, int dwSubAuthority7, + out IntPtr pSid); + + [DllImport("advapi32.dll")] + public static extern bool AllocateLocallyUniqueId(out LUID allocated); + + [DllImport("advapi32", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool ConvertSidToStringSid(IntPtr pSid, out string strSid); + + [DllImport("advapi32.dll", SetLastError = true)] + public static extern bool ConvertStringSidToSid(string StringSid, out IntPtr ptrSid); + + [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern int GetLengthSid(IntPtr pSid); + + [DllImport("advapi32.dll")] + public static extern IntPtr FreeSid(IntPtr pSid); + + [StructLayout(LayoutKind.Sequential)] + public struct SID_AND_ATTRIBUTES + { + public IntPtr Sid; + public uint Attributes; + } + + [StructLayout(LayoutKind.Sequential)] + public struct SID_IDENTIFIER_AUTHORITY + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6, ArraySubType = UnmanagedType.I1)] + public byte[] Value; + + public SID_IDENTIFIER_AUTHORITY(byte[] value) + { + this.Value = value; + } + } + + public enum SECURITY_MANDATORY_LABEL + { + Untrusted = 0x00000000, + Low = 0x00001000, + Medium = 0x00002000, + MediumPlus = 0x00002100, + High = 0x00003000, + System = 0x00004000, + } + + public static SID_IDENTIFIER_AUTHORITY SECURITY_MANDATORY_LABEL_AUTHORITY = + new SID_IDENTIFIER_AUTHORITY(new byte[] { 0, 0, 0, 0, 0, 16 }); + public const int NtSecurityAuthority = 5; + public const int AuthenticatedUser = 11; + + public const string DOMAIN_ALIAS_RID_ADMINS = "S-1-5-32-544"; + public const string DOMAIN_ALIAS_RID_LOCAL_AND_ADMIN_GROUP = "S-1-5-114"; + public const string TRUSTED_INSTALLER_RID = + "S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464"; + + public const string INTEGRITY_UNTRUSTED_SID = "S-1-16-0"; + public const string INTEGRITY_LOW_SID = "S-1-16-4096"; + public const string INTEGRITY_MEDIUM_SID = "S-1-16-8192"; + public const string INTEGRITY_MEDIUMPLUS_SID = "S-1-16-8448"; + public const string INTEGRITY_HIGH_SID = "S-1-16-12288"; + public const string INTEGRITY_SYSTEM_SID = "S-1-16-16384"; + public const string INTEGRITY_PROTECTEDPROCESS_SID = "S-1-16-20480"; + } + + public static class WTS + { + [DllImport("wtsapi32.dll", SetLastError = true)] + public static extern int WTSEnumerateSessions(IntPtr hServer, int Reserved, int Version, + ref IntPtr ppSessionInfo, ref int pCount); + + [DllImport("wtsapi32.dll", SetLastError = true)] + public static extern bool WTSEnumerateProcesses(IntPtr serverHandle, Int32 reserved, Int32 version, + ref IntPtr ppProcessInfo, ref Int32 pCount); + + [DllImport("kernel32.dll")] + public static extern uint WTSGetActiveConsoleSessionId(); + + [DllImport("wtsapi32.dll", SetLastError = true)] + public static extern bool WTSQueryUserToken(UInt32 sessionId, out TokensEx.SafeTokenHandle Token); + + [DllImport("wtsapi32.dll")] + public static extern void WTSFreeMemory(IntPtr pMemory); + + [StructLayout(LayoutKind.Sequential)] + public struct WTS_SESSION_INFO + { + public Int32 SessionID; + [MarshalAs(UnmanagedType.LPStr)] + public String pWinStationName; + public WTS_CONNECTSTATE_CLASS State; + } + + public enum WTS_CONNECTSTATE_CLASS + { + WTSActive, + WTSConnected, + WTSConnectQuery, + WTSShadow, + WTSDisconnected, + WTSIdle, + WTSListen, + WTSReset, + WTSDown, + WTSInit + } + + public struct WTS_PROCESS_INFO + { + public int SessionID; + public int ProcessID; + public IntPtr ProcessName; + public IntPtr UserSid; + } + } + + public static class LSA + { + [DllImport("secur32.dll", SetLastError = false)] + public static extern uint LsaFreeReturnBuffer(IntPtr buffer); + + [DllImport("secur32.dll", SetLastError = false)] + public static extern uint LsaEnumerateLogonSessions(out ulong LogonSessionCount, + out IntPtr LogonSessionList); + + [DllImport("secur32.dll", SetLastError = false)] + public static extern uint LsaGetLogonSessionData(IntPtr luid, out IntPtr ppLogonSessionData); + + [StructLayout(LayoutKind.Sequential)] + public struct LSA_UNICODE_STRING + { + public UInt16 Length; + public UInt16 MaximumLength; + public IntPtr buffer; + } + + [StructLayout(LayoutKind.Sequential)] + public struct SECURITY_LOGON_SESSION_DATA + { + public UInt32 Size; + public LUID LoginID; + public LSA_UNICODE_STRING Username; + public LSA_UNICODE_STRING LoginDomain; + public LSA_UNICODE_STRING AuthenticationPackage; + public UInt32 LogonType; + public UInt32 Session; + public IntPtr PSiD; + public UInt64 LoginTime; + public LSA_UNICODE_STRING LogonServer; + public LSA_UNICODE_STRING DnsDomainName; + public LSA_UNICODE_STRING Upn; + } + + public enum SECURITY_LOGON_TYPE : uint + { + Interactive = 2, //The security principal is logging on interactively. + Network, //The security principal is logging using a network. + Batch, //The logon is for a batch process. + Service, //The logon is for a service account. + Proxy, //Not supported. + Unlock, //The logon is an attempt to unlock a workstation. + NetworkCleartext, //The logon is a network logon with cleartext credentials. + NewCredentials, // Allows the caller to clone its current token and specify new credentials for outbound connections. + RemoteInteractive, // A terminal server session that is both remote and interactive. + CachedInteractive, // Attempt to use the cached credentials without going out across the network. + CachedRemoteInteractive, // Same as RemoteInteractive, except used publicly for auditing purposes. + CachedUnlock // The logon is an attempt to unlock a workstation. + } + } + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr LocalFree(IntPtr hMem); + + [DllImport("kernel32.dll", SetLastError = true)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [SuppressUnmanagedCodeSecurity] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CloseHandle(IntPtr hObject); + + [StructLayout(LayoutKind.Sequential)] + public struct LUID + { + public UInt32 LowPart; + public UInt32 HighPart; + } + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct LUID_AND_ATTRIBUTES + { + public LUID Luid; + public UInt32 Attributes; + } + + [StructLayout(LayoutKind.Sequential)] + public class SECURITY_ATTRIBUTES + { + public int nLength = 12; + public SafeLocalMemHandle lpSecurityDescriptor = new SafeLocalMemHandle(IntPtr.Zero, false); + public bool bInheritHandle; + } + + [HostProtection(MayLeakOnAbort = true)] + [SuppressUnmanagedCodeSecurityAttribute] + public sealed class SafeLocalMemHandle : SafeHandleZeroOrMinusOneIsInvalid + { + [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] + internal SafeLocalMemHandle(IntPtr existingHandle, bool ownsHandle) : base(ownsHandle) + { + SetHandle(existingHandle); + } + [DllImport("kernel32.dll")] + [ResourceExposure(ResourceScope.None)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + private static extern IntPtr LocalFree(IntPtr hMem); + override protected bool ReleaseHandle() + { + return LocalFree(handle) == IntPtr.Zero; + } + } + + [StructLayout(LayoutKind.Explicit, Size = 8)] + public struct LARGE_INTEGER + { + [FieldOffset(0)] public int Low; + [FieldOffset(4)] public int High; + [FieldOffset(0)] public long QuadPart; + + public LARGE_INTEGER(long _quad) + { + Low = 0; + High = 0; + QuadPart = _quad; + } + + public long ToInt64() + { + return ((long)High << 32) | (uint)Low; + } + + public static LARGE_INTEGER FromInt64(long value) + { + return new LARGE_INTEGER { Low = (int)value, High = (int)(value >> 32) }; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct UNICODE_STRING : IDisposable + { + public ushort Length; + public ushort MaximumLength; + private IntPtr buffer; + + public UNICODE_STRING(string s) + { + Length = (ushort)(s.Length * 2); + MaximumLength = (ushort)(Length + 2); + buffer = Marshal.StringToHGlobalUni(s); + } + + public void Dispose() + { + Marshal.FreeHGlobal(buffer); + buffer = IntPtr.Zero; + } + + public override string ToString() + { + return Marshal.PtrToStringUni(buffer); + } + } + + public const int STATUS_SUCCESS = 0; + public static readonly int STATUS_INFO_LENGTH_MISMATCH = Convert.ToInt32("0xC0000004", 16); + public const int ERROR_BAD_LENGTH = 0x00000018; + public const int ERROR_INSUFFICIENT_BUFFER = 0x0000007A; + public static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); + + public enum NtStatus : uint + { + // Success + Success = 0x00000000, + Wait0 = 0x00000000, + Wait1 = 0x00000001, + Wait2 = 0x00000002, + Wait3 = 0x00000003, + Wait63 = 0x0000003f, + Abandoned = 0x00000080, + AbandonedWait0 = 0x00000080, + AbandonedWait1 = 0x00000081, + AbandonedWait2 = 0x00000082, + AbandonedWait3 = 0x00000083, + AbandonedWait63 = 0x000000bf, + UserApc = 0x000000c0, + KernelApc = 0x00000100, + Alerted = 0x00000101, + Timeout = 0x00000102, + Pending = 0x00000103, + Reparse = 0x00000104, + MoreEntries = 0x00000105, + NotAllAssigned = 0x00000106, + SomeNotMapped = 0x00000107, + OpLockBreakInProgress = 0x00000108, + VolumeMounted = 0x00000109, + RxActCommitted = 0x0000010a, + NotifyCleanup = 0x0000010b, + NotifyEnumDir = 0x0000010c, + NoQuotasForAccount = 0x0000010d, + PrimaryTransportConnectFailed = 0x0000010e, + PageFaultTransition = 0x00000110, + PageFaultDemandZero = 0x00000111, + PageFaultCopyOnWrite = 0x00000112, + PageFaultGuardPage = 0x00000113, + PageFaultPagingFile = 0x00000114, + CrashDump = 0x00000116, + ReparseObject = 0x00000118, + NothingToTerminate = 0x00000122, + ProcessNotInJob = 0x00000123, + ProcessInJob = 0x00000124, + ProcessCloned = 0x00000129, + FileLockedWithOnlyReaders = 0x0000012a, + FileLockedWithWriters = 0x0000012b, + + // Informational + Informational = 0x40000000, + ObjectNameExists = 0x40000000, + ThreadWasSuspended = 0x40000001, + WorkingSetLimitRange = 0x40000002, + ImageNotAtBase = 0x40000003, + RegistryRecovered = 0x40000009, + + // Warning + Warning = 0x80000000, + GuardPageViolation = 0x80000001, + DatatypeMisalignment = 0x80000002, + Breakpoint = 0x80000003, + SingleStep = 0x80000004, + BufferOverflow = 0x80000005, + NoMoreFiles = 0x80000006, + HandlesClosed = 0x8000000a, + PartialCopy = 0x8000000d, + DeviceBusy = 0x80000011, + InvalidEaName = 0x80000013, + EaListInconsistent = 0x80000014, + NoMoreEntries = 0x8000001a, + LongJump = 0x80000026, + DllMightBeInsecure = 0x8000002b, + + // Error + Error = 0xc0000000, + Unsuccessful = 0xc0000001, + NotImplemented = 0xc0000002, + InvalidInfoClass = 0xc0000003, + InfoLengthMismatch = 0xc0000004, + AccessViolation = 0xc0000005, + InPageError = 0xc0000006, + PagefileQuota = 0xc0000007, + InvalidHandle = 0xc0000008, + BadInitialStack = 0xc0000009, + BadInitialPc = 0xc000000a, + InvalidCid = 0xc000000b, + TimerNotCanceled = 0xc000000c, + InvalidParameter = 0xc000000d, + NoSuchDevice = 0xc000000e, + NoSuchFile = 0xc000000f, + InvalidDeviceRequest = 0xc0000010, + EndOfFile = 0xc0000011, + WrongVolume = 0xc0000012, + NoMediaInDevice = 0xc0000013, + NoMemory = 0xc0000017, + NotMappedView = 0xc0000019, + UnableToFreeVm = 0xc000001a, + UnableToDeleteSection = 0xc000001b, + IllegalInstruction = 0xc000001d, + AlreadyCommitted = 0xc0000021, + AccessDenied = 0xc0000022, + BufferTooSmall = 0xc0000023, + ObjectTypeMismatch = 0xc0000024, + NonContinuableException = 0xc0000025, + BadStack = 0xc0000028, + NotLocked = 0xc000002a, + NotCommitted = 0xc000002d, + InvalidParameterMix = 0xc0000030, + ObjectNameInvalid = 0xc0000033, + ObjectNameNotFound = 0xc0000034, + ObjectNameCollision = 0xc0000035, + ObjectPathInvalid = 0xc0000039, + ObjectPathNotFound = 0xc000003a, + ObjectPathSyntaxBad = 0xc000003b, + DataOverrun = 0xc000003c, + DataLate = 0xc000003d, + DataError = 0xc000003e, + CrcError = 0xc000003f, + SectionTooBig = 0xc0000040, + PortConnectionRefused = 0xc0000041, + InvalidPortHandle = 0xc0000042, + SharingViolation = 0xc0000043, + QuotaExceeded = 0xc0000044, + InvalidPageProtection = 0xc0000045, + MutantNotOwned = 0xc0000046, + SemaphoreLimitExceeded = 0xc0000047, + PortAlreadySet = 0xc0000048, + SectionNotImage = 0xc0000049, + SuspendCountExceeded = 0xc000004a, + ThreadIsTerminating = 0xc000004b, + BadWorkingSetLimit = 0xc000004c, + IncompatibleFileMap = 0xc000004d, + SectionProtection = 0xc000004e, + EasNotSupported = 0xc000004f, + EaTooLarge = 0xc0000050, + NonExistentEaEntry = 0xc0000051, + NoEasOnFile = 0xc0000052, + EaCorruptError = 0xc0000053, + FileLockConflict = 0xc0000054, + LockNotGranted = 0xc0000055, + DeletePending = 0xc0000056, + CtlFileNotSupported = 0xc0000057, + UnknownRevision = 0xc0000058, + RevisionMismatch = 0xc0000059, + InvalidOwner = 0xc000005a, + InvalidPrimaryGroup = 0xc000005b, + NoImpersonationToken = 0xc000005c, + CantDisableMandatory = 0xc000005d, + NoLogonServers = 0xc000005e, + NoSuchLogonSession = 0xc000005f, + NoSuchPrivilege = 0xc0000060, + PrivilegeNotHeld = 0xc0000061, + InvalidAccountName = 0xc0000062, + UserExists = 0xc0000063, + NoSuchUser = 0xc0000064, + GroupExists = 0xc0000065, + NoSuchGroup = 0xc0000066, + MemberInGroup = 0xc0000067, + MemberNotInGroup = 0xc0000068, + LastAdmin = 0xc0000069, + WrongPassword = 0xc000006a, + IllFormedPassword = 0xc000006b, + PasswordRestriction = 0xc000006c, + LogonFailure = 0xc000006d, + AccountRestriction = 0xc000006e, + InvalidLogonHours = 0xc000006f, + InvalidWorkstation = 0xc0000070, + PasswordExpired = 0xc0000071, + AccountDisabled = 0xc0000072, + NoneMapped = 0xc0000073, + TooManyLuidsRequested = 0xc0000074, + LuidsExhausted = 0xc0000075, + InvalidSubAuthority = 0xc0000076, + InvalidAcl = 0xc0000077, + InvalidSid = 0xc0000078, + InvalidSecurityDescr = 0xc0000079, + ProcedureNotFound = 0xc000007a, + InvalidImageFormat = 0xc000007b, + NoToken = 0xc000007c, + BadInheritanceAcl = 0xc000007d, + RangeNotLocked = 0xc000007e, + DiskFull = 0xc000007f, + ServerDisabled = 0xc0000080, + ServerNotDisabled = 0xc0000081, + TooManyGuidsRequested = 0xc0000082, + GuidsExhausted = 0xc0000083, + InvalidIdAuthority = 0xc0000084, + AgentsExhausted = 0xc0000085, + InvalidVolumeLabel = 0xc0000086, + SectionNotExtended = 0xc0000087, + NotMappedData = 0xc0000088, + ResourceDataNotFound = 0xc0000089, + ResourceTypeNotFound = 0xc000008a, + ResourceNameNotFound = 0xc000008b, + ArrayBoundsExceeded = 0xc000008c, + FloatDenormalOperand = 0xc000008d, + FloatDivideByZero = 0xc000008e, + FloatInexactResult = 0xc000008f, + FloatInvalidOperation = 0xc0000090, + FloatOverflow = 0xc0000091, + FloatStackCheck = 0xc0000092, + FloatUnderflow = 0xc0000093, + IntegerDivideByZero = 0xc0000094, + IntegerOverflow = 0xc0000095, + PrivilegedInstruction = 0xc0000096, + TooManyPagingFiles = 0xc0000097, + FileInvalid = 0xc0000098, + InstanceNotAvailable = 0xc00000ab, + PipeNotAvailable = 0xc00000ac, + InvalidPipeState = 0xc00000ad, + PipeBusy = 0xc00000ae, + IllegalFunction = 0xc00000af, + PipeDisconnected = 0xc00000b0, + PipeClosing = 0xc00000b1, + PipeConnected = 0xc00000b2, + PipeListening = 0xc00000b3, + InvalidReadMode = 0xc00000b4, + IoTimeout = 0xc00000b5, + FileForcedClosed = 0xc00000b6, + ProfilingNotStarted = 0xc00000b7, + ProfilingNotStopped = 0xc00000b8, + NotSameDevice = 0xc00000d4, + FileRenamed = 0xc00000d5, + CantWait = 0xc00000d8, + PipeEmpty = 0xc00000d9, + CantTerminateSelf = 0xc00000db, + InternalError = 0xc00000e5, + InvalidParameter1 = 0xc00000ef, + InvalidParameter2 = 0xc00000f0, + InvalidParameter3 = 0xc00000f1, + InvalidParameter4 = 0xc00000f2, + InvalidParameter5 = 0xc00000f3, + InvalidParameter6 = 0xc00000f4, + InvalidParameter7 = 0xc00000f5, + InvalidParameter8 = 0xc00000f6, + InvalidParameter9 = 0xc00000f7, + InvalidParameter10 = 0xc00000f8, + InvalidParameter11 = 0xc00000f9, + InvalidParameter12 = 0xc00000fa, + MappedFileSizeZero = 0xc000011e, + TooManyOpenedFiles = 0xc000011f, + Cancelled = 0xc0000120, + CannotDelete = 0xc0000121, + InvalidComputerName = 0xc0000122, + FileDeleted = 0xc0000123, + SpecialAccount = 0xc0000124, + SpecialGroup = 0xc0000125, + SpecialUser = 0xc0000126, + MembersPrimaryGroup = 0xc0000127, + FileClosed = 0xc0000128, + TooManyThreads = 0xc0000129, + ThreadNotInProcess = 0xc000012a, + TokenAlreadyInUse = 0xc000012b, + PagefileQuotaExceeded = 0xc000012c, + CommitmentLimit = 0xc000012d, + InvalidImageLeFormat = 0xc000012e, + InvalidImageNotMz = 0xc000012f, + InvalidImageProtect = 0xc0000130, + InvalidImageWin16 = 0xc0000131, + LogonServer = 0xc0000132, + DifferenceAtDc = 0xc0000133, + SynchronizationRequired = 0xc0000134, + DllNotFound = 0xc0000135, + IoPrivilegeFailed = 0xc0000137, + OrdinalNotFound = 0xc0000138, + EntryPointNotFound = 0xc0000139, + ControlCExit = 0xc000013a, + PortNotSet = 0xc0000353, + DebuggerInactive = 0xc0000354, + CallbackBypass = 0xc0000503, + PortClosed = 0xc0000700, + MessageLost = 0xc0000701, + InvalidMessage = 0xc0000702, + RequestCanceled = 0xc0000703, + RecursiveDispatch = 0xc0000704, + LpcReceiveBufferExpected = 0xc0000705, + LpcInvalidConnectionUsage = 0xc0000706, + LpcRequestsNotAllowed = 0xc0000707, + ResourceInUse = 0xc0000708, + ProcessIsProtected = 0xc0000712, + VolumeDirty = 0xc0000806, + FileCheckedOut = 0xc0000901, + CheckOutRequired = 0xc0000902, + BadFileType = 0xc0000903, + FileTooLarge = 0xc0000904, + FormsAuthRequired = 0xc0000905, + VirusInfected = 0xc0000906, + VirusDeleted = 0xc0000907, + TransactionalConflict = 0xc0190001, + InvalidTransaction = 0xc0190002, + TransactionNotActive = 0xc0190003, + TmInitializationFailed = 0xc0190004, + RmNotActive = 0xc0190005, + RmMetadataCorrupt = 0xc0190006, + TransactionNotJoined = 0xc0190007, + DirectoryNotRm = 0xc0190008, + CouldNotResizeLog = 0xc0190009, + TransactionsUnsupportedRemote = 0xc019000a, + LogResizeInvalidSize = 0xc019000b, + RemoteFileVersionMismatch = 0xc019000c, + CrmProtocolAlreadyExists = 0xc019000f, + TransactionPropagationFailed = 0xc0190010, + CrmProtocolNotFound = 0xc0190011, + TransactionSuperiorExists = 0xc0190012, + TransactionRequestNotValid = 0xc0190013, + TransactionNotRequested = 0xc0190014, + TransactionAlreadyAborted = 0xc0190015, + TransactionAlreadyCommitted = 0xc0190016, + TransactionInvalidMarshallBuffer = 0xc0190017, + CurrentTransactionNotValid = 0xc0190018, + LogGrowthFailed = 0xc0190019, + ObjectNoLongerExists = 0xc0190021, + StreamMiniversionNotFound = 0xc0190022, + StreamMiniversionNotValid = 0xc0190023, + MiniversionInaccessibleFromSpecifiedTransaction = 0xc0190024, + CantOpenMiniversionWithModifyIntent = 0xc0190025, + CantCreateMoreStreamMiniversions = 0xc0190026, + HandleNoLongerValid = 0xc0190028, + NoTxfMetadata = 0xc0190029, + LogCorruptionDetected = 0xc0190030, + CantRecoverWithHandleOpen = 0xc0190031, + RmDisconnected = 0xc0190032, + EnlistmentNotSuperior = 0xc0190033, + RecoveryNotNeeded = 0xc0190034, + RmAlreadyStarted = 0xc0190035, + FileIdentityNotPersistent = 0xc0190036, + CantBreakTransactionalDependency = 0xc0190037, + CantCrossRmBoundary = 0xc0190038, + TxfDirNotEmpty = 0xc0190039, + IndoubtTransactionsExist = 0xc019003a, + TmVolatile = 0xc019003b, + RollbackTimerExpired = 0xc019003c, + TxfAttributeCorrupt = 0xc019003d, + EfsNotAllowedInTransaction = 0xc019003e, + TransactionalOpenNotAllowed = 0xc019003f, + TransactedMappingUnsupportedRemote = 0xc0190040, + TxfMetadataAlreadyPresent = 0xc0190041, + TransactionScopeCallbacksNotSet = 0xc0190042, + TransactionRequiredPromotion = 0xc0190043, + CannotExecuteFileInTransaction = 0xc0190044, + TransactionsNotFrozen = 0xc0190045, + MaximumNtStatus = 0xffffffff + } + } +} \ No newline at end of file diff --git a/TrustedUninstaller.Shared/WinUtil.cs b/TrustedUninstaller.Shared/WinUtil.cs index 4ff3927..162448a 100644 --- a/TrustedUninstaller.Shared/WinUtil.cs +++ b/TrustedUninstaller.Shared/WinUtil.cs @@ -5,7 +5,6 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Management; -using System.Management.Automation; using System.Net.Http; using System.Reflection; using System.Runtime.InteropServices; @@ -18,6 +17,7 @@ using System.Windows; using System.Windows.Interop; using Microsoft.Win32; using TrustedUninstaller.Shared.Actions; +using TrustedUninstaller.Shared.Tasks; namespace TrustedUninstaller.Shared { @@ -444,7 +444,7 @@ namespace TrustedUninstaller.Shared if (res == ERROR_MORE_DATA) { // Create an array to store the process results - RM_PROCESS_INFO[] processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded]; + RM_PROCESS_INFO[] processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded + 3]; pnProcInfo = pnProcInfoNeeded; // Get the list @@ -465,7 +465,7 @@ namespace TrustedUninstaller.Shared catch (ArgumentException) { } } } - else throw new Exception("Could not list processes locking resource."); + else throw new Exception("Could not list processes locking resource: " + res); } else if (res != 0) throw new Exception("Could not list processes locking resource. Could not get size of result." + $" Result value: {res}"); @@ -609,37 +609,60 @@ namespace TrustedUninstaller.Shared public static async Task RemoveProtectionAsync() { var cmdAction = new CmdAction(); - if (!IsVCInstalled()) + + if (AmeliorationUtil.UseKernelDriver) { - Console.WriteLine(Environment.NewLine + "Installing VC 15..."); + /* + if (!IsVCInstalled()) + { + Console.WriteLine(Environment.NewLine + "Installing VC 15..."); + try + { + //Install Visual C++ 2015 redistributable package silently + cmdAction.Command = "vc_redist.x64.exe /q /norestart"; + cmdAction.RunTaskOnMainThread(); + } + catch (Exception e) + { + ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, "Error while installing VC 15."); + throw; + } + } + */ + try { - //Install Visual C++ 2015 redistributable package silently - cmdAction.Command = "vc_redist.x64.exe /q /norestart"; - await cmdAction.RunTask(); + Console.WriteLine(Environment.NewLine + "Installing driver..."); + cmdAction.Command = Environment.Is64BitOperatingSystem + ? $"ProcessHacker\\x64\\ProcessHacker.exe -s -installkph" + : $"ProcessHacker\\x86\\ProcessHacker.exe -s -installkph"; + cmdAction.RunTaskOnMainThread(); + + await AmeliorationUtil.SafeRunAction(new RegistryValueAction() + { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Services\KProcessHacker2", Value = "DeleteFlag", Type = RegistryValueType.REG_DWORD, Data = 1 }); } catch (Exception e) { - ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, "Error while installing VC 15."); + ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, "ProcessHacker ran into an error while installing its driver."); throw; } } + } - try - { - Console.WriteLine(Environment.NewLine + "Installing driver..."); - cmdAction.Command = Environment.Is64BitOperatingSystem - ? $"ProcessHacker\\x64\\ProcessHacker.exe -s -installkph" - : $"ProcessHacker\\x86\\ProcessHacker.exe -s -installkph"; - var res = await cmdAction.RunTask(); - - } - catch (Exception e) - { - ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, "ProcessHacker ran into an error while installing its driver."); - throw; - } - + public static async void CheckKph() + { + if (!AmeliorationUtil.UseKernelDriver || new RegistryKeyAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Services\KProcessHacker2", Operation = RegistryKeyOperation.Add }.GetStatus() == UninstallTaskStatus.Completed) + return; + + Console.WriteLine(Environment.NewLine + "Installing driver..."); + var cmdAction = new CmdAction(); + cmdAction.Command = Environment.Is64BitOperatingSystem + ? $"ProcessHacker\\x64\\ProcessHacker.exe -s -installkph" + : $"ProcessHacker\\x86\\ProcessHacker.exe -s -installkph"; + cmdAction.RunTaskOnMainThread(); + + await AmeliorationUtil.SafeRunAction(new RegistryValueAction() + { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Services\KProcessHacker2", Value = "DeleteFlag", Type = RegistryValueType.REG_DWORD, Data = 1 }); } private const int GWL_STYLE = -16; @@ -686,6 +709,10 @@ namespace TrustedUninstaller.Shared public static async Task UninstallDriver() { + if (AmeliorationUtil.UseKernelDriver == false) + return; + AmeliorationUtil.UseKernelDriver = false; + CmdAction cmdAction = new CmdAction(); try { @@ -693,7 +720,7 @@ namespace TrustedUninstaller.Shared cmdAction.Command = Environment.Is64BitOperatingSystem ? $"ProcessHacker\\x64\\ProcessHacker.exe -s -uninstallkph" : $"ProcessHacker\\x86\\ProcessHacker.exe -s -uninstallkph"; - await cmdAction.RunTask(); + cmdAction.RunTaskOnMainThread(); } catch (Exception e) {