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.

150 lines
5.1 KiB

1 year ago
  1. using System;
  2. using System.Diagnostics;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Management.Automation;
  6. using System.Text;
  7. using System.Threading;
  8. using System.Threading.Tasks;
  9. using System.Windows.Forms;
  10. using TrustedUninstaller.Shared.Exceptions;
  11. using TrustedUninstaller.Shared.Tasks;
  12. using YamlDotNet.Serialization;
  13. namespace TrustedUninstaller.Shared.Actions
  14. {
  15. public class PowerShellAction : ITaskAction
  16. {
  17. [YamlMember(typeof(string), Alias = "command")]
  18. public string Command { get; set; }
  19. [YamlMember(typeof(string), Alias = "timeout")]
  20. public int? Timeout { get; set; }
  21. [YamlMember(typeof(string), Alias = "wait")]
  22. public bool Wait { get; set; } = true;
  23. [YamlMember(typeof(bool), Alias = "exeDir")]
  24. public bool ExeDir { get; set; } = false;
  25. [YamlMember(typeof(string), Alias = "weight")]
  26. public int ProgressWeight { get; set; } = 1;
  27. private int? ExitCode { get; set; }
  28. public string? StandardError { get; set; }
  29. public string StandardOutput { get; set; }
  30. public int GetProgressWeight() => ProgressWeight;
  31. private bool InProgress { get; set; }
  32. public void ResetProgress() => InProgress = false;
  33. public string ErrorString() => $"PowerShellAction failed to run command '{Command}'.";
  34. public UninstallTaskStatus GetStatus()
  35. {
  36. if (InProgress)
  37. {
  38. return UninstallTaskStatus.InProgress;
  39. }
  40. return ExitCode == null ? UninstallTaskStatus.ToDo: UninstallTaskStatus.Completed;
  41. }
  42. public async Task<bool> RunTask()
  43. {
  44. if (InProgress) throw new TaskInProgressException("Another Powershell action was called while one was in progress.");
  45. InProgress = true;
  46. Console.WriteLine($"Running PowerShell command '{Command}'...");
  47. var process = new Process();
  48. var startInfo = new ProcessStartInfo
  49. {
  50. WindowStyle = ProcessWindowStyle.Normal,
  51. FileName = "PowerShell.exe",
  52. Arguments = $@"-NoP -ExecutionPolicy Bypass -NonInteractive -C ""{Command}""",
  53. UseShellExecute = false,
  54. RedirectStandardError = true,
  55. RedirectStandardOutput = true,
  56. CreateNoWindow = true
  57. };
  58. if (ExeDir) startInfo.WorkingDirectory = Directory.GetCurrentDirectory() + "\\Executables";
  59. if (!Wait)
  60. {
  61. startInfo.RedirectStandardError = false;
  62. startInfo.RedirectStandardOutput = false;
  63. startInfo.WindowStyle = ProcessWindowStyle.Hidden;
  64. startInfo.UseShellExecute = true;
  65. }
  66. process.StartInfo = startInfo;
  67. process.Start();
  68. if (!Wait)
  69. {
  70. process.Dispose();
  71. return true;
  72. }
  73. var error = new StringBuilder();
  74. process.OutputDataReceived += ProcOutputHandler;
  75. process.ErrorDataReceived += delegate(object sender, DataReceivedEventArgs args)
  76. {
  77. if (!String.IsNullOrEmpty(args.Data))
  78. error.AppendLine(args.Data);
  79. else
  80. error.AppendLine();
  81. };
  82. process.BeginOutputReadLine();
  83. process.BeginErrorReadLine();
  84. if (Timeout != null)
  85. {
  86. var exited = process.WaitForExit(Timeout.Value);
  87. if (!exited)
  88. {
  89. process.Kill();
  90. throw new TimeoutException($"Command '{Command}' timeout exceeded.");
  91. }
  92. }
  93. else process.WaitForExit();
  94. StandardError = error.ToString();
  95. if (process.ExitCode != 0)
  96. {
  97. Console.WriteLine($"PowerShell instance exited with error code: {process.ExitCode}");
  98. if (!String.IsNullOrEmpty(StandardError)) Console.WriteLine($"Error message: {StandardError}");
  99. ErrorLogger.WriteToErrorLog("PowerShell exited with a non-zero exit code: " + process.ExitCode, null, "PowerShellAction Error", Command);
  100. this.ExitCode = process.ExitCode;
  101. }
  102. else
  103. {
  104. if (!String.IsNullOrEmpty(StandardError)) Console.WriteLine($"Error output: {StandardError}");
  105. ExitCode = 0;
  106. }
  107. process.CancelOutputRead();
  108. process.CancelErrorRead();
  109. process.Dispose();
  110. InProgress = false;
  111. return true;
  112. }
  113. private static void ProcOutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
  114. {
  115. if (!String.IsNullOrEmpty(outLine.Data))
  116. {
  117. Console.WriteLine(outLine.Data);
  118. }
  119. else
  120. {
  121. Console.WriteLine();
  122. }
  123. }
  124. }
  125. }