I've written a simple program that captures and executes command line Python scripts, but there is a problem. The text passed to a Python input function isn't written to my program despite my program capturing stdout.
For example:
The Python script:
import sys
print("Hello, World!")
x = input("Please enter a number: ")
print(x)
print("This work?")
Would write "Hello, World!" then stop. When I pass it a number it would continue on writing "Please enter a number: 3". What is going on? Any solutions? My C# is as follows:
public partial class PyCon : Window
{
public string strPythonPath;
public string strFile;
public string strArguments;
private StreamWriter sw;
public PyCon(string pythonpath, string file, string args)
{
strPythonPath = pythonpath;
strFile = file;
strArguments = args;
InitializeComponent();
Process p = new Process();
p.StartInfo.FileName = strPythonPath;
p.StartInfo.Arguments = "\"" + strFile + "\" " + strArguments;
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.OutputDataReceived += new DataReceivedEventHandler(p_OutputDataReceived);
p.ErrorDataReceived += new DataReceivedEventHandler(p_ErrorDataReceived);
p.Start();
p.BeginOutputReadLine();
p.BeginErrorReadLine();
sw = p.StandardInput;
}
private void p_OutputDataReceived(object sendingProcess, DataReceivedEventArgs received) {
if (!String.IsNullOrEmpty(received.Data)) {
AppendConsole(received.Data);
}
}
private void p_ErrorDataReceived(object sendingProcess, DataReceivedEventArgs received) {
if (!String.IsNullOrEmpty(received.Data)) {
AppendConsole(received.Data);
}
}
private void AppendConsole(string message) {
if (!txtConsole.Dispatcher.CheckAccess()) {
txtConsole.Dispatcher.Invoke(DispatcherPriority.Normal, (System.Windows.Forms.MethodInvoker)delegate() { txtConsole.AppendText(message + "\n"); });
} else {
//Format text
message = message.Replace("\n", Environment.NewLine);
txtConsole.AppendText(message + "\n");
}
}
private void txtInput_KeyUp(object sender, KeyEventArgs e) {
if (e.Key != Key.Enter) return;
sw.WriteLine(txtInput.Text);
txtInput.Text = "";
}
}
Edit: After a lot of research and help from this thread, I've come to the conclusion that the problem is with the Python input command not calling the C# DataReceivedEventHandler. There may not be a solution to this besides scripting changes. If that is the case, I'll make the answer containing those changes as accepted. Thanks for the help, guys!
Smells like the Python i/o is line buffered, i.e. waits for a CRLF then sends a whole line at once. You could try turning that off (python -u myscript.py, or set the PYTHONUNBUFFERED environment variable) or work around it with something like this:
print("Hello, World!")
print("Please enter a number: ")
x = input()
print(x)
It's difficult to tell because I'm using python 2.6 and you appear to be using 3.x, and I also have not programmed in c# for quite awhile but my guess is that this line:
sw.WriteLine(txtInput.Text);
Is sending "3" plus a windows new line character.
Try:
sw.Write(txtInput.Text + "\n")
sw.Flush()
This will just send a normal newline instead of a windows carriage return which may be the issue. Make sure you always Flush when dealing with complicated stream communication like this!
One more thing, make sure you can just redirect this at the command prompt. Too often programmers try to do everything at the same time and get stuck:
./stdintest.py < input.txt
If it doesn't work there, it's not going to work in C#. Good luck
Related
This question already has answers here:
How to asynchronously read the standard output stream and standard error stream at once
(4 answers)
Closed 11 months ago.
I have a WPF application in which I am starting a Powershell process and redirecting the standard output to a textbox. I am facing a problem in which when the Powershell process is started, the Powershell window opens up but stays blank. The process is executing in the background however, the textbox is only updated when the Powershell window is closed. I would like for the text box to update and the process to move forward simultaneously.
How can I achieve this? My code is like this:
private async void btnStartPowershell(object sender, RoutedEventArgs e)
{
string powershellPath = #"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe";
Thread psThread = new Thread(() =>
{
string cOut = OpenPowershell(powershellPath, Installer, logPath, "Config Builder");
if (cOut.Contains("error"))
{
ErrorStatusStack.Visibility = Visibility.Visible;
}
Action action = () => doUIUpgrade(cOut, logPath);
this.BeginInvoke(action);
});
psThread.Start();
txtboxCompletedProcess.Text = $"Powershell Scripts Completed. Please see the logs for details.";
}
private void doUIUpgrade(string cOut, string logPath)
{
PSOutputTextBlock.Text = cOut;
if (cOut.Contains("OCMS INSTALLATION STARTED"))
{
Console.WriteLine("YES");
}
InstallationStartedTextBlock.Text = $"Installation started. Please do not close this window. More details can be found at {logPath}";
}
private string OpenPowershell(string path, string script)
{
try
{
bool is64 = IntPtr.Size == 8;
var ENV = "Get-ItemProperty HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\* "
+ (is64 ? ",HKLM:\\Software\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*" : "")
+ " | Select-Object DisplayName";
ProcessStartInfo startPowershell = new ProcessStartInfo(path, ENV);
startPowershell.UseShellExecute = false;
startPowershell.Arguments = script;
startPowershell.RedirectStandardOutput = true;
startPowershell.EnvironmentVariables.Add("RedirectStandardOutput", "true");
startPowershell.EnvironmentVariables.Add("RedirectStandardError", "true");
startPowershell.EnvironmentVariables.Add("UseShellExecute", "false");
startPowershell.EnvironmentVariables.Add("CreateNoWindow", "true");
Process psProcess = Process.Start(startPowershell);
string output = psProcess.StandardOutput.ReadToEnd();
psProcess.WaitForExit();
return output;
}
catch (Exception error)
{
MessageBox.Show(error.Message);
return error.ToString();
}
}
I have tried doing string output = psProcess.StandardOutput.ReadLine(); instead of string output = psProcess.StandardOutput.ReadToEnd(); but I still only get the text box updated when the entire script is completed and not before.
I'll appreciate any help here.
you should create another thread for your psProcess's getting stdout and update by timers. it could helps you
When trying to use stdin and stdout in C# (Unity) to pipe to a Python process, I get about a dozen or so transactions and the process breaks and the error "ObjectDisposedException: The object was used after being disposed."
After trying several of the more obvious things, I'm bringing the problem here perhaps someone know just the right technique. Thanks in advance.
Here's the C# Startup code:
Process pyProcess; // <=== fixed
ProcessStartInfo pyStartInfo;
public StreamReader pyStreamReader;
public StreamWriter pyStreamWriter;
public void startPython()
{
// Create new process start info
pyStartInfo = new ProcessStartInfo(pyPath)
{
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
Arguments = pyApp + " " + pyArgs
};
pyProcess = new Process { StartInfo = pyStartInfo };
pyProcess.Start();
pyStreamReader = pyProcess.StandardOutput;
pyStreamWriter = pyProcess.StandardInput;
pyStreamWriter.WriteLine("Hello!");
string str = pyStreamReader.ReadLine();
Debug.LogFormat(str + "\n");
}
void Start()
{
if(testPython == true)
startPython();
Here is the fragment that generates data sent to python at each update...
if (controller.testPython)
{
string str, python;
str = String.Format("data to send");
pyStreamWriter.DiscardBufferedData(); #<==== fixed
pyStreamWriter.WriteLine(str);
python = pyStreamReader.ReadLine();
Debug.LogFormat("python says: " + python + "\n");
}
And here is the simplified python process that's echoing the data
while True:
cmd = input() # read a command from c#
print(cmd) # process the cmd, here we just echo it back to c#
After a little experimentation, I discovered that adding
pyStreamReader.DiscardBufferedData();
before
pyStreamWriter.WriteLine(str);
solves the main problem and this simple form of piping seems to work, at least for hundreds of transactions that I observed.
I also had to declare pyProcess outside the scope so the code so that its handle is not released. That resolved the ObjectDisposed exception.
I made a little program that execute executable file (.exe) but when you write down no existed file I get an error, the file specific not found.
So I'm wondering if there's a way to check if the process exists first before running it, and if it doesn't exist you can show a message box.
This is my code
private void btn_Start_Click(object sender, EventArgs e)
{
string text = textBox1.Text;
Process process = new Process();
if (!textBox1.Text.Contains(".exe"))
{
process.StartInfo.FileName = text + ".exe";
}
else
{
process.StartInfo.FileName = text;
}
process.Start();
}
Check that file is exists before start process:
var processFileName = !textBox1.Text.Contains(".exe")
? text + ".exe"
: text;
if (File.Exists(processFileName))
{
Process process = new Process();
process.Start(processFileName);
}
Please try the following code:
->(You have to add #using System.IO; before using "File.Exists" command)
button1_Click(object sender, EventArgs )
{
string exepath = "C:\\example\\example.xlsx";
if(File.Exists(exepath))
{
Process.Start(exepath);
}
else
{
MessageBox.Show("File not found!");
}
}
Hope it's working !
You can string the path and string the filename in other string, and then you can even check the Folder, and if the folder exists ,then check the file!
Also you can use your textbox1 as filename, but you have to add the path, unless it will be search in the program directory.(bin/debug)
If i was wrong, forgive me, i am learning c# at the moment!
Have a good day!
So what i am doing is calling java.exe and making a call to a jar file with some arguments. If everyting is fine and the command works, then the arguments are printed out to a richtext box.
The issue I am having is when the command isn't correct. So in once instance, say the user types in the wrong password that's passed into txtPassword. Stanadard out is not being redirected. If i run the exact same command in a Dos console, the message "Error: Auth fail", is being presented. How do i redirect that error to the rich text box? I thought redirecting stdout would do it, but apparently not.
Any help would be apprecaited. Please see the code below.
//Declare and instantiate a new process component.
System.Diagnostics.Process process1;
process1 = new System.Diagnostics.Process();
process1.StartInfo.UseShellExecute = false;
process1.StartInfo.RedirectStandardOutput = true;
process1.StartInfo.CreateNoWindow = true;
process1.StartInfo.FileName = "java.exe ";
toLoad = lstBarToLoad.Items[i].Text;
process1.StartInfo.Arguments = "-Xmx512M -jar Deploy.jar" + txtPassword;
process1.StartInfo.Arguments += toLoad;
Console.WriteLine(process1.StartInfo.Arguments);
process1.Start();
process1.OutputDataReceived += (s, a) => myMethod(a);
process1.BeginOutputReadLine();
//myMthod
private void myMethod(DataReceivedEventArgs e)
{
if (e.Data != null)
{
Action action = () => rchsdtOut.Text += "\r\n" + e.Data.ToString();
rchsdtOut.BeginInvoke(action, null);
Console.WriteLine(e.Data.ToString());
}
}//end of private
As well as using
process1.StartInfo.RedirectStandardOutput = true;
You also need to use
process1.StartInfo.RedirectStandardError = true;
Gets or sets a value that indicates whether the error output of an application is written to the Process.StandardError stream
http://msdn.microsoft.com/en-us/library/system.diagnostics.processstartinfo.redirectstandarderror.aspx
You can then read out the error and do with it as you please:
string error = process1.StandardError.ReadToEnd();
You can use RedirectStandardError property same way as you use the RedirectStandardOutput property.
i want to launch ffmpeg from my app and retrive all console output that ffmpeg produces. Thing seems obvious, i followed many forum threads/articles like this one but i have problem, though i follow all information included there I seem to end up in dead end.
String that should contain output from ffmpeg is always empty. I've tried to see where is the problem so i made simple c# console application that only lists all execution parameters that are passed to ffmpeg, just to check if problem is caused by ffmpeg itself. In that case everything work as expected.
I also did preview console window of my app. When i launch ffmpeg i see all the output in console but the function that should recieve that output for further processing reports that string was empty. When my param-listing app is launched the only thing I see is the expected report from function that gets output.
So my question is what to do to get ffmpeg output as i intended at first place.
Thanks in advance
MTH
This is a long shot, but have you tried redirecting StandardError too?
Here is a part of my ffmpeg wrapper class, in particular showing how to collect the output and errors from ffmpeg.
I have put the Process in the GetVideoDuration() function just so you can see everything in the one place.
Setup:
My ffmpeg is on the desktop, ffPath is used to point to it.
namespace ChildTools.Tools
{
public class FFMpegWrapper
{
//path to ffmpeg (I HATE!!! MS special folders)
string ffPath = System.Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\ffmpeg.exe";
//outputLines receives each line of output, only if they are not zero length
List<string> outputLines = new List<string>();
//In GetVideoDuration I only want the one line of output and in text form.
//To get the whole output just remove the filter I use (my search for 'Duration') and either return the List<>
//Or joint the strings from List<> (you could have used StringBuilder, but I find a List<> handier.
public string GetVideoDuration(FileInfo fi)
{
outputLines.Clear();
//I only use the information flag in this function
string strCommand = string.Concat(" -i \"", fi.FullName, "\"");
//Point ffPath to my ffmpeg
string ffPath = System.Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\ffmpeg.exe";
Process processFfmpeg = new Process();
processFfmpeg.StartInfo.Arguments = strCommand;
processFfmpeg.StartInfo.FileName = ffPath;
//I have to say that I struggled for a while with the order that I setup the process.
//But this order below I know to work
processFfmpeg.StartInfo.UseShellExecute = false;
processFfmpeg.StartInfo.RedirectStandardOutput = true;
processFfmpeg.StartInfo.RedirectStandardError = true;
processFfmpeg.StartInfo.CreateNoWindow = true;
processFfmpeg.ErrorDataReceived += processFfmpeg_OutData;
processFfmpeg.OutputDataReceived += processFfmpeg_OutData;
processFfmpeg.EnableRaisingEvents = true;
processFfmpeg.Start();
processFfmpeg.BeginOutputReadLine();
processFfmpeg.BeginErrorReadLine();
processFfmpeg.WaitForExit();
//I filter the lines because I only want 'Duration' this time
string oStr = "";
foreach (string str in outputLines)
{
if (str.Contains("Duration"))
{
oStr = str;
}
}
//return a single string with the duration line
return oStr;
}
private void processFfmpeg_OutData(object sender, DataReceivedEventArgs e)
{
//The data we want is in e.Data, you must be careful of null strings
string strMessage = e.Data;
if outputLines != null && strMessage != null && strMessage.Length > 0)
{
outputLines.Add(string.Concat( strMessage,"\n"));
//Try a Console output here to see all of the output. Particularly
//useful when you are examining the packets and working out timeframes
//Console.WriteLine(strMessage);
}
}
}
}