// Spludlow Software // Copyright © Samuel P. Ludlow 2020 All Rights Reserved // Distributed under the terms of the GNU General Public License version 3 // Distributed WITHOUT ANY WARRANTY; without implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE // https://www.spludlow.co.uk/LICENCE.TXT // The Spludlow logo is a registered trademark of Samuel P. Ludlow and may not be used without permission // v1.14 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.IO; using System.Diagnostics; using System.Threading; using System.Data; namespace Spludlow { public class SpawnProcess { public class ProcessExitInfo { public int ExitCode; public string StandardOutput; public string StandardError; public int Pid; } private StringBuilder Output; private StringBuilder Error; public SpawnProcess() { } public static void WaitForPid(int pid) { WaitForPid(pid, 10, 3); } public static void WaitForPid(int pid, int attempts, int waitSeconds) { for (int attempt = 0; attempt < attempts; ++attempt) { bool found = false; foreach (Process process in Process.GetProcesses()) { using (process) { if (process.Id == pid) { found = true; break; } } } if (found == false) return; Thread.Sleep(waitSeconds * 1000); } throw new ApplicationException("WaitForPid; Process still runing. pid:" + pid); } public static string FindPath(string filename) { string[] paths = Spludlow.Text.Split(Environment.GetEnvironmentVariable("PATH"), ';', true, false); foreach (string path in paths) { string pathName = path; if (pathName[pathName.Length - 1] == '\\') pathName += filename; else pathName += @"\" + filename; if (File.Exists(pathName) == true) return pathName; } return null; } public static object RunMethod(string assembly, string type, string method, object[] parameters) { Spludlow.CallMethod callMethod = new CallMethod(null, assembly, type, method, parameters); ProcessExitInfo exitInfo = RunMethod(callMethod, 120); if (exitInfo.ExitCode != 0) throw new ApplicationException("SpawnProcess RunMethod; ExitCode: " + exitInfo.ExitCode + ", " + exitInfo.StandardError); StringBuilder resultText = new StringBuilder(); string firstLine = null; using (StringReader reader = new StringReader(exitInfo.StandardOutput)) { string line; while ((line = reader.ReadLine()) != null) { if (firstLine == null) firstLine = line; else resultText.AppendLine(line); } } if (firstLine == null) return null; string body = resultText.ToString(); if (firstLine.StartsWith("error") == true) throw new ApplicationException("SpawnProcess RunMethod; Error in Process: " + body); return Spludlow.Parameters.DecodeParameter(new string[] { firstLine, body }); } public static int RunMethodNoWait(string assembly, string type, string method) { return RunMethodNoWait(assembly, type, method, null); } public static int RunMethodNoWait(string assembly, string type, string method, object[] parameters) { Spludlow.CallMethod callMethod = new CallMethod(null, assembly, type, method, parameters); ProcessExitInfo info = RunMethod(callMethod, -1); return info.Pid; } public static ProcessExitInfo RunMethod(Spludlow.CallMethod callMethod, int timeoutSeconds) // dup code in CallText.WriteParameters { string spludlowProcessPath = Spludlow.Config.ApplicationDirectory("Spludlow-Process") + @"\Spludlow-Process.exe"; StringBuilder arguments = new StringBuilder(); arguments.Append("-A " + callMethod.Assembly); arguments.Append(" -T " + callMethod.Type); arguments.Append(" -M " + callMethod.Method); if (callMethod.Flags != CallFlags.None) arguments.Append(" -F \"" + callMethod.Flags.ToString().Replace(",", "|") + "\""); AppendParamters(arguments, "P", callMethod.Parameters); AppendParamters(arguments, "C", callMethod.ConstructorArguments); string argumentsText = arguments.ToString(); Spludlow.Log.Info("SpawnProcess; Run Method Arguments:\t" + argumentsText); SpawnProcess process = new SpawnProcess(); return process.RunWork(spludlowProcessPath, argumentsText, null, null, timeoutSeconds, false, null, null); } public static void TestSleep(int sleepSeconds) { Spludlow.SpawnProcess.RunMethod("System", "System.Threading.Thread", "Sleep", new object[] { sleepSeconds * 1000 }); } public static void TestSubSleep(int subSleepSeconds, int sleepSeconds, string dummy) { Spludlow.SpawnProcess.RunMethodNoWait("Spludlow", "Spludlow.SpawnProcess", "TestSubSleepWork", new object[] { subSleepSeconds, sleepSeconds, dummy }); } public static void TestSubSleepWork(int subSleepSeconds, int sleepSeconds, string dummy) { Spludlow.SpawnProcess.RunMethodNoWait("System", "System.Threading.Thread", "Sleep", new object[] { subSleepSeconds * 1000 }); System.Threading.Thread.Sleep(sleepSeconds * 1000); } public static void KillChildProcesses() { uint[] processIds = ChildProcesses(); KillProcesses(processIds); } public static void KillProcesses(uint[] processIds) { foreach (uint processId in processIds) { try { Process process = Process.GetProcessById((int)processId); process.Kill(); process.WaitForExit(); // Timeout? Spludlow.Log.Info("KillProcesses, PID:" + processId); } catch (Exception ee) { Spludlow.Log.Warning("KillProcesses, Getting and Killing: " + processId, ee); } } } public static uint[] ChildProcesses() { uint processId = (uint)System.Diagnostics.Process.GetCurrentProcess().Id; return ChildProcesses(processId); } public static uint[] ChildProcesses(uint processId) { DataTable table = Spludlow.Data.TextTable.ReadText(new string[] { "Caption ProcessId ParentProcessId Depth", "String UInt32 UInt32 Int32", }); object[][] results = Spludlow.ManagementObjects.Query("Win32_Process", new string[] { "Caption", "ProcessId", "ParentProcessId" }); foreach (object[] record in results) table.Rows.Add(record); TraverseChildProcesses(table, processId, 1); DataView view = new DataView(table); view.RowFilter = "Depth IS NOT NULL"; view.Sort = "Depth DESC"; List processIds = new List(); foreach (DataRowView rowView in view) processIds.Add((uint)rowView["ProcessId"]); return processIds.ToArray(); } private static void TraverseChildProcesses(DataTable table, uint parentProcessId, int depth) { DataRow[] rows = table.Select("ParentProcessId = " + parentProcessId); foreach (DataRow row in rows) { row["Depth"] = depth; uint processId = (uint)row["ProcessId"]; TraverseChildProcesses(table, processId, depth + 1); } } public static uint[] ProcessesWhereCommandLineLike(string match) { object[][] results = Spludlow.ManagementObjects.Query("Win32_Process", new string[] { "ProcessId" }, "CommandLine LIKE '%" + match +"%'"); List pids = new List(); foreach (object[] result in results) pids.Add((uint)result[0]); return pids.ToArray(); } private static void AppendParamters(StringBuilder arguments, string prefix, string[][] parameters) { if (parameters == null || parameters.Length == 0) return; for (int index = 0; index < parameters.Length; ++index) { string parameterType = parameters[index][0]; string parameterData = parameters[index][1]; if (parameterType == "null") continue; if (parameterType.StartsWith("@") == false) { Type type = Type.GetType(parameterType, true, false); if (Spludlow.SimpleEncoding.IsSimple(type) == true) { parameterType = type.Name; if (parameterData != null) { object data = Spludlow.Parameters.DecodeParameter(parameters[index]); parameterData = Spludlow.SimpleEncoding.Encode(data, type.Name); } } else { throw new ApplicationException("Non Simple Paramters not supported in SpawnProcess (Supply allread in seperate file '@Type')"); } } arguments.Append(" -" + prefix + index.ToString("00") + parameterType + " "); arguments.Append('"'); arguments.Append(parameterData); arguments.Append('"'); } } public static ProcessExitInfo Run(string filename, string arguments, int timeoutSeconds) { SpawnProcess process = new SpawnProcess(); return process.RunWork(filename, arguments, null, null, timeoutSeconds, false, null, null); } public static ProcessExitInfo Run(string filename, string arguments, int timeoutSeconds, string input) { SpawnProcess process = new SpawnProcess(); return process.RunWork(filename, arguments, null, null, timeoutSeconds, false, input, null); } public static ProcessExitInfo Run( string filename, string arguments, string directory, string[] variables, int timeoutSeconds, bool createWindow) { SpawnProcess process = new SpawnProcess(); return process.RunWork(filename, arguments, directory, variables, timeoutSeconds, createWindow, null, null); } public static ProcessExitInfo Run( string filename, string arguments, string directory, string[] variables, int timeoutSeconds, bool createWindow, string input) { SpawnProcess process = new SpawnProcess(); return process.RunWork(filename, arguments, directory, variables, timeoutSeconds, createWindow, input, null); } public static ProcessExitInfo Run( string filename, string arguments, string directory, string[] variables, int timeoutSeconds, bool createWindow, string input, Encoding encoding) { SpawnProcess process = new SpawnProcess(); return process.RunWork(filename, arguments, directory, variables, timeoutSeconds, createWindow, input, encoding); } public System.Security.SecureString convertToSecureString(string strPassword) { System.Security.SecureString secureStr = new System.Security.SecureString(); foreach (var c in strPassword.ToCharArray()) secureStr.AppendChar(c); return secureStr; } private ProcessExitInfo RunWork( string filename, string arguments, string directory, string[] variables, int timeoutSeconds, // +ve wait seconds, 0 wait indefinate, -ve dont wait bool createWindow, // If process crashing try this set to true string input, Encoding outputEncoding) { ProcessStartInfo startInfo = new ProcessStartInfo(filename); if (arguments != null) startInfo.Arguments = arguments; if (directory != null) startInfo.WorkingDirectory = directory; if (variables != null) { foreach (string varible in variables) { string[] words = varible.Split(new char[] { '=' }); if (words.Length != 2) throw new ApplicationException("Did not split varible:\t" + varible); startInfo.EnvironmentVariables.Add(words[0], words[1]); } } if (timeoutSeconds < 0) { startInfo.UseShellExecute = true; // Started by OS not calling process, does not inhertit handles from parent process } else { startInfo.UseShellExecute = false; // Started by calling proccess, gives more control startInfo.RedirectStandardOutput = true; startInfo.RedirectStandardError = true; } startInfo.RedirectStandardInput = false; if (input != null) startInfo.RedirectStandardInput = true; startInfo.CreateNoWindow = ! createWindow; // causes Cygwin xritdecompress to fail if (outputEncoding != null) { startInfo.StandardErrorEncoding = outputEncoding; startInfo.StandardOutputEncoding = outputEncoding; } this.Output = new StringBuilder(); this.Error = new StringBuilder(); using (Process process = new Process()) { process.StartInfo = startInfo; if (startInfo.UseShellExecute == false) { process.OutputDataReceived += process_OutputDataReceived; process.ErrorDataReceived += process_ErrorDataReceived; } process.Start(); if (startInfo.UseShellExecute == false) { process.BeginOutputReadLine(); process.BeginErrorReadLine(); } ProcessExitInfo info = new ProcessExitInfo(); info.Pid = process.Id; if (timeoutSeconds < 0) { if (process.HasExited == true) throw new ApplicationException("Process exited imediatly after starting:\t" + process.ExitCode); return info; } if (input != null) { process.StandardInput.WriteLine(input); process.StandardInput.Close(); } if (timeoutSeconds == 0) { process.WaitForExit(); } else { if (process.WaitForExit(timeoutSeconds * 1000) == false) { process.Kill(); throw new ApplicationException("SpawnProcess; WaitForExit Timeout."); } // ensure that asynchronous event handling has been completed process.WaitForExit(); } if (process.HasExited == false) throw new ApplicationException("SpawnProcess; Process has not Exited."); info.ExitCode = process.ExitCode; lock (this.Output) info.StandardOutput = this.Output.ToString(); lock (this.Error) info.StandardError = this.Error.ToString().Trim(); return info; } } void process_ErrorDataReceived(object sender, DataReceivedEventArgs e) { lock (this.Error) this.Error.AppendLine(e.Data); } void process_OutputDataReceived(object sender, DataReceivedEventArgs e) { lock (this.Output) this.Output.AppendLine(e.Data); } public static int GetCmdCodePage() { Spludlow.SpawnProcess.ProcessExitInfo info = Spludlow.SpawnProcess.Run("cmd.exe", null, null, null, 0, false, "chcp"); int codePage = -1; using (StringReader reader = new StringReader(info.StandardOutput)) { bool nextLine = false; string line; while ((line = reader.ReadLine()) != null) { if (line.EndsWith("chcp") == true) { nextLine = true; continue; } if (nextLine == true) { int index = line.IndexOf(":"); if (index != -1) Int32.TryParse(line.Substring(index + 1), out codePage); break; } } } if (codePage == -1) throw new ApplicationException("Did not find codepage in cmd.exe 'chcp' output: " + info.StandardOutput); return codePage; } } }