CLI tool for running Playbooks
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

221 lines
7.5 KiB

1 year ago
  1. using System;
  2. using System.Diagnostics;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Threading.Tasks;
  6. using TrustedUninstaller.Shared.Tasks;
  7. using YamlDotNet.Serialization;
  8. namespace TrustedUninstaller.Shared.Actions
  9. {
  10. public class RunAction : ITaskAction
  11. {
  12. [YamlMember(typeof(string), Alias = "path")]
  13. public string RawPath { get; set; } = null;
  14. [YamlMember(typeof(string), Alias = "exe")]
  15. public string Exe { get; set; }
  16. [YamlMember(typeof(string), Alias = "args")]
  17. public string? Arguments { get; set; }
  18. [YamlMember(typeof(bool), Alias = "baseDir")]
  19. public bool BaseDir { get; set; } = false;
  20. [YamlMember(typeof(bool), Alias = "exeDir")]
  21. public bool ExeDir { get; set; } = false;
  22. [YamlMember(typeof(bool), Alias = "createWindow")]
  23. public bool CreateWindow { get; set; } = false;
  24. [YamlMember(typeof(bool), Alias = "showOutput")]
  25. public bool ShowOutput { get; set; } = true;
  26. [YamlMember(typeof(bool), Alias = "showError")]
  27. public bool ShowError { get; set; } = true;
  28. [YamlMember(typeof(int), Alias = "timeout")]
  29. public int? Timeout { get; set; }
  30. [YamlMember(typeof(string), Alias = "wait")]
  31. public bool Wait { get; set; } = true;
  32. [YamlMember(typeof(string), Alias = "weight")]
  33. public int ProgressWeight { get; set; } = 5;
  34. public int GetProgressWeight() => ProgressWeight;
  35. private bool InProgress { get; set; } = false;
  36. public void ResetProgress() => InProgress = false;
  37. private bool HasExited { get; set; } = false;
  38. //public int ExitCode { get; set; }
  39. public string? Output { get; private set; }
  40. private string? StandardError { get; set; }
  41. public string ErrorString() => String.IsNullOrEmpty(Arguments) ? $"RunAction failed to execute '{Exe}'." : $"RunAction failed to execute '{Exe}' with arguments '{Arguments}'.";
  42. public static bool ExistsInPath(string fileName)
  43. {
  44. if (File.Exists(fileName))
  45. return true;
  46. var values = Environment.GetEnvironmentVariable("PATH");
  47. foreach (var path in values.Split(Path.PathSeparator))
  48. {
  49. var fullPath = Path.Combine(path, fileName);
  50. if (File.Exists(fullPath))
  51. return true;
  52. if (!fileName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase) && File.Exists(fullPath + ".exe"))
  53. return true;
  54. }
  55. return false;
  56. }
  57. public UninstallTaskStatus GetStatus()
  58. {
  59. if (InProgress)
  60. {
  61. return UninstallTaskStatus.InProgress;
  62. }
  63. return HasExited || !Wait ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo;
  64. }
  65. public async Task<bool> RunTask()
  66. {
  67. if (RawPath != null) RawPath = Environment.ExpandEnvironmentVariables(RawPath);
  68. InProgress = true;
  69. if (Arguments == null) Console.WriteLine($"Running '{Exe}'...");
  70. else Console.WriteLine($"Running '{Exe}' with arguments '{Arguments}'...");
  71. var currentDir = Directory.GetCurrentDirectory();
  72. if (ExeDir) RawPath = AmeliorationUtil.Playbook.Path + "\\Executables";
  73. if (BaseDir) RawPath = currentDir;
  74. string file = null;
  75. if (RawPath != null && File.Exists(Path.Combine(RawPath, Exe)))
  76. file = Path.Combine(RawPath, Exe);
  77. else if (ExistsInPath(Exe) || File.Exists(Environment.ExpandEnvironmentVariables(Exe)))
  78. file = Exe;
  79. if (file == null)
  80. throw new FileNotFoundException($"Executable not found.");
  81. var startInfo = new ProcessStartInfo
  82. {
  83. CreateNoWindow = !this.CreateWindow,
  84. UseShellExecute = false,
  85. WindowStyle = ProcessWindowStyle.Normal,
  86. RedirectStandardError = true,
  87. RedirectStandardOutput = true,
  88. FileName = file,
  89. };
  90. if (Arguments != null) startInfo.Arguments = Environment.ExpandEnvironmentVariables(Arguments);
  91. if (ExeDir) startInfo.WorkingDirectory = AmeliorationUtil.Playbook.Path + "\\Executables";
  92. if (!Wait)
  93. {
  94. startInfo.RedirectStandardError = false;
  95. startInfo.RedirectStandardOutput = false;
  96. startInfo.WindowStyle = ProcessWindowStyle.Hidden;
  97. startInfo.UseShellExecute = true;
  98. }
  99. if (!ShowOutput)
  100. startInfo.RedirectStandardOutput = false;
  101. if (!ShowError)
  102. startInfo.RedirectStandardError = false;
  103. var exeProcess = new Process
  104. {
  105. StartInfo = startInfo,
  106. EnableRaisingEvents = true
  107. };
  108. exeProcess.Start();
  109. if (!Wait)
  110. {
  111. exeProcess.Dispose();
  112. return true;
  113. }
  114. if (ShowOutput)
  115. exeProcess.OutputDataReceived += ProcOutputHandler;
  116. if (ShowError)
  117. exeProcess.ErrorDataReceived += ProcOutputHandler;
  118. if (ShowOutput)
  119. exeProcess.BeginOutputReadLine();
  120. if (ShowError)
  121. exeProcess.BeginErrorReadLine();
  122. if (Timeout.HasValue)
  123. {
  124. var exited = exeProcess.WaitForExit(Timeout.Value);
  125. if (!exited)
  126. {
  127. exeProcess.Kill();
  128. throw new TimeoutException($"Executable run timeout exceeded.");
  129. }
  130. }
  131. else
  132. {
  133. bool exited = exeProcess.WaitForExit(30000);
  134. // WaitForExit alone seems to not be entirely reliable
  135. while (!exited && ExeRunning(exeProcess))
  136. {
  137. exited = exeProcess.WaitForExit(30000);
  138. }
  139. }
  140. if (exeProcess.ExitCode != 0)
  141. {
  142. ErrorLogger.WriteToErrorLog("Process exited with a non-zero exit code: " + exeProcess.ExitCode, null, "RunAction Error", Exe + " " + Arguments);
  143. }
  144. HasExited = true;
  145. if (ShowOutput)
  146. exeProcess.CancelOutputRead();
  147. if (ShowError)
  148. exeProcess.CancelErrorRead();
  149. InProgress = false;
  150. return true;
  151. }
  152. private static bool ExeRunning(Process process)
  153. {
  154. try
  155. {
  156. return Process.GetProcessesByName(process.ProcessName).Any(x => x.Id == process.Id);
  157. }
  158. catch (Exception)
  159. {
  160. return false;
  161. }
  162. }
  163. private void ProcOutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
  164. {
  165. // Collect the sort command output.
  166. if (!String.IsNullOrEmpty(outLine.Data))
  167. {
  168. var outputString = outLine.Data;
  169. if (outputString.Contains("\\AME"))
  170. {
  171. outputString = outputString.Substring(outputString.IndexOf('>') + 1);
  172. }
  173. Console.WriteLine(outputString);
  174. Output += outputString + Environment.NewLine;
  175. }
  176. else
  177. {
  178. Console.WriteLine();
  179. }
  180. }
  181. }
  182. }