I am currently trying to develop a program which takes the output of an existing program (written in C) and uses it as input (in C#). The problem I am having is that the existing program prints data in redundant format but it dynamically changes. An example could be a random name generator and I need to make a program that logs all of the random names as they appear.
Could I just pipe this and the output will be grabbed as it comes? The C program is run from a CLI.
You could redirect the output streams from the Process object to get direct access to it. You make a call and it will invoke an event of your choice when output is received. One of my questions may provide some explanation on how to do this - C# Shell - IO redirection:
processObject.StartInfo.RedirectStandardOutput = true;
processObject.OutputDataReceived += new DataReceivedEventHandler(processObject_OutputDataReceived);
/* ... */
processObject.Start();
processObject.BeginOutputReadLine();
And then later:
public void processObject_OutputDataReceived(object sender, DataReceivedEventArgs e) {
ProcessNewData(e.Data);
}
Note that this includes the trailing newline.
Or you can just pipe it and use Console.ReadLine to read it in. On the command line, you would execute:
cprogram | csharp_program
Unfortunately, you can't do this directly with the Process object - just use the method above. If you do choose to go this route, you can use:
string input = "";
int currentChar = 0;
while ( (currentChar = Console.Read()) > -1 ) {
input += Convert.ToChar(currentChar);
}
Which will read from the input until EOF.
If the C# program is also command-line based, running this command from the command-line will chain them together:
my_c_program.exe | my_csharp_program.exe
The C# program will receive the output of the C program, via its standard input stream (Console.Read, etc.)
Related
I have a service that runs as a console app and outputs to stdout and stderr streams to the console. The developers don't want to add any logging capabilities since it was mainly designed for linux world and they have tools like logroate and other means already but don't care about the windows port. I want to redirect those streams into a log and I'm able to do it like this:
service.exe 2>&1 >> service.log
But if I leave this running this log will grow out of control. So I tried using something called logrotateWin but it needs to rename the log file and I get access denied since its in use. So I'm trying to write another app that can write to logfile and rotate it based once a day and only keep last n logs.
I want to be able to run it like:
service.exe 2>&1 | logrotater.exe
So I'm trying to write logrotater with the following code but it seems to fail to read the stream from the pipeline. It can read the stream if it stops like from a simple echo "test" command source, but not from service.exe that continuously keeps streaming data out. Any suggestions?
So this works:
echo "test" | logrotater.exe
But this doesn't work:
service.exe 2>&1 | logrotater.exe
static void Main(string[] args) {
Console.SetIn(new StreamReader(Console.OpenStandardInput(8192))); // This will allow input >256 chars
while (Console.In.Peek() != -1) {
string input = Console.In.ReadLine();
Console.WriteLine("Data read was " + input);
}
}
Add a Log() method that opens and closes the file as needed. Moving to a separate method will make it easier to isolate these changes from the rest of the program. One easy way to open/close as needed is with File.AppendAllText().
static void Main(string[] args)
{
Console.SetIn(new StreamReader(Console.OpenStandardInput(8192))); // This will allow input >256 chars
string input;
while ( (input= Console.ReadLine()) != null)
{
Log(input);
}
}
static void Log(string data)
{
string msg = $"Data read was {data}";
Console.WriteLine(msg);
File.AppendAllText("C:\YourPath", msg);
}
I am writing a C# application that runs user code over the internet. The user code is sent to the application over an HTTP post request, and it is handled on the server (either compiled, then executed, or executed directly for languages such as python, lua, php). I am using the following code to read the output of the process that runs the user's code (either the compiled binary, or the interpreter):
Thread readThread = new Thread(() =>
{
char[] buffer = new char[Console.BufferWidth];
int read = 1;
while (read > 0)
{
read = p.StandardOutput.Read(buffer, 0, buffer.Length);
string data = read > 0 ? new string(buffer, 0, read) : null;
if (data != null)
env.ClientSock.Send(data);
Thread.Sleep(10);
}
});
readThread.Start();
Where p is the process running the user's code, and env.ClientSock is the web socket over which the output is sent to the user. The user can write to the process' standard input stream over this same web socket.
This works absolutely fine for most languages the application supports (C#, VB, C++, Python, PHP, Java). However, for C and Lua, if the user's code involves reading from the standard input stream, this read operations blocks before any output is given.
For example, the following C program:
#include <stdio.h>
int main(){
printf("Hello, World!");
int i;
scanf("%d", &i);
return 0;
}
Will not print anything until the user gives some input. For C, I am using the MSVC compiler that comes with Visual Studio. The same issue occurs when running Lua code:
io.write("Hello World!")
io.read()
Nothing is printed until the user enters some input. I cannot figure out why this is happening, and I would like my application to properly support C and Lua. This issue was also present using Python 2.7, although I overlooked this as Python 3 works as expected.
Why is this happening? How can I fix it?
This question already has an answer here:
Reading from a process, StreamReader.Peek() not working as expected
(1 answer)
Closed 6 years ago.
I am having trouble setting up a c# application that creates and interacts with a python process1. A simplistic example is given below.
EDIT: Further research on SO unveiled that my question is a possible duplicate. A potentially related known bug in the .NET Framework is duscussed here and here. It seems that back in 2014 the only easy workaround is indeed to ask the child process to write something in both stdOut and stdErr. But I'd like to know if this assumption is correct and wonder if there hasn't been a fix since 2014?
I have to fulfill the following boundary conditions:
I am not able to close the python process after handing over a script or a command, but I have to keep the process alive. Edit: For that reason I can not make use of the Process.WaitForExit() Method
As the std's remain open all the time, I believe I can't check for EndOfStream, as that would require to read to the end of the stream, which does not exist.
Furthermore, my application has to wait for the response of the python process, therefore the asynchronous option using BeginOutputReadLine() with OnOutputDataReceived seems not appropriate to me.
As the commands that will be sent to python are arbitrary user input, pythons result might be either in stdOut or stdErr ("4+7" results in "11" stored in stdOut; "4+a" results in "name 'a' is not defined" in stdErr)
What I do is to:
set up a python process in interactive mode (Argument "-i")
enable redirect of StdIn, Out, and Err
start the process
get StreamReaders and Writers for the Std's
After that, I want to initially check the StdOut and StdErr. I know that python writes the following piece of information to the StdErr
Python 2.7.11 (v2.7.11:6d1b6a68f775, Dec 5 2015, 20:32:19) [MSC
v.1500 32 bit (Intel)] on win32
and I am able to get this line by using errorReader.Peek() and reading character-based from the errorReader 2.
However, the situation with another process might be totally different. Even with Python, I run into the following problem: when I want to initially read from the outputReader, there is nothing contained in it and outputReader.Peek() seems to run into a deadlock. As mentioned above, the same holds for outputReader.EndOfStream or outputReader.ReadToEnd(). So how do I know if the stdOut can be used at all without causing a deadlock?
Code:
// create the python process StartupInfo object
ProcessStartInfo _tempProcessStartInfo = new ProcessStartInfo(#"C:\TMP\Python27\python.exe");
// ProcessStartInfo _tempProcessStartInfo = new ProcessStartInfo(PathToPython + "python.exe");
// python uses "-i" to run in interactive mode
_tempProcessStartInfo.Arguments = "-i";
// Only start the python process, but don't show a (console) window
_tempProcessStartInfo.WindowStyle = ProcessWindowStyle.Minimized;
_tempProcessStartInfo.CreateNoWindow = true;
// Enable the redirection of python process std's
_tempProcessStartInfo.UseShellExecute = false;
_tempProcessStartInfo.RedirectStandardOutput = true;
_tempProcessStartInfo.RedirectStandardInput = true;
_tempProcessStartInfo.RedirectStandardError = true;
// Create the python process object and apply the startupInfos from above
Process _tempProcess = new Process();
_tempProcess.StartInfo = _tempProcessStartInfo;
// Start the process
bool _hasStarted = _tempProcess.Start();
//// ASynch reading seems not appropriate to me:
// _tempProcess.BeginOutputReadLine();
// _tempProcess.BeginErrorReadLine();
// Create StreamReaders and Writers for the Std's
StreamReader outputReader = _tempProcess.StandardOutput;
StreamReader errorReader = _tempProcess.StandardError;
StreamWriter commandWriter = _tempProcess.StandardInput;
// Create StringBuilder that collects results and ErrorMessages
StringBuilder tmp = new StringBuilder("");
// Create temp variable that is used to peek into streams. C# uses -1 to indicate that there is no more byte to read
int currentPeek = -1;
// Get Initial Error Message. In this specific case, this is the python version
tmp.AppendLine("INITIAL ERROR MESSAGE:");
currentPeek = errorReader.Peek();
while (currentPeek >= 0)
{
char text = (char)errorReader.Read();
tmp.Append(text);
currentPeek = errorReader.Peek();
}
// Get initial output Message. In this specific case, this is EMPTY, which seems to cause this problem, as ...
tmp.AppendLine("INITIAL STDOUT MESSAGE:");
//// ... the following command CREATES a well defined output, and afterwards everything works fine (?) but ...
//commandWriter.WriteLine(#"print 'Hello World'");
//// ... without the the above command, neither
//bool isEndOfStream = outputReader.EndOfStream;
//// ... nor
// currentPeek = outputReader.Peek();
//// ... nor
// tmp.AppendLine(outputReader.ReadLine());
//// ... nor
//tmp.AppendLine(outputReader.ReadToEnd());
//// ... works
// Therefore, the following command creates a deadlock
currentPeek = outputReader.Peek();
while (currentPeek >= 0)
{
char text = (char)outputReader.Read();
tmp.Append(text);
currentPeek = errorReader.Peek();
}
_currentPythonProcess = _tempProcess;
return true;
1 An easy fix to this very specific problem is to send a valid command to the process first, for example simply "4", which returns a "4" as well... However, I want to understand how process streams, pipes and the corresponing readers and writers work and how I can use them in C#. Who knows what future brings, maybe I run into buffer problems when pythons response is 2^n+1 bytes long...
2 I know that I can also read line-based. However, the Peek() prevents me from reporting problems that are related to truncated lines.
If you can wait for the process to end and then read the buffers, you might be able to use Process.WaitForExit. There is also another method you can check, Process.WaitForInputIdle, but it depends on the process having a message loop, which I don't think a Python script gets when executing.
What I'm trying to achieve is a self-compiled c# file without toxic output.I'm trying to achieve this with Console.MoveBufferArea method but looks does not work.
Eg. - save the code below with .bat extension :
// 2>nul||#goto :batch
/*
:batch
#echo off
setlocal
:: find csc.exe
set "frm=%SystemRoot%\Microsoft.NET\Framework\"
for /f "tokens=* delims=" %%v in ('dir /b /a:d /o:-n "%SystemRoot%\Microsoft.NET\Framework\v*"') do (
set netver=%%v
goto :break_loop
)
:break_loop
set csc=%frm%%netver%\csc.exe
:: csc.exe found
%csc% /nologo /out:"%~n0.exe" "%~dpsfnx0"
%~n0.exe
endlocal
exit /b 0
*/
public class Hello
{
public static void Main() {
ClearC();
System.Console.WriteLine("Hello, C# World!");
}
private static void ClearC() {
System.Console.MoveBufferArea(
0,0,
System.Console.BufferWidth,System.Console.BufferHeight-1,
0,0
);
}
}
the output will be:
C:\>// 2>nul ||
Hello, C# World!
What want is to rid of the // 2>nul || .Is it possible? Is there something wrong in my logic (the ClearC method)?Do I need PInvoke?
If you want to do it in the C#, then changing your ClearC function to the following seems to work:
public static void ClearC() {
System.Console.CursorTop = System.Console.CursorTop - 1;
System.Console.Write(new string(' ', System.Console.BufferWidth));
System.Console.CursorTop = System.Console.CursorTop - 1;
}
Essentially, move the Cursor up a line (to the line that should contain your prompt), blank the entire line, then move up another line (which should move you to the blank line between commands). Future output will then take place from here.
The obvious downside to this is that you need to wait for the C# code to be compiled and executed, before the // 2>nul || is removed. If you want it to be faster, you'll need to find a console/batch file based solution. The other thing to keep in mind is that is assumes that the prompt is a single line. If it's a really long prompt that spans two lines, then you'll get a bit of a mess, so it may be better to clear two lines, depending on how you're planning on using this.
If you want to go the whole hog and start reading the console buffer to determine how long the prompt is, then you might want to have a look at this question. Whilst the article link in the answer is broken, the code download still seems to work.
If you want to go down the batchfile based approach, then you might want to have a look at this question.
I have a simple application that opens a TCP connection and communicates via Telnet to another system. The application is to read a file that contains parameters and a simple scripting language to issue commands based on prompts from the connection.
Written in VS2013 using .NET 4
My application works as designed with one little exception.
I am publishing to a location using VS2013 which works well enough but the idea is to read a command line passed to my application that contains the path/file for the script to execute and that doesn't work as expected.
Finding out the hard way, the standard args[] parameters are not passed when it's published this way.
I have searched out multiple solutions that don't work both on here and other sites.
This is the basis (excerpt from page) of my current implementation to read the command line (found here: http://developingfor.net/2010/06/23/processing-command-line-arguments-in-an-offline-clickonce-application/). This seems to be similar to all solutions I've found, each with some variation that doesn't work.
string[] args = null;
if (ApplicationDeployment.IsNetworkDeployed)
{
var inputArgs = AppDomain.CurrentDomain.SetupInformation.ActivationArguments.ActivationData;
if (inputArgs != null && inputArgs.Length > 0)
{
args = inputArgs[0].Split(new char[] { ',' });
}
}
else
{
args = e.Args;
}
This SHOULD return args[] with parameters passed. I believe it would also include the actual command with path to the application. The Split function is because the author wishes to pass arguments separated by commas and not spaces.
My incarnation of this is a bit longer to include some checks to see if we actually get arguments from being compiled as an exe instead. If I compile to EXE and supply a command line, all is fine. Here is my code, not very concise as I've made lots of changes to debug and make this work.
I haven't figured out how to debug in the ide as network deployed with a command line so my debug code is via messagebox.
[STAThread]
static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
if (args.Length == 0) //If we don't have args, assume onclick launch
{
if (ApplicationDeployment.IsNetworkDeployed) //are we oneclick launched?
{
var cmdline = AppDomain.CurrentDomain.SetupInformation.ActivationArguments.ActivationData; //this should hold the command line and arguments????
if (cmdline != null && cmdline.Length > 0) //we have something and it contains at least 1 value
{
//This is all debug code to see what we get since we can't trace in this mode
MessageBox.Show(cmdline.Length.ToString()); //how many objects do we have?
foreach (String s in cmdline)
{
MessageBox.Show(s); //show us the value of each object
}
Application.Run(new frmMain(args)); //launch the form with our arguments
}
else
{
//quit application
MessageBox.Show("No command line.1"); //debug so we know where we failed
Application.Exit();
}
}
else
{
//quit application
MessageBox.Show("No command line.2"); //debug so we know where we failed
Application.Exit();
}
}
else
{
Application.Run(new frmMain(args)); //launch form with args passed with exe command line
}
}
Running the code above like this:
sTelent.application 1234
I have also explored the URL passing method which seems to only apply if launched from a web server, which this application is not.
At first I got NULL for my object:
AppDomain.CurrentDomain.SetupInformation.ActivationArguments.ActivationData
After more research I discovered that in my project properties under the Publish section there is an option button and under Manifests I can choose "Allow URL Parameters to be passed to application"
I checked this box and while I get different behavior, I don't get the desired behavior.
With that option checked I now get 2 messages boxes: The first showing the number of objects in cmdline and that number is 1 and the second showing the value of that one object which contains only the path/command to my application. No other objects and definitely not my arguments.
Am I totally off base? How do I get my command line arguments from an offline clickonce published application?
It seems that you must put the argument on the .appref-ms and not on not the .application or .exe for this to work correctly for clickonce based applications.
I created a short cut on my desktop by copying the installed application link found under All Programs. That should create an icon on your desktop with the same name as your application.
Then, open a command prompt, type in “%userprofile%\Desktop\My App Name.appref-ms” word for word (of course replace "my app name" with your application name). It should then pass the arguments. You can also put the command within a .bat file. I'm sure that you can also reference the link directly, it typically is located under c:\users[user profile]\appdata\roaming\Microsoft\windows\start menu\programs[app name]