StreamWrite (Interact) to another Application running within a C# Console App - c#

I am writing a shell in C# using a Console App. Using my Shell I would execute other applications such as ping.exe, takeown.exe etc just like you would with cmd or bash.
I have no problem in redirecting Standard Out from the other process to my Console App. The issue is that I don't know how to redirect StardardIn properly so that I can interact with the running application. I.e say I need to enter "y" to confirm an action using cacls.exe.
Here is my code for StandardOut:
How can I write back to the App?
Process process = new Process();
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardInput = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.FileName = cmd;
process.StartInfo.Arguments = Arguments;
process.OutputDataReceived += new DataReceivedEventHandler(
(s, e) =>
{
Console.WriteLine(e.Data);
}
);
process.ErrorDataReceived += new DataReceivedEventHandler((s, e) => { Console.WriteLine(e.Data); });
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();

You should write back the data to the app by using process.StandardInput.WriteLine(message)
and message should be a string.
You can use Write() or WriteLine()

Related

Run process in SQL Server CLR stored procedure but Process.OutputDataReceived not firing

I want to run process in SQL Server CLR this my code:
[SqlProcedure]
private static int RunExecutable()
{
SqlDataRecord sqlDataRecord = new SqlDataRecord(new SqlMetaData("message", SqlDbType.NVarChar, 1L));
SqlContext.Pipe.SendResultsStart(sqlDataRecord);
int lineCount = 0;
Process process = new Process();
process.StartInfo.FileName = "ipconfig.exe";
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardInput = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.CreateNoWindow = true;
process.EnableRaisingEvents = true;
process.OutputDataReceived += new DataReceivedEventHandler((sender, e) =>
{
sqlDataRecord.SetString(0, "OnDataReceived");
SqlContext.Pipe.SendResultsRow(sqlDataRecord);
if (!String.IsNullOrEmpty(e.Data))
{
lineCount++;
sqlDataRecord.SetString(0, "[" + lineCount + "]: " + e.Data);
SqlContext.Pipe.SendResultsRow(sqlDataRecord);
}
});
process.Start();
process.BeginOutputReadLine();
while (!process.HasExited)
{
sqlDataRecord.SetString(0, "process WaitForExit ");
SqlContext.Pipe.SendResultsRow(sqlDataRecord);
process.WaitForExit(300);
}
}
and ipconfig.exe runs (I see in results "process WaitForExit"), but the OutputDataReceived event is not triggered.
The assembly was created in SQL Server 2019 Enterprise with PERMISSION_SET = UNSAFE;. If I run the same code as the standard console application everything works fine
That's going to require a background thread to run the event while you block the session's thread on WaitForExit. I'm not surprised it doesn't work in SQLCLR, which is a very different .NET Framework host than a console application.
And even if the event fires, you could not access SqlContext.Pipe from a thread other than the thread that called into the method.
Instead perform blocking reads of StandardOutput using the thread that called into your method, like this:
static IEnumerable<string> GetOutputLines(string exeName, string args = null)
{
Process process = new Process();
process.StartInfo.FileName = exeName;
process.StartInfo.Arguments = args;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardInput = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.CreateNoWindow = true;
process.EnableRaisingEvents = true;
process.Start();
while ( true )
{
var line = process.StandardOutput.ReadLine();
if (line == null)
break;
yield return line;
}
process.WaitForExit();
}
+1 to David for answering the general question of how to best capture and return command-line output.
However, for your specific scenario of running ipconfig.exe and returning all of the output, I think you would be far better served by simply using:
NetworkInterface.GetAllNetworkInterfaces()
I would try that first. This might require you to load the System.Net.NetworkInformation.dll Framework library as UNSAFE in the same database, but you are already doing "unsafe" operations by shelling out to the OS, so at least this is handled in managed code.

Calling git clone from C# app doesn't return output data

I am calling git clone from a C# application by creating a new process and it calls git successfully but no ouput is returned to my delegate. This means I can't provide any updates to the user as the process progresses. The Clone_DataOutputReceived method is not being called at all.
public void CloneRepo(string repoUrl, string dataPath)
{
var process = new Process();
process.StartInfo.FileName = #"git.exe";
process.StartInfo.Arguments = $"clone {repoUrl}";
process.StartInfo.WorkingDirectory = dataPath;
process.OutputDataReceived += Clone_DataOutputReceived;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = true;
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
process.CancelOutputRead();
}
private void Clone_DataOutputReceived(object sender, DataReceivedEventArgs e)
{
// This method is not being called
// Expecting d.Data to be populated with the line from the output
}
I think you want to set FileName to "cmd.exe" and then issue process.SendCommand("git.exe") to the cmd instance.

How to call docker run from c# application

I've got a WPF application that whilst processing a file needs to use a docker process. The docker container is built on the box, currently after processing a file with the WPF application the user has to start a command prompt and type in
docker run --it --rm -v folderdedirect process parameters_including_filePath
to do further processing.
I want to include that in the WPF application. I could presumably use system.diagnostics.process with cmd.exe? I looked at the Docker.dotnet but couldn't for the life of me work out how it's supposed to just run a local container.
Here's how I did it in the end but there may be a better way.
var processInfo = new ProcessStartInfo("docker", $"run -it --rm blahblahblah");
processInfo.CreateNoWindow = true;
processInfo.UseShellExecute = false;
processInfo.RedirectStandardOutput = true;
processInfo.RedirectStandardError = true;
int exitCode;
using (var process = new Process())
{
process.StartInfo = processInfo;
process.OutputDataReceived += new DataReceivedEventHandler(logOrWhatever());
process.ErrorDataReceived += new DataReceivedEventHandler(logOrWhatever());
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit(1200000);
if (!process.HasExited)
{
process.Kill();
}
exitCode = process.ExitCode;
process.Close();
}
Adapted the code above for my context. The hardest part is determining how you want to monitor if your process finished. You can try setting up event listeners as shown here. Having other conditions to verify (if client sends a kill signal), I decided to just continually monitor process.HasExited value.
private static void RunDockerImage(ContainerData containerData)
{
var processInfo = new ProcessStartInfo("docker", $"run "+containerData.ImageName);
processInfo.CreateNoWindow = true;
processInfo.UseShellExecute = false;
processInfo.RedirectStandardOutput = true;
processInfo.RedirectStandardError = true;
int exitCode;
using (var process = new Process())
{
process.StartInfo = processInfo;
// indicate process is started
StartUpdateOrch(containerData);
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
while(!process.HasExited)
{
// continually check if kill signal is set
if (getKillSignal())
{
process.Kill();
}
Thread.Sleep(2000);
}
exitCode = process.ExitCode;
containerData.exitCode = exitCode;
// indicate process is done
FinishUpdateOrch(containerData);
process.Close();
}
}

Run command line command passing STDIN [duplicate]

This question already has answers here:
Capturing console output from a .NET application (C#)
(8 answers)
Closed 6 years ago.
I need to spawn a child process that is a console application, and capture its output.
I wrote up the following code for a method:
string retMessage = String.Empty;
ProcessStartInfo startInfo = new ProcessStartInfo();
Process p = new Process();
startInfo.CreateNoWindow = true;
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardInput = true;
startInfo.UseShellExecute = false;
startInfo.Arguments = command;
startInfo.FileName = exec;
p.StartInfo = startInfo;
p.Start();
p.OutputDataReceived += new DataReceivedEventHandler
(
delegate(object sender, DataReceivedEventArgs e)
{
using (StreamReader output = p.StandardOutput)
{
retMessage = output.ReadToEnd();
}
}
);
p.WaitForExit();
return retMessage;
However, this does not return anything. I don't believe the OutputDataReceived event is being called back, or the WaitForExit() command may be blocking the thread so it will never callback.
Any advice?
EDIT: Looks like I was trying too hard with the callback. Doing:
return p.StandardOutput.ReadToEnd();
Appears to work fine.
Here's code that I've verified to work. I use it for spawning MSBuild and listening to its output:
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.OutputDataReceived += (sender, args) => Console.WriteLine("received output: {0}", args.Data);
process.Start();
process.BeginOutputReadLine();
I just tried this very thing and the following worked for me:
StringBuilder outputBuilder;
ProcessStartInfo processStartInfo;
Process process;
outputBuilder = new StringBuilder();
processStartInfo = new ProcessStartInfo();
processStartInfo.CreateNoWindow = true;
processStartInfo.RedirectStandardOutput = true;
processStartInfo.RedirectStandardInput = true;
processStartInfo.UseShellExecute = false;
processStartInfo.Arguments = "<insert command line arguments here>";
processStartInfo.FileName = "<insert tool path here>";
process = new Process();
process.StartInfo = processStartInfo;
// enable raising events because Process does not raise events by default
process.EnableRaisingEvents = true;
// attach the event handler for OutputDataReceived before starting the process
process.OutputDataReceived += new DataReceivedEventHandler
(
delegate(object sender, DataReceivedEventArgs e)
{
// append the new data to the data already read-in
outputBuilder.Append(e.Data);
}
);
// start the process
// then begin asynchronously reading the output
// then wait for the process to exit
// then cancel asynchronously reading the output
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
process.CancelOutputRead();
// use the output
string output = outputBuilder.ToString();
Here's some full and simple code to do this. This worked fine when I used it.
var processStartInfo = new ProcessStartInfo
{
FileName = #"C:\SomeProgram",
Arguments = "Arguments",
RedirectStandardOutput = true,
UseShellExecute = false
};
var process = Process.Start(processStartInfo);
var output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
Note that this only captures standard output; it doesn't capture standard error. If you want both, use this technique for each stream.
I needed to capture both stdout and stderr and have it timeout if the process didn't exit when expected. I came up with this:
Process process = new Process();
StringBuilder outputStringBuilder = new StringBuilder();
try
{
process.StartInfo.FileName = exeFileName;
process.StartInfo.WorkingDirectory = args.ExeDirectory;
process.StartInfo.Arguments = args;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.EnableRaisingEvents = false;
process.OutputDataReceived += (sender, eventArgs) => outputStringBuilder.AppendLine(eventArgs.Data);
process.ErrorDataReceived += (sender, eventArgs) => outputStringBuilder.AppendLine(eventArgs.Data);
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
var processExited = process.WaitForExit(PROCESS_TIMEOUT);
if (processExited == false) // we timed out...
{
process.Kill();
throw new Exception("ERROR: Process took too long to finish");
}
else if (process.ExitCode != 0)
{
var output = outputStringBuilder.ToString();
var prefixMessage = "";
throw new Exception("Process exited with non-zero exit code of: " + process.ExitCode + Environment.NewLine +
"Output from process: " + outputStringBuilder.ToString());
}
}
finally
{
process.Close();
}
I am piping the stdout and stderr into the same string, but you could keep it separate if needed. It uses events, so it should handle them as they come (I believe). I have run this successfully, and will be volume testing it soon.
It looks like two of your lines are out of order. You start the process before setting up an event handler to capture the output. It's possible the process is just finishing before the event handler is added.
Switch the lines like so.
p.OutputDataReceived += ...
p.Start();
Redirecting the stream is asynchronous and will potentially continue after the process has terminated. It is mentioned by Umar to cancel after process termination process.CancelOutputRead(). However that has data loss potential.
This is working reliably for me:
process.WaitForExit(...);
...
while (process.StandardOutput.EndOfStream == false)
{
Thread.Sleep(100);
}
I didn't try this approach but I like the suggestion from Sly:
if (process.WaitForExit(timeout))
{
process.WaitForExit();
}
You need to call p.Start() to actually run the process after you set the StartInfo. As it is, your function is probably hanging on the WaitForExit() call because the process was never actually started.
The answer from Judah did not work for me (or is not complete) as the application was exiting after the first BeginOutputReadLine();
This works for me as a complete snippet, reading the constant output of a ping:
var process = new Process();
process.StartInfo.FileName = "ping";
process.StartInfo.Arguments = "google.com -t";
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.UseShellExecute = false;
process.OutputDataReceived += (sender, a) => Console.WriteLine(a.Data);
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
Here's a method that I use to run a process and gets its output and errors :
public static string ShellExecute(this string path, string command, TextWriter writer, params string[] arguments)
{
using (var process = Process.Start(new ProcessStartInfo { WorkingDirectory = path, FileName = command, Arguments = string.Join(" ", arguments), UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true }))
{
using (process.StandardOutput)
{
writer.WriteLine(process.StandardOutput.ReadToEnd());
}
using (process.StandardError)
{
writer.WriteLine(process.StandardError.ReadToEnd());
}
}
return path;
}
For example :
#"E:\Temp\MyWorkingDirectory".ShellExecute(#"C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\svcutil.exe", Console.Out);

Why does Shellexecute=false break this?

I'm learning C# at the moment for a bit of fun and am trying to make a windows application that has a bit of a gui for running some python commands. Basically, I'm trying to teach myself the guts of running a process and sending commands to it, as well as receiving commands from it.
I have the following code at the moment:
Process p = new Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = "C:/Python31/python.exe";
p.Start();
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
textBox1.Text = output;
Running python.exe from a command prompt gives some introductory text that I'd like to capture and send to a textbox in the windows form (textBox1). Basically, the goal is to have something that looks like the python console running from the windows app. When I don't set UseShellExecute to false, a console pops up and everything runs fine; however, when I set UseShellExecute to false in order to re-direct the input, all I get is that a console pops up very quickly and closes again.
What am I doing wrong here?
For some reason, you shouldn't use forward slashes when you start the process.
Compare (does not work):
Process p = new Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.FileName = "C:/windows/system32/cmd.exe";
p.StartInfo.Arguments = "/c dir" ;
p.OutputDataReceived += new DataReceivedEventHandler(p_OutputDataReceived);
bool f = p.Start();
p.BeginOutputReadLine();
p.WaitForExit();
[...]
static void p_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
Console.WriteLine(e.Data);
}
to (works as expected):
Process p = new Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.FileName = #"C:\windows\system32\cmd.exe";
p.StartInfo.Arguments = "/c dir" ;
p.OutputDataReceived += new DataReceivedEventHandler(p_OutputDataReceived);
bool f = p.Start();
p.BeginOutputReadLine();
p.WaitForExit();
[...]
static void p_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
Console.WriteLine(e.Data);
}
Python seems to be doing something weird. I wouldn't believe it until I tested it and then did some research. But all of these posts basically seem to have the same exact problem:
https://stackoverflow.com/questions/4106095/capturing-standard-output-from-django-using-c
Python & C#: Is IronPython absolutely necessary?
C# capturing python.exe output and displaying it in textbox
Certain Python commands aren't caught in Stdout

Categories

Resources