I have the following class to call subprocesses from my application and read their stdout and stderr. Since there are no instance variables for this class, I would have thought it to be thread-safe, but without the lock (_lockObject) I can reproduce an error where the stdout of a subprocess is incorrectly reported as empty.
public class SubprocessRepository
{
private readonly static object _lockObject = new Object();
public ProcessReturnInfo Call(string fileName, params string[] args)
{
lock (_lockObject)
{
var joinedArguments = String.Join(" ", args);
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = String.Format(#"""{0}""", fileName),
Arguments = joinedArguments,
RedirectStandardError = true,
RedirectStandardOutput = true,
UseShellExecute = false
}
};
// read the stdout and stderr streams asynchronously. if they are read synchronously and the streams fill up,
// the entire process waits for the stream to be consumed which will never happen and the subprocess will be
// never complete its operation
var sortSdtOut = new StringBuilder();
var sortSdtErr = new StringBuilder();
process.OutputDataReceived += (sendingProcess, outLine) =>
{
// Collect the sort command output.
if (!String.IsNullOrEmpty(outLine.Data))
{
// Add the text to the collected output.
sortSdtOut.Append(Environment.NewLine + outLine.Data);
}
};
process.ErrorDataReceived += (sendingProcess, outLine) =>
{
// Collect the sort command output.
if (!String.IsNullOrEmpty(outLine.Data))
{
// Add the text to the collected output.
sortSdtErr.Append(Environment.NewLine + outLine.Data);
}
};
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
process.WaitForExit(15 * 60 * 1000); // N minutes * 60 seconds * 1000 milliseconds
if (!process.HasExited)
{
process.Kill(); // executes asynchronously
process.WaitForExit(10000);
}
return new ProcessReturnInfo
{
ExitCode = process.ExitCode,
StdOut = sortSdtOut.ToString().Trim('\r', '\n'),
StdErr = sortSdtErr.ToString().Trim('\r', '\n')
};
}
}
}
I have done some reading on the topic but everything seems to suggest putting locks around the class' instance variables. I suspect it's something to do with the event-handling delegates, but am a bit unsure.
What is the cause of the thread-unsafety in this code when the lock is absent?
FYI, one of the processes that is being called (and incorrectly returning an empty string) is the following Powershell script:
Param(
[string]$server_name,
[string]$service_name
)
$path = (Get-WmiObject -ComputerName $server_name -query "SELECT PathName FROM Win32_Service WHERE DisplayName = '$service_name'").PathName
if ($path -eq $null) {
Write-Error "The $service_name Windows service does not exist on the target server."
exit 1
} else {
Write-Host $path
}
Related
I have a console application and a method that executes a PowerShell script within the console application. So I'm trying to grab an error text that it outputs in the application and do something with it.
Example/What I'm trying to do:
If Error.contains("Object")
{
// do something here
}
Here is my current method
public void ExecutePowershellScript()
{
var file = #"C:\Path\filename.ps1";
var start = new ProcessStartInfo()
{
FileName = "powershell.exe",
Arguments = $"-NoProfile -ExecutionPolicy unrestricted -file \"{file}\"",
UseShellExecute = false
};
Process.Start(start);
}
Process.start: how to get the output?
When you create your Process object set StartInfo appropriately:
var proc = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "program.exe",
Arguments = "command line arguments to your executable",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
then start the process and read from it:
proc.Start();
while (!proc.StandardOutput.EndOfStream)
{
string line = proc.StandardOutput.ReadLine();
// do something with line
}
You can use int.Parse() or int.TryParse() to convert the strings to numeric values. You may have to do some string manipulation first if there are invalid numeric characters in the strings you read.
You can set RedirectStandardError = true and access any errors from process.StandardError
public static void ExecutePowershellScript()
{
var file = #"C:\Path\filename.ps1";
var start = new ProcessStartInfo()
{
FileName = "powershell.exe",
Arguments = $"-NoProfile -ExecutionPolicy unrestricted -file \"{file}\"",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true
};
using Process process = Process.Start(start);
string output = process.StandardOutput.ReadToEnd();
string errors = process.StandardError.ReadToEnd();
}
Okay, scratch the above suggestion.
After being corrected by mklement0,
This is a perfectly reasonable attempt, but, unfortunately, it can lead to hangs (while waiting for one's stream end, the other, when exceeding the buffer size, may cause process execution to block). If you need to capture both streams, you must collect the output from one of them via events. – mklement0
I changed the solution to use the ErrorDataReceived event
public static async Task ExecutePowershellScript()
{
var file = #"C:\Path\filename.ps1";
var start = new ProcessStartInfo
{
FileName = "powershell.exe",
Arguments = $"-NoProfile -ExecutionPolicy unrestricted -file \"{file}\"",
UseShellExecute = false,
// redirect standard error stream to process.StandardError
RedirectStandardError = true
};
using var process = new Process
{
StartInfo = start
};
// Subscribe to ErrorDataReceived event
process.ErrorDataReceived += (sender, e) =>
{
// code to process the error lines in e.Data
};
process.Start();
// Necessary to start redirecting errors to StandardError
process.BeginErrorReadLine();
// Wait for process to exit
await process.WaitForExitAsync();
}
start.Start();
while (!start.StandardOutput.EndOfStream)
{
string line = start.StandardOutput.ReadLine();
}
I have the following code which opens command window (from WPF interface) and executes code where can take long like # 8-10 minutes:
ProcessStartInfo procStartInfo = new ProcessStartInfo();
procStartInfo.FileName = _exePath;
procStartInfo.Arguments = arguments;
procStartInfo.UseShellExecute = false;
procStartInfo.CreateNoWindow = false;
procStartInfo.RedirectStandardOutput = true;
procStartInfo.RedirectStandardError = true;
using (Process pr = Process.Start(procStartInfo))
{
pr.WaitForExit();
string result = pr.StandardOutput.ReadToEnd();
string[] split = result.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
int output = 0;
int.TryParse(split[split.Length - 1], out output);
return output;
}
And in Program.cs I have method which update status (show operation status and percent) with:
Console.Title = "Loading ... 5 %";
// do request to server and check status
while(status.InProgress) {
Thread.Sleep(2000); // 2 seconds
status = web.getJsonFromApiServer(url); // {InProgress: "true", Message: "Checking X%";
}
Sometimes the process is hanged and its title is not updated anymore like something goes in infinite loop.
If I use console without starting from WPF ( I mean use command prompt and then set location to exe path and run it with arguments), it works fine, no issue.
Why does this thing happens ?
A deadlock condition can result if the parent process calls
p.WaitForExit before p.StandardOutput.ReadToEnd and the child process
writes enough text to fill the redirected stream. The parent process
would wait indefinitely for the child process to exit. The child
process would wait indefinitely for the parent to read from the full
StandardOutput stream.
To avoid deadlocks you should use asynchronous methods:
var procStartInfo = new ProcessStartInfo
{
FileName = _exePath,
Arguments = arguments,
UseShellExecute = false,
CreateNoWindow = false,
RedirectStandardOutput = true,
RedirectStandardError = true
};
var p = new Process { StartInfo = procStartInfo };
p.OutputDataReceived += (sender, eventArgs) => { Console.WriteLine(eventArgs.Data); };
p.Start();
p.BeginOutputReadLine();
p.WaitForExit();
I'm trying to write a program which executes 2 different .exe files and outputs their results to a text file. When I execute them separately, they work fine, but when I try to execute them both, the second process doesn't run. Can anyone help?
Here is the code. Player1.exe and Player2.exe are console applications returning 0 or 1.
static void Main(string[] args)
{
Process process1 = new Process();
process1.StartInfo.FileName = "C:/Programming/Tournament/Player1.exe";
process1.StartInfo.Arguments = "";
process1.StartInfo.UseShellExecute = false;
process1.StartInfo.RedirectStandardOutput = true;
process1.Start();
var result1 = process1.StandardOutput.ReadToEnd();
process1.Close();
Process process2 = new Process();
process2.StartInfo.FileName = "C:/Programming/Tournament/Player2.exe";
process2.StartInfo.Arguments = "";
process2.StartInfo.UseShellExecute = false;
process2.StartInfo.RedirectStandardOutput = true;
process2.Start();
string result2 = process2.StandardOutput.ReadToEnd().ToString();
process2.Close();
string resultsPath = "C:/Programming/Tournament/Results/Player1vsPlayer2.txt";
if (!File.Exists(resultsPath))
{
StreamWriter sw = File.CreateText(resultsPath);
sw.WriteLine("Player1 " + "Player2");
sw.WriteLine(result1 + " " + result2);
sw.Flush();
}
}
1.
you wrote in a comment: "The program doesn't even reach to process2. I tested that by putting a breakpoint."
process1.Start() may be throwing an exception. Rather than setting a breakpoint at process2, step through the lines and find the exception. Or better yet, add a try/catch block and report an error.
2.
Another possibility is that ReadToEnd is not behaving as expected. You can Peek and see if there is more data to read by looping like this:
var result1 = new StringBuilder()
while (process1.StandardOutput.Peek() > -1)
{
result1.Append(process1.StandardOutput.ReadLine());
}
3.
If these processes don't immediately provide data and end, then you need to get them both started before you begin waiting on their StandardOutput. Call Start() on each process before doing the reads.
I don't know too much about using process, but I use this approach when I need separate things to run at once. I didn't pull in the bottom portion of your project, but check this out to see if it helps in any way.
class Program
{
static void Main(string[] args)
{
Process process1 = new Process();
process1.StartInfo.FileName = "C:/Programming/Tournament/Player1.exe";
process1.StartInfo.Arguments = "";
process1.StartInfo.UseShellExecute = false;
process1.StartInfo.RedirectStandardOutput = true;
Process process2 = new Process();
process2.StartInfo.FileName = "C:/Programming/Tournament/Player2.exe";
process2.StartInfo.Arguments = "";
process2.StartInfo.UseShellExecute = false;
process2.StartInfo.RedirectStandardOutput = true;
Thread T1 = new Thread(delegate() {
doProcess(process1);
});
Thread T2 = new Thread(delegate() {
doProcess(process2);
});
T1.Start();
T2.Start();
}
public static void doProcess(Process myProcess)
{
myProcess.Start();
var result1 = myProcess.StandardOutput.ReadToEnd();
myProcess.Close();
}
}
I have this code that execute shell commands:
public void ExecuteShellCommand(string _FileToExecute, string _CommandLine, ref string _outputMessage, ref string _errorMessage)
{
//Set process variable.
//Provides access to local and remote processes and enables you to start and stop local system processes.
System.Diagnostics.Process _Process = null;
try
{
_Process = new System.Diagnostics.Process();
_Process.StartInfo.Verb = "runas";
//Invokes the cmd process specifying the command to be executed.
var culture = new System.Globalization.CultureInfo("pt-BR", true);
Thread.CurrentThread.CurrentUICulture = new CultureInfo("pt-BR", false);
string _CMDProcess = string.Format(culture, #"{0}\cmd.exe",
new object[] { Environment.SystemDirectory });
//Pass executing file to cmd (Windows command interpreter) as a arguments
// /C tells cmd we want it to execute the comand that follows, then exit.
string _Arguments = string.Format(culture, "/C {0}",
new object[] { _FileToExecute });
//Pass any command line parameters for execution
if (!string.IsNullOrEmpty(_CommandLine))
{
_Arguments += string.Format(culture, " {0}",
new object[] { _CommandLine, culture });
}
var _ProcessStartInfo =
new System.Diagnostics.ProcessStartInfo(_CMDProcess, _Arguments);
//Sets a value indicating not to start the process in a new window.
_ProcessStartInfo.CreateNoWindow = true;
//Sets a value indicating now to use the operating system shell to start the process.
_ProcessStartInfo.UseShellExecute = false;
//Sets the value that indicates the output/input/error of an aplication is written to the Process.
_ProcessStartInfo.RedirectStandardOutput = true;
_ProcessStartInfo.RedirectStandardInput = true;
_ProcessStartInfo.RedirectStandardError = true;
_Process.StartInfo = _ProcessStartInfo;
//Starts a process resource and associates it with a Process component.
_Process.Start();
//Instructs the Process component t wait indefitely for the associated process to exit.
_errorMessage = _Process.StandardError.ReadToEnd();
_Process.WaitForExit();
//Instructs the Process component to wait indefinitely for the associated process to exit.
_outputMessage = _Process.StandardOutput.ReadToEnd();
_Process.WaitForExit();
}
catch (Win32Exception _Win32Exception)
{
//Error
MessageBox.Show("Win32 Exception caught in process: " + _Win32Exception.ToString());
}
catch (Exception _Exception)
{
//Error
MessageBox.Show("Exception caught in process: " + _Exception.ToString());
}
finally
{
_Process.Close();
_Process.Dispose();
_Process = null;
}
}
The problem is that my system language is pt-BR, the output:
_outputMessage = _Process.StandardOutput.ReadToEnd();
returns broken strings:
Returned string: "Autentica‡Æo"
Expected string: "Autenticação"
But if I use the same command inside CMD, everything returns okay, no erros or broken strings...
What is wrong with my code?
EDIT:
I'm trying execute shell commands via code. Using cmd.exe + arguments.
Working:
_ProcessStartInfo.StandardOutputEncoding = Encoding.GetEncoding(850);
Now, the encoding matches.
It is code page 850, the MS-Dos code page for Portuguese. ç = 0x87, ã = 0xc6.
Your program is currently incorrectly using code page 1252, 0x87 = ‡, 0xc6 = Æ.
I've written some code to interact with a console app that regularly hangs (due to buggy COM interop I have no control over). My method includes a call to Process.Kill() after a timeout, but it doesn't seem to kill the process--it still appears in Task Manager. Is there something wrong with this code?
private static string CallBuggyConsoleApp(string path, string ext) {
var startInfo = new ProcessStartInfo {
FileName = ConsoleAppPath,
Arguments = String.Format("\"{0}\" {1}", path, ext),
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true
};
using (var proc = Process.Start(startInfo)) {
//The line above should be replaced with:
//using (var proc = new Process()) {
// proc.StartInfo = startInfo;
var output = new StringBuilder();
var error = new StringBuilder();
proc.OutputDataReceived += (_, args) => output.Append(args.Data);
proc.ErrorDataReceived += (_, args) => error.Append(args.Data);
proc.Start();
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
if (proc.WaitForExit((int)ConsoleAppTimeout.TotalMilliseconds)) {
proc.WaitForExit();
if (proc.ExitCode != 0) {
throw new Exception(String.Format("Pid {0} exited at {1} with exit code {2} and the following error: {3}",
proc.Id, proc.ExitTime, proc.ExitCode, error.ToString()));
}
return output.ToString();
}
proc.CancelOutputRead();
proc.CancelErrorRead();
proc.Kill();
proc.WaitForExit();
throw new Exception(String.Format("Killed pid {0} at {1}", proc.Id, proc.ExitTime));
}
}
in the inner part of the code where you do the first throw you are not calling the Kill.
in general Exception should not be used in the throws, it's more usual to throw some derived classes of it like ApplicationException or other more specialized ones.
beside this, why are you calling start twice? How would it behave calling start only once? Do you see any difference?