So basicly the ReadLine() method just hangs right on the last output line.
I dont need to end the process. The main goal is to do "cmd-shell" with stdin and stdout (in variables/Printed out).
If the same can be achieved with async methods, please, leave it in comments.
Thanks in advance.
{
Process p = new Process()
{
StartInfo = new ProcessStartInfo("cmd")
{
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardError = true,
RedirectStandardOutput = true,
CreateNoWindow = true,
Arguments = "/K",
}
};
p.ErrorDataReceived += (s, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
Console.WriteLine(e.Data);
}
};
p.Start();
while (true)
{
Console.Write("/SHELL/ #> ");
input = Console.ReadLine();
p.StandardInput.WriteLine(input);
p.BeginErrorReadLine();
Console.WriteLine("Errorgoing...");
while (p.StandardOutput.Peek() > -1) {
Console.WriteLine(p.StandardOutput.Peek());
Console.WriteLine(p.StandardOutput.ReadLine());
// When it hangs, Peek() outputs 67, that represents "C" char.
// Seems like just the last stdout line dont want to be printed out.
}
}
}
}```
After few implemented tricks I've ended up with the following code. Quite tricky.
static async Task Main(string[] args)
{
using (SemaphoreSlim semaphore = new SemaphoreSlim(0, 1))
{
Process p = new Process()
{
StartInfo = new ProcessStartInfo("cmd")
{
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardError = true,
RedirectStandardOutput = true,
CreateNoWindow = true,
Arguments = "/K set PROMPT=PROMPT$P$G$_",
}
};
p.ErrorDataReceived += (s, e) =>
{
if (e.Data?.Length > 0)
{
Console.WriteLine(e.Data);
}
};
bool wasprompt = false;
string prompt = "";
p.OutputDataReceived += (s, e) =>
{
if (e.Data?.Length > 0)
{
if (e.Data.StartsWith("PROMPT") && e.Data.EndsWith(">"))
{
prompt = e.Data.Substring(6, e.Data.Length - 7);
semaphore.Release();
wasprompt = true;
}
else
{
if (!wasprompt)
Console.WriteLine(e.Data);
else
wasprompt = false;
}
}
};
p.Start();
p.BeginErrorReadLine();
p.BeginOutputReadLine();
await semaphore.WaitAsync();
while (!p.HasExited)
{
Console.Write($"/SHELL/ {prompt}#> ");
string input = Console.ReadLine();
p.StandardInput.WriteLine(input);
if (input == "exit") break;
await semaphore.WaitAsync();
}
p.WaitForExit();
}
Console.WriteLine("Bye.");
Console.ReadKey();
}
Few times tested and looks like it works as you expect.
The idea was adding $_ to PROMPT environment variable to force the prompt printed to output. Then I catch that prompt and implement input/output synchronization with SemaphoreSlim.
Related
I want to run a process in the Windows console, after that, I want to pass (with button click) some commands and see the result in a RichTextBox.
I’m able to launch the program and read the responses after starting, but when I try to send any commands, it doesn’t work: I’m not able to communicate with the process.
Below the code:
public static void SortOutputHandler(object sendingProcess, DataReceivedEventArgs outLine) {
// Collect the sort command output.
if (!String.IsNullOrEmpty(outLine.Data)) {
numOutputLines++;
// Add the text to the collected output.
sortOutput.Append(Environment.NewLine + $"[{numOutputLines}] - {outLine.Data}");
//RichTextBox
MCM.ActiveForm.Invoke(MCM.AffichageTextDelegate, new object[] { outLine.Data });
}
}
public static async Task<int> RunProcessAsync() {
using (var process = new Process {
StartInfo = {
FileName = "AAA.exe",
Arguments = “-v COM74",
UseShellExecute = false,
CreateNoWindow = false,
RedirectStandardOutput = true,
RedirectStandardInput = true,
RedirectStandardError = true
},
EnableRaisingEvents = true
})
{ return await RunProcessAsync(process).ConfigureAwait(false); }
}
private static Task<int> RunProcessAsync(Process process) {
var tcs = new TaskCompletionSource<int>();
process.Exited += (s, ea) => tcs.SetResult(process.ExitCode);
process.OutputDataReceived += (s, ea) => Console.WriteLine(ea.Data);
sortOutput = new StringBuilder();
process.OutputDataReceived += SortOutputHandler;
process.ErrorDataReceived += (s, ea) => Console.WriteLine("ERR: " + ea.Data);
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
return tcs.Task;
}
private async void Btn_Click(object sender, EventArgs e) {
await RunProcessAsync();
}
It's working with a synchronous process :
public static Process myProcess;
public static StreamWriter myStreamWriter;
Process myProcess = new Process();
myProcess.StartInfo.FileName = "AAA.exe";
myProcess.StartInfo.Arguments = '-' + "p" + " " + ComboBoxPortCom.Text;
myProcess.StartInfo.UseShellExecute = false;
myProcess.StartInfo.RedirectStandardInput = true;
myProcess.StartInfo.RedirectStandardOutput = true;
myProcess.StartInfo.RedirectStandardError = true;
myProcess.OutputDataReceived += (s, ea) => Console.WriteLine(ea.Data);
sortOutput = new StringBuilder();
myProcess.OutputDataReceived += SortOutputHandler;
myProcess.ErrorDataReceived += (s, ea) => Console.WriteLine("ERR: " + ea.Data);
myProcess.Start();
myProcess.BeginOutputReadLine();
myProcess.BeginErrorReadLine();
myStreamWriter = myProcess.StandardInput;
myStreamWriter.WriteLine("Command1" + '\n');
Lots of answers to other questions tell you that you can capture the output of a process using code looking something like this:
public async Task Run()
{
await Task.Run(() =>
{
using (Process process = new Process()
{
StartInfo = new ProcessStartInfo
{
WindowStyle = ProcessWindowStyle.Normal,
RedirectStandardOutput = true,
RedirectStandardError = true,
FileName = "cmd",
Arguments = "path/filename.bat",
}
})
{
process.OutputDataReceived += Process_OutputDataReceived;
process.ErrorDataReceived += Process_ErrorDataReceived;
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
}
});
}
private void Process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
log.Error(e.Data);
}
}
private void Process_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
log.Info(e.Data);
}
}
There are lots of variants which achieve the same thing more or less but they all redirect the standard output/errors - is there a way to capture them while still having them show in the cmd window?
I know how can I spawn a new process to execute as many tasks as I want, however I would like to execute them all in one bash/cmd.exe.
The problem is, of course that I don't know when it is done since WaitForExit() never happens (the bash can run forever).
However maybe it is possible to know when the StandardInput.WriteLine() is available so that I know that bash has finished with the task and I can execute next one, or if the last one is finished - close the bash.
Complete Code (Fiddle)
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace ProcessExample
{
class Program
{
static void Main(string[] args)
{
var commands = new List<string> { "dir", "cd C:/", "dir" };
var startInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
CreateNoWindow = true
};
using (var process = Process.Start(startInfo))
{
commands.ForEach(command => { process.StandardInput.WriteLine(command); });
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.ErrorDataReceived += (object sender, DataReceivedEventArgs e) => { Console.WriteLine(e.Data); };
process.OutputDataReceived += (object sender, DataReceivedEventArgs e) => { Console.WriteLine(e.Data); };
process.WaitForExit(); // This never happends.
}
Console.ReadLine();
}
}
}
I have a program that executes a batch file using an asynchronous background worker. Here is the code:
public static void Execute(CmdObj obj, bool batch)
{
CmdObj = obj;
var theWorker = new BackgroundWorker();
theWorker.RunWorkerCompleted += WorkerCompleted;
theWorker.WorkerReportsProgress = true;
if(batch)
{
theWorker.DoWork += WorkerBatchWork;
}else{
theWorker.DoWork += WorkerCommandWork;
}
theWorker.RunWorkerAsync();
}
private static void WorkerBatchWork(object sender, DoWorkEventArgs e)
{
RunBatch(CmdObj);
}
private static void WorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
var temp = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
if (temp != null)
ProgramPath = temp.Substring(6);
WriteLog(CmdObj.Activity, false);
WriteLog(CmdObj.Error, true);
CmdObj.TheButton.Enabled = true;
}
private static void RunBatch(CmdObj obj)
{
var process = new Process();
var startInfo = new ProcessStartInfo
{
FileName = obj.SrcFile,
WindowStyle = ProcessWindowStyle.Normal,
CreateNoWindow = false,
RedirectStandardInput = true,
RedirectStandardOutput = false,
RedirectStandardError = true,
UseShellExecute = false
};
try
{
if (!obj.SrcFile.ToLower().Trim().EndsWith(".bat"))
throw new FileLoadException("Not a valid .bat file",obj.SrcFile);
process.StartInfo = startInfo;
process.Start();
process.WaitForExit();
//obj.Activity = process.StandardOutput.ReadToEnd();
obj.Error = process.StandardError.ReadToEnd();
}
catch (Exception ex)
{
obj.Exception = ex;
}
finally
{
process.Close();
}
}
class CmdObj
{
public string Command { get; set; }
public string SrcFile { get; set; }
public string Activity { get; set; }
public string Error { get; set; }
public Exception Exception { get; set; }
public Button TheButton { get; set; }
}
So when I run this program and choose a batch file to execute, I get a blank CMD window. Is there some way to show the output in the CMD window when my program executes the batch file? Alternatively, if I could have the CMD output sent to a textbox or some other control (in real-time), that would work as well.
Thanks!
Since you have one of the following properties set to true, you'll get a blank black Window
RedirectStandardInput
RedirectStandardOutput
RedirectStandardError
This will actually happen because you've told your application to receive/send the output/input from the target process
I see that you were trying to get the output from the batch file using this line
//obj.Activity = process.StandardOutput.ReadToEnd();
Unfortunately, this won't work unless you set RedirectStandardOutput to True so that your application would have the ability to read the output from the target batch file. Otherwise, the output will be empty
Example
private void RunBatch(CmdObj obj)
{
var process = new Process();
var startInfo = new ProcessStartInfo
{
FileName = obj.SrcFile,
WindowStyle = ProcessWindowStyle.Normal,
CreateNoWindow = false,
RedirectStandardInput = true,
RedirectStandardOutput = true, //This is required if we want to get the output
RedirectStandardError = true,
UseShellExecute = false
};
try
{
if (!obj.SrcFile.ToLower().Trim().EndsWith(".bat"))
throw new FileLoadException("Not a valid .bat file",obj.SrcFile);
process.StartInfo = startInfo;
process.Start();
process.WaitForExit();
obj.Activity = process.StandardOutput.ReadToEnd(); //Get the output from the target process
obj.Error = process.StandardError.ReadToEnd();
}
catch (Exception ex)
{
obj.Exception = ex;
}
finally
{
process.Close(); //Terminate the process after getting what is required
}
}
Notice that: UseShellExecute must be False in order to use RedirectStandardOutput
Thanks,
I hope you find this helpful :)
Don't you need to call
process.BeginErrorReadLine();
process.BeginOutputReadLine();
after process starts?
I had some problems without this if I remember right.
Can I start a process (using C# Process.Start()) in the same console as the calling program? This way no new window will be created and standard input/output/error will be the same as the calling console application. I tried setting process.StartInfo.CreateNoWindow = true; but the process still starts in a new window (and immediately closes after it finishes).
You shouldn't need to do anything other than set UseShellExecute = false, as the default behaviour for the Win32 CreateProcess function is for a console application to inherit its parent's console, unless you specify the CREATE_NEW_CONSOLE flag.
I tried the following program:
private static void Main()
{
Console.WriteLine( "Hello" );
var p = new Process();
p.StartInfo = new ProcessStartInfo( #"c:\windows\system32\netstat.exe", "-n" )
{
UseShellExecute = false
};
p.Start();
p.WaitForExit();
Console.WriteLine( "World" );
Console.ReadLine();
}
and it gave me this output:
You could try redirecting the output of this process and then printing it on the calling process console:
public class Program
{
static void Main()
{
var psi = new ProcessStartInfo
{
FileName = #"c:\windows\system32\netstat.exe",
Arguments = "-n",
RedirectStandardOutput = true,
UseShellExecute = false
};
var process = Process.Start(psi);
while (!process.HasExited)
{
Thread.Sleep(100);
}
Console.WriteLine(process.StandardOutput.ReadToEnd());
}
}
Alternative approach using the Exited event and a wait handle:
static void Main()
{
using (Process p = new Process())
{
p.StartInfo = new ProcessStartInfo
{
FileName = #"netstat.exe",
Arguments = "-n",
RedirectStandardOutput = true,
UseShellExecute = false
};
p.EnableRaisingEvents = true;
using (ManualResetEvent mre = new ManualResetEvent(false))
{
p.Exited += (s, e) => mre.Set();
p.Start();
mre.WaitOne();
}
Console.WriteLine(p.StandardOutput.ReadToEnd());
}
}