using System; 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; var privilegeText = RunAs == Privilege.CurrentUser ? " as the current user" : RunAs == Privilege.CurrentUserElevated ? " as the current user elevated" : RunAs == Privilege.System ? " as the system account" : ""; if (Arguments == null) Console.WriteLine($"Running '{Exe + privilegeText}'..."); else Console.WriteLine($"Running '{Exe}' with arguments '{Arguments + privilegeText}'..."); 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; [YamlMember(typeof(string), Alias = "exe")] public string Exe { get; set; } [YamlMember(typeof(string), Alias = "args")] public string? Arguments { get; set; } [YamlMember(typeof(bool), Alias = "baseDir")] public bool BaseDir { get; set; } = false; [YamlMember(typeof(bool), Alias = "exeDir")] public bool ExeDir { get; set; } = false; [YamlMember(typeof(bool), Alias = "createWindow")] public bool CreateWindow { get; set; } = false; [YamlMember(typeof(bool), Alias = "showOutput")] public bool ShowOutput { get; set; } = true; [YamlMember(typeof(bool), Alias = "showError")] public bool ShowError { get; set; } = true; [YamlMember(typeof(int), Alias = "timeout")] public int? Timeout { get; set; } [YamlMember(typeof(string), Alias = "wait")] public bool Wait { get; set; } = true; [YamlMember(typeof(string), Alias = "weight")] public int ProgressWeight { get; set; } = 5; public int GetProgressWeight() => ProgressWeight; private bool InProgress { get; set; } = false; public void ResetProgress() => InProgress = false; private bool HasExited { get; set; } = false; //public int ExitCode { get; set; } public string? Output { get; private set; } private string? StandardError { get; set; } public string ErrorString() => String.IsNullOrEmpty(Arguments) ? $"RunAction failed to execute '{Exe}'." : $"RunAction failed to execute '{Exe}' with arguments '{Arguments}'."; public static bool ExistsInPath(string fileName) { if (File.Exists(fileName)) return true; var values = Environment.GetEnvironmentVariable("PATH"); foreach (var path in values.Split(Path.PathSeparator)) { var fullPath = Path.Combine(path, fileName); if (File.Exists(fullPath)) return true; if (!fileName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase) && File.Exists(fullPath + ".exe")) return true; } return false; } public UninstallTaskStatus GetStatus() { if (InProgress) { return UninstallTaskStatus.InProgress; } return HasExited || !Wait ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo; } public Task RunTask() { return null; } private void RunAsProcess(string file) { var startInfo = new ProcessStartInfo { CreateNoWindow = !this.CreateWindow, UseShellExecute = false, WindowStyle = ProcessWindowStyle.Normal, RedirectStandardError = true, RedirectStandardOutput = true, FileName = file, }; if (Arguments != null) startInfo.Arguments = Environment.ExpandEnvironmentVariables(Arguments); 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 Process { StartInfo = startInfo, EnableRaisingEvents = true }; exeProcess.Start(); if (!Wait) { exeProcess.Dispose(); return; } if (ShowOutput) exeProcess.OutputDataReceived += ProcOutputHandler; if (ShowError) exeProcess.ErrorDataReceived += ProcOutputHandler; 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); } } int exitCode = 0; try { exitCode = exeProcess.ExitCode; } catch (Exception ex) { 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; if (ShowOutput) exeProcess.CancelOutputRead(); if (ShowError) exeProcess.CancelErrorRead(); exeProcess.Dispose(); } 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; 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(); exeProcess.Dispose(); } private static bool ExeRunning(string name, int id) { try { return Process.GetProcessesByName(name).Any(x => x.Id == id); } catch (Exception) { return false; } } private void PrivilegedProcOutputHandler(object sendingProcess, AugmentedProcess.DataReceivedEventArgs outLine) { try { // 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(); } } catch (Exception e) { ErrorLogger.WriteToErrorLog("Error processing process output", e.StackTrace, "RunAction Error", Exe); } } private void ProcOutputHandler(object sendingProcess, DataReceivedEventArgs outLine) { try { // 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(); } } catch (Exception e) { ErrorLogger.WriteToErrorLog("Error processing process output", e.StackTrace, "RunAction Error", Exe); } } } }