Execute a few commands via C# console-app - c#

I'm new to C# (and development in general). I need to write a method in C# that will execute following via console:
- install Newman;
- execute postman Run.
I've created a method as below (I tried 2 options: ReadToEnd and WaitForExit), but it seems to stuck at each of these steps.
Any help how to make this to execute both commands in sequence (1st needs to finish before 2nd start) and exit after 2nd command executed fully?
Thanks in advance.
public string Runner ()
{
string readOutput = null;
var psiNpm = new ProcessStartInfo
{
FileName = "cmd",
RedirectStandardOutput = true,
RedirectStandardInput = true,
UseShellExecute = false
};
var pNpmRun = Process.Start(psiNpm);
pNpmRun.StandardInput.WriteLine($"npm install -g newman");
pNpmRun.WaitForExit();
//pNpmRun.StandardOutput.ReadToEnd();
pNpmRun.StandardInput.WriteLine($"newman run " +
$"\"C:\\Postman\\Test.postman.json\" " +
$"--folder \"TestSearch\" " +
$"--environment \"C:\\Postman\\postman_environment.json\" " +
$"--disable-unicode");
pNpmRun.StandardOutput.ReadToEnd();
pNpmRun.WaitForExit();
return readOutput = pNpmRun.StandardOutput.ReadLine();
}

Try writing an exit command to your cmd input stream.
The issue, is the commands you sent (npm, newman) are executing, then control is returning to the shell (cmd), which is then waiting for more user input to be sent. You need to tell it to quit by sending "exit". You should only have 1 WaitForExit call. The WaitForExit is waiting for the cmd process to exit, not the individual commands you send to it.
Next, you should move ReadToEnd after the process has exited (after WaitForExit). Here is a question concering why: ReadToEnd from std output of process and waitforexit
So, something like this:
public string Runner ()
{
var psiNpm = new ProcessStartInfo
{
FileName = "cmd",
RedirectStandardOutput = true,
RedirectStandardInput = true,
UseShellExecute = false
};
var pNpmRun = Process.Start(psiNpm);
pNpmRun.StandardInput.WriteLine("npm install -g newman");
pNpmRun.StandardInput.WriteLine("newman run " +
"\"C:\\Postman\\Test.postman.json\" " +
"--folder \"TestSearch\" " +
"--environment \"C:\\Postman\\postman_environment.json\" " +
"--disable-unicode");
pNpmRun.StandardInput.WriteLine("exit");
pNpmRun.WaitForExit();
return pNpmRun.StandardOutput.ReadToEnd();
You might also consider adding a timeout to the WaitForExit. If these cmds get stuck for some reason your calling process is also going to get stuck waiting for them to complete. Usually best to eventually fail than have a bunch of hung processes. You could then kill the process if the timeout expires.
var tenMin = 10 * 60 * 1000;
if(pNpmRun.WaitForExit(tenMin)) {
return pNpmRun.StandardOutput.ReadToEnd();
} else {
pNpmRun.Kill();
throw new TimeoutException("Command didn't complete in 10 minute timeout");
}

Related

How to make a process crash if it doesn't log anything for 5 minutes

I work at Ubisoft and we use a very old program to manipulate some files. Since it's legacy software, it's really bad and it may happen that the software has crashed and keeps on running. We sadly don't have access to the code, so we're unable to fix that. I was wondering, is it possible to use System.Diagnostics.Process with a "no log timeout"? Here's what I'm trying to achieve
var legacySoftwareProcess = new Process
{
StartInfo =
{
UseShellExecute = false,
RedirectStandardOutput = true,
WorkingDirectory = localPackageFolder,
FileName = CiConfig.DataGeneration.RebuildUnstrippedBatName
}
};
legacySoftwareProcess.IdleLogTimeout = 5 * 60; // 5 minutes
legacySoftwareProcess.Start();
var output = proc.StandardOutput.ReadToEnd();
legacySoftwareProcess.WaitForExit();
if (legacySoftwareProcess.ExitCode != 0)
{
Context.LogMessage(output);
Context.LogError("The process exited with non 0 code");
}
Rather than using:
var output = proc.StandardOutput.ReadToEnd();
You can listen for the event when output data is received from the process:
proc.OutputDataReceived += ResetTimer;
proc.Start();
proc.BeginOutputReadLine(); // not sure that you should use that as it may read output synchronously (I will check that soon)
And in the handler method ResetTimer, as the method name implies, reset a 5-minute timer:
static void ResetTimer(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
// reset the timer
}
}
If timer has elapsed, it means nothing has been outputed for 5 minutes, and you can take action accordingly, ie kill the process.

What is the "MOST" efficient way of executing a command line tool too many times?

When you search for a way of executing a command-line-tool or the command-prompt, you find too many results like this question, this question or this article. They all contain solutions to the exact same issue. However, the program I'm currently working on has to execute a command-line-tool almost 500 times. When I do the following:
for (int i = 0; i < 500; i++)
{
Process.Start("cmd","My Input: " + i);
}
The computer stops responding for a while because too many programs got opened at the same time.
When I do the following:
for (int i = 0; i < 500; i++)
{
Process.Start("cmd","My Input: " + i);
System.Threading.Thread.Sleep(myInterval);
}
The program takes too much time to get the job done(I got no problem if my program hangs while executing those commands).
From my point of view, the main reason all this happens is that I keep opening another cmd window each time I run a tool. Could I keep a single cmd window open and keep feeding it commands till I am done? Is there a more efficient way of doing that?
If you can run your commands in parallel, I'd do it as least like this:
Parallel.For(0, 500, new ParallelOptions() {MaxDegreeOfParallelism = 8}, i => {
var process = Process.Start("cmd", "My Input: " + i);
process.WaitForExit();
});
So you run commands in parallel, but no more than 8 at the same time, and you wait for command process to exit until starting new one.
What also might help with perfomance is using UseShellExecute and CreateNoWindow flags, like this:
Parallel.For(0, 500, new ParallelOptions() {MaxDegreeOfParallelism = 8}, i => {
var process = Process.Start(new ProcessStartInfo("cmd", "My Input: " + i) {
CreateNoWindow = true,
UseShellExecute = false
});
process.WaitForExit();
});
This might or might not work as expected depending on what kind of process you are execuing. If you just execute some .exe application, this should work just fine and create no windows.
I supposed your "cmd" is just the name of application you want to execute. However, as stated in comments, if you mean cmd.exe application by that - most likely you won't need to execute it. Instead, just execute your application itself, without cmd.exe.
You don’t need cmd.exe to execute a command-line program. This startup procedure might be faster:
var process = Process.Start(
new ProcessStartInfo(#"path\to\your\command.exe", "My Input: " + i) {
/* more options if you like */
RedirectStandardOutput = true,
WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden,
CreateNoWindow = true
}
);
process.WaitForExit();
string output = process.StandardOutput.ReadToEnd();
See the documentation for ProcessStartInfo for more available options.
Have you tried multithreading the whole thing?
for (int i = 0; i < 500; i++)
{
new Thread(() =>
{
Process.Start("cmd","My Input: " + i);
}).Start();
}
Does it give you better performance?
Edit: Wait.. Do you need the commands to run one after another?

Program runs in background through cmd even after Process is explicitly closed

I have a Process l_batchProcess and internally it executes cmd.exe which has certain commands related to CAT.NET cmd, But when I explicitly close the Process instance , I see that it still executes the CAT.NET cmd.exe in the background .
There is another issue; I'm not sure if l_batchProcess.WaitForExit() has anything to do keep the process to get back once CAT.NET is completed in background. When I check for l_batchProcess it seems the process has exited and tells no process is associated.
Could someone guide me and let me know if more details are required.
ExecuteBatchFile is run asynchronously.
public string ExecuteBatchFile(BatchFile batchFile)
{
//var lockObject = new object();
string returnMessage = string.Empty;
l_batchProcess.StartInfo = new ProcessStartInfo()
{
FileName = batchFile.InputFilePath,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardError = true,
RedirectStandardOutput = true,
};
if (File.Exists(batchFile.InputFilePath))
{
try
{
l_batchProcess.Refresh();
l_batchProcess.Start();
processStartedFlag = true;
l_batchProcess.BeginOutputReadLine();
l_batchProcess.BeginErrorReadLine();
l_batchProcess.WaitForExit();
if (l_batchProcess.ExitCode != 0)
returnMessage = "Errors while executing";
}
finally
{
CloseProcess();
}
}
else
{
returnMessage = "Batch File path incorrect";
}
File.Delete(batchFile.InputFilePath);
return returnMessage;
}
public bool CloseProcess()
{
if (!processStartedFlag) return false;
l_batchProcess.CancelOutputRead();
l_batchProcess.CancelErrorRead();
l_batchProcess.Close();
processStartedFlag = false;
return true;
}
EDIT : Sorry I think from my question it appears that the process is still running in the normal course , I meant when I explicitly try to close the process.
One workaround that seems to be working is directly calling the executables for CAT.NET and passing the arguments . then I Kill the process instead of Close . This way I have the control of killing the executable.
But doesn't seem elegant .
PowerShell does seem an interesting solution.

Sending message from one C# console application to another

First of all, I've read all related topics and they gave general idea but implementation doesn't work for me:
Send strings from one console application to another
How to send input to the console as if the user is typing?
Sending input/getting output from a console application (C#/WinForms)
I have a console application that is doing some actions in background until cancellation is requested. Typical usage scenario is :
1) Execute application
2) Enter input data
3) Issue start command
4) After some time passes, enter stop command
5) Exit application
Child application Program.cs :
static void Main()
{
Console.WriteLine("Enter input parameter : ");
var inputParameter = Console.ReadLine();
Console.WriteLine("Entered : " + inputParameter);
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
Task.Factory.StartNew(() =>
{
while (true)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("Stopping actions");
return;
}
// Simulating some actions
Console.Write("*");
}
}, token);
if (Console.ReadKey().KeyChar == 'c')
{
tokenSource.Cancel();
Console.WriteLine("Stop command");
}
Console.WriteLine("Finished");
Console.ReadLine();
}
What I'm looking for is some sort of host utility to control this application - spawn multiple instances and perform required user actions on each instance.
Host application Program.cs :
static void Main()
{
const string exe = "Child.exe";
var exePath = System.IO.Path.GetFullPath(exe);
var startInfo = new ProcessStartInfo(exePath)
{
RedirectStandardOutput = true,
RedirectStandardInput = true,
WindowStyle = ProcessWindowStyle.Hidden,
WindowStyle = ProcessWindowStyle.Maximized,
CreateNoWindow = true,
UseShellExecute = false
};
var childProcess = new Process { StartInfo = startInfo };
childProcess.OutputDataReceived += readProcess_OutputDataReceived;
childProcess.Start();
childProcess.BeginOutputReadLine();
Console.WriteLine("Waiting 5s for child process to start...");
Thread.Sleep(5000);
Console.WriteLine("Enter input");
var msg = Console.ReadLine();
// Sending input parameter
childProcess.StandardInput.WriteLine(msg);
// Sending start command aka any key
childProcess.StandardInput.Write("s");
// Wait 5s while child application is working
Thread.Sleep(5000);
// Issue stop command
childProcess.StandardInput.Write("c");
// Wait for child application to stop
Thread.Sleep(20000);
childProcess.WaitForExit();
Console.WriteLine("Batch finished");
Console.ReadLine();
}
When I run this tool, after first input it crashes with "has stopped working" error and prompt to send memory dump to Microsoft. Output window in VS shows no exceptions.
Guess this problem occurs somewhere between applications and may be because of output stream buffer overflow (child app is writing a lot of stars each second which mimics real output which may be huge) and I yet have no idea how to fix it. I don't really need to pass child's output to host (only send start-stop commands to child), but commenting RedirectStandardOutput and OutputDataReceived doesn't fix this problem. Any ideas how to make this work?
I would recommend using NamedPipeServerStream and NamedPipeClientStream, which allows you to open a stream which will communicate between processes on a given machine.
First, this will create a pipe server stream and wait for someone to connect to it:
var stream = new NamedPipeServerStream(this.PipeName, PipeDirection.InOut);
stream.WaitForConnection();
return stream;
Then, this will connect to that stream (from your other process), allowing you to read / write in either direction:
var stream = new NamedPipeClientStream(".", this.PipeName, PipeDirection.InOut);
stream.Connect(100);
return stream;
Another alternative is to use MSMQ, you can find a good tutorial here
I would advise to look to the Working with memory mapped files in .NET 4
http://blogs.msdn.com/b/salvapatuel/archive/2009/06/08/working-with-memory-mapped-files-in-net-4.aspx
It's fast and efficient.

Hanging process when run with .NET Process.Start -- what's wrong?

I wrote a quick and dirty wrapper around svn.exe to retrieve some content and do something with it, but for certain inputs it occasionally and reproducibly hangs and won't finish. For example, one call is to svn list:
svn list "http://myserver:84/svn/Documents/Instruments/" --xml --no-auth-cache --username myuser --password mypassword
This command line runs fine when I just do it from a command shell, but it hangs in my app. My c# code to run this is:
string cmd = "svn.exe";
string arguments = "list \"http://myserver:84/svn/Documents/Instruments/\" --xml --no-auth-cache --username myuser --password mypassword";
int ms = 5000;
ProcessStartInfo psi = new ProcessStartInfo(cmd);
psi.Arguments = arguments;
psi.RedirectStandardOutput = true;
psi.WindowStyle = ProcessWindowStyle.Normal;
psi.UseShellExecute = false;
Process proc = Process.Start(psi);
StreamReader output = new StreamReader(proc.StandardOutput.BaseStream, Encoding.UTF8);
proc.WaitForExit(ms);
if (proc.HasExited)
{
return output.ReadToEnd();
}
This takes the full 5000 ms and never finishes. Extending the time doesn't help. In a separate command prompt, it runs instantly, so I'm pretty sure it's unrelated to an insufficient waiting time. For other inputs, however, this seems to work fine.
I also tried running a separate cmd.exe here (where exe is svn.exe and args is the original arg string), but the hang still occurred:
string cmd = "cmd";
string arguments = "/S /C \"" + exe + " " + args + "\"";
What could I be screwing up here, and how can I debug this external process stuff?
EDIT:
I'm just now getting around to addressing this. Mucho thanks to Jon Skeet for his suggestion, which indeed works great. I have another question about my method of handling this, though, since I'm a multi-threaded novice. I'd like suggestions on improving any glaring deficiencies or anything otherwise dumb. I ended up creating a small class that contains the stdout stream, a StringBuilder to hold the output, and a flag to tell when it's finished. Then I used ThreadPool.QueueUserWorkItem and passed in an instance of my class:
ProcessBufferHandler bufferHandler = new ProcessBufferHandler(proc.StandardOutput.BaseStream,
Encoding.UTF8);
ThreadPool.QueueUserWorkItem(ProcessStream, bufferHandler);
proc.WaitForExit(ms);
if (proc.HasExited)
{
bufferHandler.Stop();
return bufferHandler.ReadToEnd();
}
... and ...
private class ProcessBufferHandler
{
public Stream stream;
public StringBuilder sb;
public Encoding encoding;
public State state;
public enum State
{
Running,
Stopped
}
public ProcessBufferHandler(Stream stream, Encoding encoding)
{
this.stream = stream;
this.sb = new StringBuilder();
this.encoding = encoding;
state = State.Running;
}
public void ProcessBuffer()
{
sb.Append(new StreamReader(stream, encoding).ReadToEnd());
}
public string ReadToEnd()
{
return sb.ToString();
}
public void Stop()
{
state = State.Stopped;
}
}
This seems to work, but I'm doubtful that this is the best way. Is this reasonable? And what can I do to improve it?
One standard issue: the process could be waiting for you to read its output. Create a separate thread to read from its standard output while you're waiting for it to exit. It's a bit of a pain, but that may well be the problem.
Jon Skeet is right on the money!
If you don't mind polling after you launch your svn command try this:
Process command = new Process();
command.EnableRaisingEvents = false;
command.StartInfo.FileName = "svn.exe";
command.StartInfo.Arguments = "your svn arguments here";
command.StartInfo.UseShellExecute = false;
command.StartInfo.RedirectStandardOutput = true;
command.Start();
while (!command.StandardOutput.EndOfStream)
{
Console.WriteLine(command.StandardOutput.ReadLine());
}
I had to drop an exe on a client's machine and use Process.Start to launch it.
The calling application would hang - the issue ended up being their machine assuming the exe was dangerous and preventing other applications from starting it.
Right click the exe and go to properties. Hit "Unblock" toward the bottom next to the security warning.
Based on Jon Skeet's answer this is how I do it in modern day (2021) .NET 5
var process = Process.Start(processStartInfo);
var stdErr = process.StandardError;
var stdOut = process.StandardOutput;
var resultAwaiter = stdOut.ReadToEndAsync();
var errResultAwaiter = stdErr.ReadToEndAsync();
await process.WaitForExitAsync();
await Task.WhenAll(resultAwaiter, errResultAwaiter);
var result = resultAwaiter.Result;
var errResult = errResultAwaiter.Result;
Note that you can't await the standard output before the error, because the wait will hang in case the standard error buffer gets full first (same for trying it the other way around).
The only way is to start reading them asynchronously, wait for the process to exit, and then complete the await by using Task.WaitAll
I know this is an old post but maybe this will assist someone. I used this to execute some AWS (Amazon Web Services) CLI commands using .Net TPL tasks.
I did something like this in my command execution which is executed within a .Net TPL Task which is created within my WinForm background worker bgwRun_DoWork method which holding a loop with while(!bgwRun.CancellationPending). This contains the reading of the Standard Output from the Process via a new Thread using the .Net ThreadPool class.
private void bgwRun_DoWork(object sender, DoWorkEventArgs e)
{
while (!bgwRun.CancellationPending)
{
//build TPL Tasks
var tasks = new List<Task>();
//work to add tasks here
tasks.Add(new Task(()=>{
//build .Net ProcessInfo, Process and start Process here
ThreadPool.QueueUserWorkItem(state =>
{
while (!process.StandardOutput.EndOfStream)
{
var output = process.StandardOutput.ReadLine();
if (!string.IsNullOrEmpty(output))
{
bgwRun_ProgressChanged(this, new ProgressChangedEventArgs(0, new ExecutionInfo
{
Type = "ExecutionInfo",
Text = output,
Configuration = s3SyncConfiguration
}));
}
if (cancellationToken.GetValueOrDefault().IsCancellationRequested)
{
break;
}
}
});
});//work Task
//loop through and start tasks here and handle completed tasks
} //end while
}
I know my SVN repos can run slow sometimes, so maybe 5 seconds isn't long enough? Have you copied the string you are passing to the process from a break point so you are positive it's not prompting you for anything?

Categories