I'm writing an application that creates a batch file and then run :
I know I can create a Batch file and run it
no problem with that .
What I want to do is :
once I have created the string that makes the file , Is there is any way to execute the string as a Batch file ?
something like
string BatchFile = "echo \"bla bla\" \n iperf -c 123 ... ... .. "
Diagnostics.Process.Start(BatchFile);
You can run CMD.EXE with /c as an executable and have the rest as arguments :
Process.Start("cmd.exe", "/c echo \"bla bla\" \n iperf -c 123 ... ... .. ");
for me, I am using this code:
Process process;
private void button1_Click(object sender, EventArgs e)
{
process = new Process();
process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = "cmd";
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardInput = true;
process.Start();
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerAsync();
process.StandardInput.WriteLine("cd d:/tempo" );
process.StandardInput.WriteLine("dir");
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
string line;
while (!process.StandardOutput.EndOfStream)
{
line = process.StandardOutput.ReadLine();
if (!string.IsNullOrEmpty(line))
{
SetText(line);
}
}
}
delegate void SetTextCallback(string text);
private void SetText(string text)
{
if (this.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.textBox1.Text += text + Environment.NewLine;
}
}
private void FrmMain_FormClosing(object sender, FormClosingEventArgs e)
{
process.StandardInput.WriteLine("exit");
process.Close();
}
You may create your Batch "file" as a long string with lines terminated in \n, exactly as you shown in your example, and then execute that string (I called it a "NotBatch-text") executing cmd.exe and redirecting such string into its Stdin standard handle. This way, your "NotBatch-text" may use a large number of Batch features, like expansion of %variables%, IF and FOR commands nested at any level, and many more. You may also use delayed !variable! expansion if you execute cmd.exe with /V:ON switch. Really, the only things that don't works in the NotBatch-text are: parameters and SHIFT command, and GOTO/CALL :label commands; further details at this post.
If you want to execute a more advanced "NotBatch-text" string, you may even simulate GOTO and CALL :label commands with the aid of a third party program, as described at this post.
Related
All,
I am attempting to execute a series of batch files via a C# winforms app. In this early stage, with a test batch file, I am unable to get the process execution to respect the timeout in my batch file unless i set UseShellExecute = true, which is something i am trying to avoid. My goal is to execute the script file and redirect the output to the GUI as shown in the code here:
Process process;
public void ExecuteScript(string workingDirectory, string batchFileName)
{
if (process != null)
process.Dispose();
process = new Process();
process.StartInfo.WorkingDirectory = workingDirectory;
process.StartInfo.FileName = workingDirectory + batchFileName;
process.StartInfo.Arguments = "";
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardInput = true;
process.EnableRaisingEvents = true;
process.OutputDataReceived += proc_OutputDataReceived;
process.Start();
process.BeginOutputReadLine();
process.Exited += OnProcessExit;
}
private void OnProcessExit(object sender, EventArgs e)
{
Console.WriteLine("the script has ended");
}
private void proc_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
this.Invoke((Action)(() =>
{
textBox1.AppendText(Environment.NewLine + e.Data);
}));
(sender as Process)?.StandardInput.WriteLine();
}
my batch file looks like this:
#echo off
echo This is a running script
timeout /t 10
echo Done sleeping. Will Exit
exit
Is there an appropriate combination of settings i can call to prevent the command window from appearing, while still redirecting the output, and executing the script appropriately?
The problem with your code is that the timeout command is not supported when stdin is redirected. This is a good example of why one should always redirect both stdout and stderr. An error message is actually emitted from the batch file, but because you weren't capturing the stderr stream, you didn't see the error message. All too many questions here on Stack Overflow involving Process scenarios that "don't work" could be easily solved had the person looked at the stderr output.
A work-around to this limitation of the timeout command is to use the waitfor command instead, using a known-nonexistent signal name with a timeout value, e.g. waitfor /t 10 placeholder.
Here is a console program that is entirely self-contained and which demonstrates both the failure of the timeout command when stdin is redirected, as well as the work-around of waitfor:
const string batchFileText =
#"#echo off
echo Starting batch file
timeout /t 5
waitfor /t 5 placeholder
echo Timeout completed
exit";
static void Main(string[] args)
{
string batchFileName = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".bat");
try
{
File.WriteAllText(batchFileName, batchFileText);
ProcessStartInfo psi = new ProcessStartInfo
{
FileName = batchFileName,
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardInput = true,
RedirectStandardError = true,
};
Process process = new Process
{
EnableRaisingEvents = true,
};
process.OutputDataReceived += Process_OutputDataReceived;
process.ErrorDataReceived += Process_ErrorDataReceived;
process.Exited += Process_Exited;
process.StartInfo = psi;
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
}
finally
{
File.Delete(batchFileName);
}
}
private static void Process_Exited(object sender, EventArgs e)
{
WriteLine("Process exited");
}
private static void Process_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
WriteLine($"stdout: {DateTime.Now:HH:mm:ss.sss}: {e.Data}");
}
}
private static void Process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
WriteLine($"stderr: {DateTime.Now:HH:mm:ss.sss}: {e.Data}");
}
}
Note that the waitfor command writes a message to stderr if the timeout occurs (which it always will in this case). You may or may not want that to show up in the captured stderr stream. If not, you can redirect the stderr of that command specifically by using 2>nul. E.g. waitfor /t 10 placeholder 2>nul.
I have built a winform interface for my python program, my python program is a real time voice assistant, what I need is the interface should response instantly when the python gives outputs. I need to display standard output to the interface instantly. the below program is what I made.
in this code, the interface is not responding properly. python program executes in background continuously and not responding to the voice. i need a program that execute my python program and display the standard output to the winform interface.
namespace #interface
{
public partial class Form1 : Form
{
public static string text;
public Form1()
{
InitializeComponent();
}
private void pictureBox1_Click(object sender, EventArgs e)
{
}
private async void start_button_Click(object sender, EventArgs e)
{
string line;
int counter=0;
msg.Text = "Hey, Tell me something!";
Task task = new Task(Execute);
task.Start();
}
public void Execute()
{
// full path of python interpreter
string python = #"C:/Users/Jayasooryan/AppData/Local/Programs/Python/Python36-32/python.exe";
// python app to call
string myPythonApp = #"C:/Users/Jayasooryan/AppData/Local/Programs/Python/Python36-32/Avira.py";
// Create new process start info
ProcessStartInfo myProcessStartInfo = new ProcessStartInfo(python);
// make sure we can read the output from stdout
myProcessStartInfo.UseShellExecute = false;
myProcessStartInfo.RedirectStandardOutput = true;
myProcessStartInfo.CreateNoWindow = true;
// start python app with 3 arguments
// 1st arguments is pointer to itself,
// 2nd and 3rd are actual arguments we want to send
myProcessStartInfo.Arguments = myPythonApp;
Process myProcess = new Process();
// assign start information to the process
myProcess.StartInfo = myProcessStartInfo;
// start the process
myProcess.Start();
// Read the standard output of the app we called.
// in order to avoid deadlock we will read output first
// and then wait for process terminate:
StreamReader myStreamReader = myProcess.StandardOutput;
string myString = myStreamReader.ReadLine();
text = myString;
//Console.WriteLine(myString);
/*if you need to read multiple lines, you might use:
string myString = myStreamReader.ReadToEnd() */
// wait exit signal from the app we called and then close it.
myProcess.WaitForExit();
myProcess.Close();
// write the output we got from python app
//Console.ReadLine();
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
}
}
}
First: you should not try to read a single line from the python process, but rather use the OutputDataReceived event to be triggered when the process writes new data.
Second: since the output is buffered, you probably want to flush it after writing in your python process.
So here's a simple python script that keeps writing to the standard output (note how stdout.flush is called):
import random
import time
import sys
while True:
rand = random.randint(1, 10)
time.sleep(1)
print rand
sys.stdout.flush()
if rand in (9, 10):
break
And here's a simple Form that reads the output of that very script:
var f = new Form();
var t = new TextBox();
t.Dock = DockStyle.Fill;
t.Multiline = true;
f.Controls.Add(t);
f.Load += (s, e) => {
Process process = new Process();
process.StartInfo.FileName = "python";
process.StartInfo.Arguments = #"d:\script.py";
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.OutputDataReceived += (s2, e2) => {
t.Text += e2.Data + Environment.NewLine;
};
process.Start();
process.BeginOutputReadLine();
};
f.ShowDialog();
I have a WinForms application that runs multiple processes which run as background workers. I create a background worker for each new Process
BackgroundWorker background = new BackgroundWorker();
background.DoWork += new DoWorkEventHandler(bkWorker_DoWork);
background.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bkWorker_Complete);
background.WorkerReportsProgress = false;
background.WorkerSupportsCancellation = true;
My Dowork code looks like this:
private void bkWorker_DoWork(object sender, DoWorkEventArgs e) {
WorkerArgument obj = (WorkerArgument) e.Argument;
BackgroundWorker worker = (BackgroundWorker) sender;
Process proc = new Process();
proc.StartInfo.FileName = "cmdprocess"; //"cmd.exe";
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.CreateNoWindow = true;
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.RedirectStandardInput = true;
proc.StartInfo.RedirectStandardError = true;
proc.OutputDataReceived += new DataReceivedEventHandler(SortOutputHandler);
proc.ErrorDataReceived += new DataReceivedEventHandler(ErrorOutputHandler);
proc.Start();
process.Add(obj.Row, proc.Id);
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
proc.WaitForExit();
proc.Close();
}
As you may notice that I am using an event handler to sort the output given by the cmdProcess (SortOutputHandler) same as Error output.
I am showing this output in a text box
private void SortOutputHandler(object sendingProcess, DataReceivedEventArgs outLine) {
if (!String.IsNullOrEmpty(outLine.Data)) {
if (txtLog.InvokeRequired) {
this.Invoke((MethodInvoker) delegate {
string[] lines = txtLog.Lines;
if (lines.Length > 200) {
string[] newlines = lines.Skip(200) as string[];
txtLog.Lines = newlines;
}
txtLog.AppendText(Environment.NewLine + outLine.Data);
});
} else {
string[] lines = txtLog.Lines;
if (lines.Length > 200) {
string[] newlines = lines.Skip(200) as string[];
txtLog.Lines = newlines;
}
txtLog.AppendText(Environment.NewLine + outLine.Data);
}
}
}
The Error Handler is as follows:
private void ErrorOutputHandler(object sendingProcess, DataReceivedEventArgs outLine) {
if (!String.IsNullOrEmpty(outLine.Data)) {
if (txtLog.InvokeRequired) {
this.Invoke((MethodInvoker) delegate {
string[] lines = txtLog.Lines;
if (lines.Length > 200) {
string[] newlines = lines.Skip(200) as string[];
txtLog.Lines = newlines;
}
txtLog.AppendText(Environment.NewLine + outLine.Data);
});
} else {
string[] lines = txtLog.Lines;
if (lines.Length > 200) {
string[] newlines = lines.Skip(200) as string[];
txtLog.Lines = newlines;
}
txtLog.AppendText(Environment.NewLine + outLine.Data);
}
}
}
The problem is that when the number of processes increases the log starts running rapidly.
I have created a new Form frmLog with a multi-line textbox inside it, Can you tell me How I can use it to get Output from one process? Like there can be a "View Log" Button and when I click it only the log of that process show.
Also is it possible to write the output of all the processes to unique files separately?
First of all, consider please as an example following type to hold process output
interface IProcessOutput
{
IReadOnlyCollection<string> Cerr { get; }
IReadOnlyCollection<string> Cout { get; }
void OnError(string text);
void OnOutput(string text);
}
Then, starting a new process, instantiate a type that implements this interface and "bind" events to corresponding objects.
I encourage you use Queue (as IReadOnlyCollection) if you want to roll your logs (with 2000 or whatever limit) because Queue holds head and tail internally so it does not allocate/move memory if you keep capacity of some limit. It is fast enough for it.
Change GUI so that ListBox with log output can belong to any of running processes (kind of master/detail). So, once you select a process, you will see corresponding log lines.
Instead of textbox, please use ListBox. Use BeginUpdate() and EndUpdate() while loading strings into it. If your output is huge, use self-drawing in listbox so you do not need to store strings into its' Items collection rather access queue objects directly.
PS: Be ready to receive strings that form not a complete line (without new line char). The event with output is internally called when process flushes file handle (cerr, cout are also file handles). So if process flushes it in the middle of a string - you will get half of the string.
I'm Re-Creating the "command prompt" into a Windows Form.
The application is not working properly; and i can't figure exactly why.
When the form Loads it is suposed to run the cmd.exe (Load the cmd info into "TextBox_Receive"), but it doesnt; and also after writing any command in the "textBox_send" (that sends input); it will only show input after pressing "Enter" key 2 or 3 times.
Any idea what i am missing here?
public partial class Form1 : Form
{
// Global Variables:
private static StringBuilder cmdOutput = null;
Process p;
StreamWriter SW;
public Form1()
{
InitializeComponent();
textBox1.Text = Directory.GetCurrentDirectory();
// TextBox1 Gets the Current Directory
}
private void Form1_Load(object sender, EventArgs e)
{
checkBox1.Checked = true;
// This checkBox activates / deactivates writing commands into the "textBox_Send"
cmdOutput = new StringBuilder("");
p = new Process();
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.EnableRaisingEvents = true;
p.OutputDataReceived += new DataReceivedEventHandler(SortOutputHandler);
p.Start();
SW = p.StandardInput;
p.BeginOutputReadLine();
p.BeginErrorReadLine();
}
private static void SortOutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
// I dont actually understand this part of the code; as this is a "copy" of a snippet i found somewhere. Although it fixed one of my issues to redirect.
{
if (!String.IsNullOrEmpty(outLine.Data))
{
cmdOutput.Append(Environment.NewLine + outLine.Data);
}
}
private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
{
// Send "Enter Key" - Send Command
if (e.KeyChar == 13)
{
SW.WriteLine(txtbox_send.Text);
txtbox_receive.Text = cmdOutput.ToString();
txtbox_send.Clear();
}
}
private void checkBox1_CheckedChanged(object sender, EventArgs e)
{
// Enable / Disable Sending Commands
if (checkBox1.Checked)
txtbox_send.Enabled = true;
else
txtbox_send.Enabled = false;
}
}
}
You might also try capturing the error data.
To do this:
after your line
p.OutputDataReceived += new DataReceivedEventHandler(SortOutputHandler);
enter this line
p.ErrorDataReceived += new DataReceivedEventHandler(SortOutputHandler);
It might also be a problem with cmd.exe.
I think your issue is the use of OutputDataReceived. In the documentation:
The event is enabled during asynchronous read operations on
StandardOutput. To start asynchronous read operations, you must
redirect the StandardOutput stream of a Process, add your event
handler to the OutputDataReceived event, and call BeginOutputReadLine.
Thereafter, the OutputDataReceived event signals each time the process
writes a line to the redirected StandardOutput stream, until the
process exits or calls CancelOutputRead.
See the example code on that page for more details.
However - I'm not sure you need to go that route. Have you tried just reading directly from the StandardOutput stream?
I'm trying to write a Console wrapper WPF gui that simply runs a selection of .bat files, I'd like to be able to view any output from the .bat files "live" (as if it were running in cmd).
I've looked into OutputDataReceived and event handlers which append text and then sent this to the screen, however it still waits until the Process has finished before anything appears on the screen.
How do I get the output from the .bat to appear in "real time"?
Snippits of my code so far (this is in a form):
The form has one button (go) and one multi-line text field (textArea).
private void go_Click(object sender, EventArgs e)
{
ExecuteCommand();
}
public void ExecuteCommand()
{
int ExitCode;
ProcessStartInfo ProcessInfo;
Process Process;
//ProcessInfo = new ProcessStartInfo("cmd.exe", "/C z:\foo.bat");
ProcessInfo = new ProcessStartInfo(#"z:\foo.bat"); ;
ProcessInfo.CreateNoWindow = true;
ProcessInfo.UseShellExecute = false;
ProcessInfo.RedirectStandardError = true;
ProcessInfo.RedirectStandardOutput = true;
Process = new Process();
Process.StartInfo = ProcessInfo;
Process.OutputDataReceived += new DataReceivedEventHandler(OutputToTextArea);
Process.Start();
// Start the asynchronous read of the sort output stream.
Process.BeginOutputReadLine();
Process.WaitForExit();
ExitCode = Process.ExitCode;
Process.Close();
}
private int numOutputLines = 0;
private void OutputToTextArea(object sendingProcess, DataReceivedEventArgs outLine)
{
// Collect the sort command output.
if (!String.IsNullOrEmpty(outLine.Data))
{
numOutputLines++;
this.AppendToTextArea("[" + numOutputLines.ToString() + "] - " + outLine.Data + Environment.NewLine);
}
}
private void AppendToTextArea(string s)
{
if (this.textArea.InvokeRequired)
{
// It's on a different thread, so use Invoke.
this.BeginInvoke (new MethodInvoker(() => textArea.AppendText(s)));
} else {
textArea.AppendText(s);
}
}
Where my foo.bat is just a for loop:
ECHO OFF
FOR /L %%i IN (1,1,10) DO (
echo %%i
ping -n 2 127.0.0.1 >nul
)
Well yeah, you're currently blocking the main thread (which is the UI thread) as you wait for process exit in ExecuteCommand, which is directly called from the UI thread (in go_Click).
Just start a new thread (or use a ThreadPool) (Winforms example):
private void button1_Click(object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(this.ExecuteCommand));
}
public void ExecuteCommand(object state)
{
...
}
If you're using WPF, you probably want to use a BackgroundWorker.
If you wish to keep it simple you can just start a command prompt with the /K argument and pass in the batch file.
string arguments = #"z:\foo.bat";
Process.Start("cmd.exe", "/K " + arguments);
The cmd.exe /K opens and command prompt and runs your foo.bat but remains on the screen.