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.

401 lines
14 KiB

1 year ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
10 months ago
1 year ago
6 months ago
6 months ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
6 months ago
1 year ago
6 months ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
1 year ago
6 months ago
1 year ago
  1. using System;
  2. using System.Diagnostics;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Threading;
  6. using System.Threading.Tasks;
  7. using System.Windows.Documents;
  8. using TrustedUninstaller.Shared.Tasks;
  9. using YamlDotNet.Serialization;
  10. namespace TrustedUninstaller.Shared.Actions
  11. {
  12. public enum Privilege
  13. {
  14. TrustedInstaller,
  15. System,
  16. CurrentUserElevated,
  17. CurrentUser,
  18. }
  19. public class RunAction : TaskAction, ITaskAction
  20. {
  21. public void RunTaskOnMainThread()
  22. {
  23. if (RawPath != null) RawPath = Environment.ExpandEnvironmentVariables(RawPath);
  24. InProgress = true;
  25. var privilegeText = RunAs == Privilege.CurrentUser ? " as the current user" : RunAs == Privilege.CurrentUserElevated ? " as the current user elevated" : RunAs == Privilege.System ?
  26. " as the system account" : "";
  27. if (Arguments == null) Console.WriteLine($"Running '{Exe + privilegeText}'...");
  28. else Console.WriteLine($"Running '{Exe}' with arguments '{Arguments + privilegeText}'...");
  29. WinUtil.CheckKph();
  30. var currentDir = Directory.GetCurrentDirectory();
  31. if (ExeDir) RawPath = AmeliorationUtil.Playbook.Path + "\\Executables";
  32. if (BaseDir) RawPath = currentDir;
  33. string file = null;
  34. if (RawPath != null && File.Exists(Path.Combine(Environment.ExpandEnvironmentVariables(RawPath), Exe)))
  35. file = Path.Combine(Environment.ExpandEnvironmentVariables(RawPath), Exe);
  36. else if (ExistsInPath(Exe) || File.Exists(Environment.ExpandEnvironmentVariables(Exe)))
  37. file = Environment.ExpandEnvironmentVariables(Exe);
  38. if (file == null)
  39. throw new FileNotFoundException($"Executable not found.");
  40. if (RunAs == Privilege.TrustedInstaller)
  41. RunAsProcess(file);
  42. else
  43. RunAsPrivilegedProcess(file);
  44. InProgress = false;
  45. return;
  46. }
  47. [YamlMember(typeof(Privilege), Alias = "runas")]
  48. public Privilege RunAs { get; set; } = Privilege.TrustedInstaller;
  49. [YamlMember(typeof(string), Alias = "path")]
  50. public string RawPath { get; set; } = null;
  51. [YamlMember(typeof(string), Alias = "exe")]
  52. public string Exe { get; set; }
  53. [YamlMember(typeof(string), Alias = "args")]
  54. public string? Arguments { get; set; }
  55. [YamlMember(typeof(bool), Alias = "baseDir")]
  56. public bool BaseDir { get; set; } = false;
  57. [YamlMember(typeof(bool), Alias = "exeDir")]
  58. public bool ExeDir { get; set; } = false;
  59. [YamlMember(typeof(bool), Alias = "createWindow")]
  60. public bool CreateWindow { get; set; } = false;
  61. [YamlMember(typeof(bool), Alias = "showOutput")]
  62. public bool ShowOutput { get; set; } = true;
  63. [YamlMember(typeof(bool), Alias = "showError")]
  64. public bool ShowError { get; set; } = true;
  65. [YamlMember(typeof(int), Alias = "timeout")]
  66. public int? Timeout { get; set; }
  67. [YamlMember(typeof(string), Alias = "wait")]
  68. public bool Wait { get; set; } = true;
  69. [YamlMember(typeof(string), Alias = "weight")]
  70. public int ProgressWeight { get; set; } = 5;
  71. public int GetProgressWeight() => ProgressWeight;
  72. private bool InProgress { get; set; } = false;
  73. public void ResetProgress() => InProgress = false;
  74. private bool HasExited { get; set; } = false;
  75. //public int ExitCode { get; set; }
  76. public string? Output { get; private set; }
  77. private string? StandardError { get; set; }
  78. public string ErrorString() => String.IsNullOrEmpty(Arguments) ? $"RunAction failed to execute '{Exe}'." : $"RunAction failed to execute '{Exe}' with arguments '{Arguments}'.";
  79. public static bool ExistsInPath(string fileName)
  80. {
  81. if (File.Exists(fileName))
  82. return true;
  83. var values = Environment.GetEnvironmentVariable("PATH");
  84. foreach (var path in values.Split(Path.PathSeparator))
  85. {
  86. var fullPath = Path.Combine(path, fileName);
  87. if (File.Exists(fullPath))
  88. return true;
  89. if (!fileName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase) && File.Exists(fullPath + ".exe"))
  90. return true;
  91. }
  92. return false;
  93. }
  94. public UninstallTaskStatus GetStatus()
  95. {
  96. if (InProgress)
  97. {
  98. return UninstallTaskStatus.InProgress;
  99. }
  100. return HasExited || !Wait ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo;
  101. }
  102. public Task<bool> RunTask()
  103. {
  104. return null;
  105. }
  106. private void RunAsProcess(string file)
  107. {
  108. var startInfo = new ProcessStartInfo
  109. {
  110. CreateNoWindow = !this.CreateWindow,
  111. UseShellExecute = false,
  112. WindowStyle = ProcessWindowStyle.Normal,
  113. RedirectStandardError = true,
  114. RedirectStandardOutput = true,
  115. FileName = file,
  116. };
  117. if (Arguments != null) startInfo.Arguments = Environment.ExpandEnvironmentVariables(Arguments);
  118. if (ExeDir) startInfo.WorkingDirectory = AmeliorationUtil.Playbook.Path + "\\Executables";
  119. if (!Wait)
  120. {
  121. startInfo.RedirectStandardError = false;
  122. startInfo.RedirectStandardOutput = false;
  123. startInfo.WindowStyle = ProcessWindowStyle.Hidden;
  124. startInfo.UseShellExecute = true;
  125. }
  126. if (!ShowOutput)
  127. startInfo.RedirectStandardOutput = false;
  128. if (!ShowError)
  129. startInfo.RedirectStandardError = false;
  130. var exeProcess = new Process
  131. {
  132. StartInfo = startInfo,
  133. EnableRaisingEvents = true
  134. };
  135. exeProcess.Start();
  136. if (!Wait)
  137. {
  138. exeProcess.Dispose();
  139. return;
  140. }
  141. if (ShowOutput)
  142. exeProcess.OutputDataReceived += ProcOutputHandler;
  143. if (ShowError)
  144. exeProcess.ErrorDataReceived += ProcOutputHandler;
  145. if (ShowOutput)
  146. exeProcess.BeginOutputReadLine();
  147. if (ShowError)
  148. exeProcess.BeginErrorReadLine();
  149. if (Timeout.HasValue)
  150. {
  151. var exited = exeProcess.WaitForExit(Timeout.Value);
  152. if (!exited)
  153. {
  154. exeProcess.Kill();
  155. throw new TimeoutException($"Executable run timeout exceeded.");
  156. }
  157. }
  158. else
  159. {
  160. bool exited = exeProcess.WaitForExit(30000);
  161. // WaitForExit alone seems to not be entirely reliable
  162. while (!exited && ExeRunning(exeProcess.ProcessName, exeProcess.Id))
  163. {
  164. exited = exeProcess.WaitForExit(30000);
  165. }
  166. }
  167. int exitCode = 0;
  168. try
  169. {
  170. exitCode = exeProcess.ExitCode;
  171. }
  172. catch (Exception ex)
  173. {
  174. ErrorLogger.WriteToErrorLog("Error fetching process exit code. (1)", null, "RunAction Error", Exe + " " + Arguments);
  175. }
  176. if (exitCode != 0)
  177. {
  178. ErrorLogger.WriteToErrorLog("Process exited with a non-zero exit code: " + exitCode, null, "RunAction Error", Exe + " " + Arguments);
  179. }
  180. HasExited = true;
  181. if (ShowOutput)
  182. exeProcess.CancelOutputRead();
  183. if (ShowError)
  184. exeProcess.CancelErrorRead();
  185. exeProcess.Dispose();
  186. }
  187. private void RunAsPrivilegedProcess(string file)
  188. {
  189. var startInfo = new AugmentedProcess.ProcessStartInfo
  190. {
  191. CreateNoWindow = !this.CreateWindow,
  192. UseShellExecute = false,
  193. WindowStyle = ProcessWindowStyle.Normal,
  194. RedirectStandardError = true,
  195. RedirectStandardOutput = true,
  196. FileName = file,
  197. };
  198. if (Arguments != null) startInfo.Arguments = Arguments;
  199. if (ExeDir) startInfo.WorkingDirectory = AmeliorationUtil.Playbook.Path + "\\Executables";
  200. if (!Wait)
  201. {
  202. startInfo.RedirectStandardError = false;
  203. startInfo.RedirectStandardOutput = false;
  204. startInfo.WindowStyle = ProcessWindowStyle.Hidden;
  205. startInfo.UseShellExecute = true;
  206. }
  207. if (!ShowOutput)
  208. startInfo.RedirectStandardOutput = false;
  209. if (!ShowError)
  210. startInfo.RedirectStandardError = false;
  211. var exeProcess = new AugmentedProcess.Process
  212. {
  213. StartInfo = startInfo,
  214. EnableRaisingEvents = true
  215. };
  216. ProcessPrivilege.StartPrivilegedTask(exeProcess, RunAs);
  217. if (!Wait)
  218. {
  219. exeProcess.Dispose();
  220. return;
  221. }
  222. if (ShowOutput)
  223. exeProcess.OutputDataReceived += PrivilegedProcOutputHandler;
  224. if (ShowError)
  225. exeProcess.ErrorDataReceived += PrivilegedProcOutputHandler;
  226. if (ShowOutput)
  227. exeProcess.BeginOutputReadLine();
  228. if (ShowError)
  229. exeProcess.BeginErrorReadLine();
  230. if (Timeout.HasValue)
  231. {
  232. var exited = exeProcess.WaitForExit(Timeout.Value);
  233. if (!exited)
  234. {
  235. exeProcess.Kill();
  236. throw new TimeoutException($"Executable run timeout exceeded.");
  237. }
  238. }
  239. else
  240. {
  241. bool exited = exeProcess.WaitForExit(30000);
  242. // WaitForExit alone seems to not be entirely reliable
  243. while (!exited && ExeRunning(exeProcess.ProcessName, exeProcess.Id))
  244. {
  245. exited = exeProcess.WaitForExit(30000);
  246. }
  247. }
  248. try
  249. {
  250. if (exeProcess.ExitCode != 0)
  251. {
  252. ErrorLogger.WriteToErrorLog("Process exited with a non-zero exit code: " + exeProcess.ExitCode, null, "RunAction Error", Exe + " " + Arguments);
  253. }
  254. }
  255. catch (Exception ex)
  256. {
  257. ErrorLogger.WriteToErrorLog("Error fetching process exit code. (1)", null, "RunAction Error", Exe + " " + Arguments);
  258. Thread.Sleep(500);
  259. try
  260. {
  261. if (exeProcess.ExitCode != 0)
  262. {
  263. ErrorLogger.WriteToErrorLog("Process exited with a non-zero exit code: " + exeProcess.ExitCode, null, "RunAction Error", Exe + " " + Arguments);
  264. }
  265. }
  266. catch (Exception e)
  267. {
  268. ErrorLogger.WriteToErrorLog("Error fetching process exit code. (2)", null, "RunAction Error", Exe + " " + Arguments);
  269. }
  270. }
  271. HasExited = true;
  272. if (ShowOutput)
  273. exeProcess.CancelOutputRead();
  274. if (ShowError)
  275. exeProcess.CancelErrorRead();
  276. exeProcess.Dispose();
  277. }
  278. private static bool ExeRunning(string name, int id)
  279. {
  280. try
  281. {
  282. return Process.GetProcessesByName(name).Any(x => x.Id == id);
  283. }
  284. catch (Exception)
  285. {
  286. return false;
  287. }
  288. }
  289. private void PrivilegedProcOutputHandler(object sendingProcess, AugmentedProcess.DataReceivedEventArgs outLine)
  290. {
  291. try
  292. {
  293. // Collect the sort command output.
  294. if (!String.IsNullOrEmpty(outLine.Data))
  295. {
  296. var outputString = outLine.Data;
  297. if (outputString.Contains("\\AME"))
  298. {
  299. outputString = outputString.Substring(outputString.IndexOf('>') + 1);
  300. }
  301. Console.WriteLine(outputString);
  302. Output += outputString + Environment.NewLine;
  303. }
  304. else
  305. {
  306. Console.WriteLine();
  307. }
  308. }
  309. catch (Exception e)
  310. {
  311. ErrorLogger.WriteToErrorLog("Error processing process output", e.StackTrace, "RunAction Error", Exe);
  312. }
  313. }
  314. private void ProcOutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
  315. {
  316. try
  317. {
  318. // Collect the sort command output.
  319. if (!String.IsNullOrEmpty(outLine.Data))
  320. {
  321. var outputString = outLine.Data;
  322. if (outputString.Contains("\\AME"))
  323. {
  324. outputString = outputString.Substring(outputString.IndexOf('>') + 1);
  325. }
  326. Console.WriteLine(outputString);
  327. Output += outputString + Environment.NewLine;
  328. }
  329. else
  330. {
  331. Console.WriteLine();
  332. }
  333. }
  334. catch (Exception e)
  335. {
  336. ErrorLogger.WriteToErrorLog("Error processing process output", e.StackTrace, "RunAction Error", Exe);
  337. }
  338. }
  339. }
  340. }