Guest User

Untitled

a guest
Jul 20th, 2018
88
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 13.48 KB | None | 0 0
  1. public class ExternalProcess
  2. {
  3. public string FileName { get; set; }
  4. public string Arguments { get; set; } = "";
  5. public string WorkingDirectory { get; set; } = "";
  6. public int Timeout_milliseconds { get; set; } = -1;
  7. public bool ReadOutput { get; set; }
  8. public bool ShowWindow { get; set; }
  9. public bool StartAsAdministrator { get; set; }
  10. public string Output { get; private set; } = "";
  11. public string OutputError { get; private set; } = "";
  12. public int ExitCode { get; private set; }
  13. public bool WasKilled { get; private set; }
  14. public bool WasSuccessful { get; private set; }
  15.  
  16. public event EventHandler OutputChanged;
  17.  
  18. public event EventHandler OutputErrorChanged;
  19.  
  20. public bool Start(CancellationToken cancellationToken = default(CancellationToken))
  21. {
  22. var task = StartAsync(cancellationToken);
  23. task.Wait();
  24. return task.Result;
  25. }
  26.  
  27. public async Task<bool> StartAsync(CancellationToken cancellationToken = default(CancellationToken))
  28. {
  29. if (String.IsNullOrWhiteSpace(FileName))
  30. {
  31. throw new ArgumentNullException(nameof(FileName));
  32. }
  33. //if (!File.Exists(FileName)) // removed because also commands could be executed (for example: ping)
  34. if (!ReadOutput)
  35. {
  36. Output = OutputError = $"Enable {nameof(ReadOutput)} to get Output ";
  37. }
  38. if (StartAsAdministrator)
  39. {
  40. ReadOutput = false; // Verb="runas" only possible with ShellExecute=true.
  41. Output = OutputError = "Output couldn't be read when started as Administrator ";
  42. }
  43. var useShellExecute = !ReadOutput; // true when started as admin, false for reading output
  44. if (!StartAsAdministrator && !ShowWindow)
  45. useShellExecute = false; // false for hiding the window
  46. using (var p = new Process
  47. {
  48. EnableRaisingEvents = true,
  49. StartInfo = new ProcessStartInfo()
  50. {
  51. FileName = FileName,
  52. Arguments = Arguments,
  53. UseShellExecute = useShellExecute,
  54. RedirectStandardOutput = ReadOutput,
  55. RedirectStandardError = ReadOutput,
  56. RedirectStandardInput = ReadOutput,
  57. CreateNoWindow = !ShowWindow,
  58. }
  59. })
  60. {
  61. if (StartAsAdministrator)
  62. {
  63. p.StartInfo.Verb = "runas";
  64. }
  65. if (!String.IsNullOrWhiteSpace(WorkingDirectory))
  66. {
  67. p.StartInfo.WorkingDirectory = WorkingDirectory;
  68. }
  69. if (ReadOutput)
  70. {
  71. p.OutputDataReceived += (sender, args) =>
  72. {
  73. if (!String.IsNullOrEmpty(args?.Data))
  74. {
  75. Output += args.Data + Environment.NewLine;
  76. OutputChanged?.Invoke(this, EventArgs.Empty);
  77. }
  78. };
  79. p.ErrorDataReceived += (sender, args) =>
  80. {
  81. if (!String.IsNullOrEmpty(args?.Data))
  82. {
  83. OutputError += args.Data + Environment.NewLine;
  84. OutputErrorChanged?.Invoke(this, EventArgs.Empty);
  85. }
  86. };
  87. }
  88.  
  89. try
  90. {
  91. p.Start();
  92. }
  93. catch (System.ComponentModel.Win32Exception ex)
  94. {
  95. if (ex.NativeErrorCode == 1223)
  96. {
  97. OutputError += "AdminRights request Canceled by User!! " + ex;
  98. ExitCode = -1;
  99. WasSuccessful = false;
  100. return WasSuccessful;
  101. }
  102. else
  103. {
  104. OutputError += "Win32Exception thrown: " + ex;
  105. ExitCode = -1;
  106. WasSuccessful = false;
  107. throw;
  108. }
  109. }
  110. catch (Exception ex)
  111. {
  112. OutputError += "Exception thrown: " + ex;
  113. ExitCode = -1;
  114. WasSuccessful = false;
  115. throw;
  116. }
  117.  
  118. if (ReadOutput)
  119. {
  120. // var writer = p.StandardInput; writer.WriteLine("");
  121. p.BeginOutputReadLine();
  122. p.BeginErrorReadLine();
  123. }
  124.  
  125. CancellationTokenSource timeoutCancellationToken = null;
  126. if (Timeout_milliseconds > 0)
  127. {
  128. timeoutCancellationToken = new CancellationTokenSource(Timeout_milliseconds);
  129. }
  130. while (!p.HasExited && !cancellationToken.IsCancellationRequested && !(timeoutCancellationToken?.IsCancellationRequested ?? false))
  131. {
  132. await Task.Delay(10).ConfigureAwait(false); // configureAwait is that the task doesn't continue after the first await is done (prevent deadlock)
  133. }
  134. if (!p.HasExited)
  135. {
  136. WasKilled = true;
  137. OutputError += " Process was cancelled!";
  138. try
  139. {
  140. p.CloseMainWindow();
  141. int waitForKill = 30;
  142. do
  143. {
  144. Thread.Sleep(10);
  145. waitForKill--;
  146. } while (!p.HasExited && waitForKill > 0);
  147. if (!p.HasExited)
  148. {
  149. p.Kill();
  150. }
  151. }
  152. catch { }
  153. }
  154. ExitCode = p.ExitCode;
  155. p.Close();
  156. if (ExitCode == -1073741510)
  157. {
  158. OutputError += $"Process exited by user, exitcode: {ExitCode}!";
  159. }
  160. WasSuccessful = !WasKilled && ExitCode == 0;
  161. return WasSuccessful;
  162. }
  163. }
  164. }
  165.  
  166. static Task MostBasicProcess()
  167. {
  168. var t = new TaskCompletionSource<bool>(); //Using bool, because TaskCompletionSource needs at least one generic param
  169.  
  170. var p = new Process();
  171. //TODO: more setup
  172. p.EnableRaisingEvents = true; //VERY important
  173. p.Exited += (object sender, EventArgs e) =>
  174. {
  175. ////TODO: Exceptions will go first, followed by `return;`
  176. //t.SetException();
  177.  
  178. //TODO: Finally, if there are no problems, return successfully
  179. t.SetResult(true);
  180. };
  181. p.Start();
  182. //TODO: wrap .Start() in try-block and call t.SetException on error
  183.  
  184. return t.Task; //We actually don't want the caller using the bool param, it's implicitly casted to plain Task.
  185. }
  186.  
  187. try
  188. {
  189. await MostBasicProcess();
  190. }
  191. catch (Exception ex)
  192. {
  193. }
  194.  
  195. [Serializable]
  196. public class ProcessSettings
  197. {
  198. public string FileName { get; set; }
  199. public string Arguments { get; set; } = "";
  200. public string WorkingDirectory { get; set; } = "";
  201. public string InputText { get; set; } = null;
  202. public int Timeout_milliseconds { get; set; } = -1;
  203. public bool ReadOutput { get; set; }
  204. public bool ShowWindow { get; set; }
  205. public bool KeepWindowOpen { get; set; }
  206. public bool StartAsAdministrator { get; set; }
  207. public CancellationToken CancellationToken { get; set; }
  208. }
  209.  
  210. public class OutputReader // to get the output while executing instead only as result at the end
  211. {
  212. public event TextEventHandler OutputChanged;
  213. public event TextEventHandler OutputErrorChanged;
  214. public void UpdateOutput(string text)
  215. {
  216. OutputChanged?.Invoke(this, new TextEventArgs(text));
  217. }
  218. public void UpdateOutputError(string text)
  219. {
  220. OutputErrorChanged?.Invoke(this, new TextEventArgs(text));
  221. }
  222. public delegate void TextEventHandler(object sender, TextEventArgs e);
  223. public class TextEventArgs : EventArgs
  224. {
  225. public string Text { get; }
  226. public TextEventArgs(string text) { Text = text; }
  227. }
  228. }
  229.  
  230. [Serializable]
  231. public class ProcessResult
  232. {
  233. public string Output { get; set; }
  234. public string OutputError { get; set; }
  235. public int ExitCode { get; set; }
  236. public bool WasKilled { get; set; }
  237. public bool WasSuccessful { get; set; }
  238. }
  239.  
  240. public class ProcessStarter
  241. {
  242. public ProcessResult Execute(ProcessSettings settings, OutputReader outputReader = null)
  243. {
  244. var task = ExecuteAsync(settings, outputReader);
  245. task.Wait();
  246. return task.Result;
  247. }
  248.  
  249. public async Task<ProcessResult> ExecuteAsync(ProcessSettings settings, OutputReader outputReader = null)
  250. {
  251. if (settings.FileName == null) throw new ArgumentNullException(nameof(ProcessSettings.FileName));
  252. if (settings.Arguments == null) throw new ArgumentNullException(nameof(ProcessSettings.Arguments));
  253.  
  254. var cmdSwitches = "/Q " + (settings.KeepWindowOpen ? "/K" : "/C");
  255.  
  256. var arguments = $"{cmdSwitches} {settings.FileName} {settings.Arguments}";
  257. var startInfo = new ProcessStartInfo("cmd", arguments)
  258. {
  259. UseShellExecute = false,
  260. RedirectStandardOutput = settings.ReadOutput,
  261. RedirectStandardError = settings.ReadOutput,
  262. RedirectStandardInput = settings.InputText != null,
  263. CreateNoWindow = !(settings.ShowWindow || settings.KeepWindowOpen),
  264. };
  265. var output = new StringBuilder();
  266. var error = new StringBuilder();
  267. if (!settings.ReadOutput)
  268. {
  269. output.AppendLine($"Enable {nameof(ProcessSettings.ReadOutput)} to get Output");
  270. }
  271. if (settings.StartAsAdministrator)
  272. {
  273. startInfo.Verb = "runas";
  274. startInfo.UseShellExecute = true; // Verb="runas" only possible with ShellExecute=true.
  275. startInfo.RedirectStandardOutput = startInfo.RedirectStandardError = startInfo.RedirectStandardInput = false;
  276. output.AppendLine("Output couldn't be read when started as Administrator");
  277. }
  278. if (!String.IsNullOrWhiteSpace(settings.WorkingDirectory))
  279. {
  280. startInfo.WorkingDirectory = settings.WorkingDirectory;
  281. }
  282. var result = new ProcessResult();
  283. var taskCompletionSourceProcess = new TaskCompletionSource<bool>();
  284. using (var process = new Process { StartInfo = startInfo, EnableRaisingEvents = true })
  285. {
  286. process.OutputDataReceived += (sender, e) =>
  287. {
  288. if (e?.Data != null)
  289. {
  290. output.AppendLine(e.Data);
  291. outputReader?.UpdateOutput(e.Data);
  292. }
  293. };
  294. process.ErrorDataReceived += (sender, e) =>
  295. {
  296. if (e?.Data != null)
  297. {
  298. error.AppendLine(e.Data);
  299. outputReader?.UpdateOutputError(e.Data);
  300. }
  301. };
  302. process.Exited += (sender, e) =>
  303. {
  304. try { (sender as Process)?.WaitForExit(); } catch (InvalidOperationException) { }
  305. taskCompletionSourceProcess.TrySetResult(false);
  306. };
  307.  
  308. try
  309. {
  310. process.Start();
  311. }
  312. catch (System.ComponentModel.Win32Exception ex)
  313. {
  314. if (ex.NativeErrorCode == 1223)
  315. {
  316. error.AppendLine("AdminRights request Canceled by User!! " + ex);
  317. taskCompletionSourceProcess.SetException(ex);
  318. }
  319. else
  320. {
  321. error.AppendLine("Win32Exception thrown: " + ex);
  322. taskCompletionSourceProcess.SetException(ex);
  323. }
  324. }
  325. catch (Exception ex)
  326. {
  327. error.AppendLine("Exception thrown: " + ex);
  328. taskCompletionSourceProcess.SetException(ex);
  329. }
  330. if (startInfo.RedirectStandardOutput)
  331. process.BeginOutputReadLine();
  332. if (startInfo.RedirectStandardError)
  333. process.BeginErrorReadLine();
  334. if (startInfo.RedirectStandardInput)
  335. process.StandardInput.WriteLine(settings.InputText);
  336.  
  337. if (settings.CancellationToken != default(CancellationToken))
  338. settings.CancellationToken.Register(() => taskCompletionSourceProcess.TrySetResult(true));
  339. if (settings.Timeout_milliseconds > 0)
  340. new CancellationTokenSource(settings.Timeout_milliseconds).Token.Register(() => taskCompletionSourceProcess.TrySetResult(true));
  341.  
  342. var taskProcess = taskCompletionSourceProcess.Task;
  343. await taskProcess.ConfigureAwait(false);
  344. if (taskProcess.Result == true) // process was cancelled by token or timeout
  345. {
  346. if (!process.HasExited)
  347. {
  348. result.WasKilled = true;
  349. error.AppendLine("Process was cancelled!");
  350. try
  351. {
  352. process.CloseMainWindow();
  353. await Task.Delay(10).ConfigureAwait(false);
  354. if (!process.HasExited)
  355. {
  356. process.Kill();
  357. }
  358. }
  359. catch { }
  360. }
  361. }
  362. result.ExitCode = process.ExitCode;
  363. process.Close();
  364. }
  365. if (result.ExitCode == -1073741510 && !result.WasKilled)
  366. {
  367. error.AppendLine($"Process exited by user!");
  368. }
  369. result.WasSuccessful = !result.WasKilled && result.ExitCode == 0;
  370. result.Output = output.ToString();
  371. result.OutputError = error.ToString();
  372. return result;
  373. }
  374. }
Add Comment
Please, Sign In to add comment