I'm creating a MVC4 web application that will have the ability to create a console that logs the output from a Powershell script to a log window using SignalR. Currently the best I have gotten to work is Capturing Powershell output in C# after Pipeline.Invoke throws. Problem is that I am not able to pass a live stream of output to the client using this method. I am only able to feed the output once the script is finished being processed. Currently i am trying this but am not receiving any output.
var loggingHub = new LoggingHub();
string powerShellExeLocation = null;
var localKey = Registry.LocalMachine;
var subKey = localKey.OpenSubKey(#"SOFTWARE\Microsoft\PowerShell\1\PowerShellEngine");
powerShellExeLocation = subKey.GetValue("ApplicationBase").ToString();
if (!Directory.Exists(powerShellExeLocation))
throw new Exception("Cannot locate the PowerShell dir.");
powerShellExeLocation = Path.Combine(powerShellExeLocation, "powershell.exe");
if (!File.Exists(powerShellExeLocation))
throw new Exception("Cannot locate the PowerShell executable.");
if (!File.Exists(scriptFile))
throw new Exception("Cannot locate the PowerShell script.");
var processInfo = new ProcessStartInfo
{
Verb = "runas",
UseShellExecute = false,
RedirectStandardOutput = true
};
processInfo.RedirectStandardOutput = true;
processInfo.WorkingDirectory = Environment.CurrentDirectory;
processInfo.FileName = powerShellExeLocation;
//processInfo.Arguments = "-NoLogo -OutputFormat Text -NonInteractive -WindowStyle Hidden -ExecutionPolicy Unrestricted -File \"" + scriptFile + "\" ";
processInfo.Arguments = build;
var powerShellProcess = new Process {StartInfo = processInfo};
powerShellProcess.Start();
while (!powerShellProcess.HasExited)
{
loggingHub.Send(DateTime.Now.ToString("h:mm:ss tt"), "info", powerShellProcess.StandardOutput.ReadLine());
}
One way to log the output as it is happening is to not only host the PowerShell engine but to also implement the host interfaces. You will then get called back for every "write" to the "host" as the PowerShell engine processes the script. Implement the host interfaces isn't trivial but it's not too hard either. Take a look at this MSDN link http://msdn.microsoft.com/en-us/windows/desktop/ee706584(v=vs.85)
From:
http://msdn.microsoft.com/en-us/library/system.diagnostics.processstartinfo.redirectstandardoutput(v=vs.110).aspx
**When a Process writes text to its standard stream, that text is typically displayed on the console. By redirecting the StandardOutput stream, you can manipulate or suppress the output of a process. For example, you can filter the text, format it differently, or write the output to both the console and a designated log file.
Note
You must set UseShellExecute to false if you want to set RedirectStandardOutput to true. Otherwise, reading from the StandardOutput stream throws an exception.
The redirected StandardOutput stream can be read synchronously or asynchronously. Methods such as Read, ReadLine, and ReadToEnd perform synchronous read operations on the output stream of the process. These synchronous read operations do not complete until the associated Process writes to its StandardOutput stream, or closes the stream.
In contrast, BeginOutputReadLine starts asynchronous read operations on the StandardOutput stream. This method enables a designated event handler for the stream output and immediately returns to the caller, which can perform other work while the stream output is directed to the event handler.
Note
The application that is processing the asynchronous output should call the WaitForExit method to ensure that the output buffer has been flushed.
Synchronous read operations introduce a dependency between the caller reading from the StandardOutput stream and the child process writing to that stream. These dependencies can cause deadlock conditions. When the caller reads from the redirected stream of a child process, it is dependent on the child. The caller waits for the read operation until the child writes to the stream or closes the stream. When the child process writes enough data to fill its redirected stream, it is dependent on the parent. The child process waits for the next write operation until the parent reads from the full stream or closes the stream. The deadlock condition results when the caller and child process wait for each other to complete an operation, and neither can continue. You can avoid deadlocks by evaluating dependencies between the caller and child process.**
That seems to explain what's happening. Have you tried using the BeginOutputReadLine method instead of ReadLine?
Related
I have a C# Console application which I am trying to execute from another WinForm application just like batch runner by giving the console application's .exe file like below.
Process.Start("Path of Console application exe to execute")
However I need to wait and handle the output and display the output in WinForm's richtextbox from console application once it has completed the execution. How can I achieve this?
Update
I have changed the code to Start a Process and Read using StandardOutput and BeginOutputReadLine() to Read the output asynchronously, but not able to see output in console window, instead console window is getting closed. Not sure how to solve this.
p.StartInfo.UseShellExecute = false;
// p.StartInfo.CreateNoWindow = True
p.StartInfo.RedirectStandardOutput = true;
string #out = null;
p.StartInfo.RedirectStandardError = true;
p.OutputDataReceived += new DataReceivedEventHandler((sender, e) =>
{
#out += e.Data;
});
p.StartInfo.FileName = currentTest;
p.Start();
p.BeginOutputReadLine();
// string output = p.StandardError.ReadToEnd();
// p.WaitForExit()
while (!p.HasExited)
Application.DoEvents();
//Console.WriteLine($#"Output \n'{output.Substring(output.Length - 50)}'");
Console.WriteLine($#"\n Error stream: {#out}");
Console.ReadLine();
You need to redirect stdout (and probably stderr) so that any output comes to you, instead of a console; you may also want to redirect stdin. All of these things are available via ProcessStartInfo, with an example on MSDN. Note that if you want to display updates while the exe is running, you may need a worker thread to read incrementally from StandardOutput and StandardError, rather than ReadToEnd() - which won't return anything at all until the associated output pipe is closed.
However! If the console exe is "yours", it may be simpler to just expose the functionality you want in a library, and invoke it directly in-process. There are times when out-of-process is actively preferred, such as when you need to allow that process to go catastrophically wrong in some scenarios - but usually in-process is preferable, given free rein.
I need to execute the SFTP process through C# code. I am using Windows service. Below is the code I am using.
WebCash.writeToFile(WebCash.outerbatchpath, command);
WebCash.writeToFile(WebCash.outerbatchpath, "exit");
ProcessStartInfo psi = new ProcessStartInfo("cmd.exe", "/c "+WebCash.outerbatchpath);
psi.RedirectStandardOutput = true;
psi.RedirectStandardInput = true;
psi.RedirectStandardError = true;
psi.UseShellExecute = false;
psi.CreateNoWindow = true;
psi.WindowStyle = ProcessWindowStyle.Hidden;
Process p = new Process();
p.StartInfo = psi;
p.Start();
p.WaitForExit();
psi = null;
WebCash.writeToFile(WebCash.GetErrorLog(), DateTime.Now.ToString() + "- Running SFTP end - " + p.ExitCode);
return p.ExitCode;
While trying to debug the above code the debugger is not able to pass p.WaitforExit() function(i.e) service is not executing after p.WaitforExit().
I am running the below command(SFTP) in my outerbatch.
psftp -b D:\Source_Data\SFTP\innerbatch.bat -l username -i D:\privatekey.ppk #servername
Inner batch contains the below command
put D:\Source_Data\SFTP\FileToTransfer.xml
I need the exit code also to get the error. Pls help.
I assume the program you are running (the "outer batch") never finishes.
When you redirect a process output, you must be reading it. There's a limited buffer for the redirected output. When it fills, the child process stops on the next attempt to produce an output and waits until the parent process reads some data from the buffer. As you never do and you wait for the process to exit, deadlock occurs.
See the MSDN for the ProcessStartInfo.RedirectStandardOutput property :
Synchronous read operations introduce a dependency between the caller reading from the StandardOutput stream and the child process writing to that stream. These dependencies can cause deadlock conditions. When the caller reads from the redirected stream of a child process, it is dependent on the child. The caller waits for the read operation until the child writes to the stream or closes the stream. When the child process writes enough data to fill its redirected stream, it is dependent on the parent. The child process waits for the next write operation until the parent reads from the full stream or closes the stream. The deadlock condition results when the caller and child process wait for each other to complete an operation, and neither can continue. You can avoid deadlocks by evaluating dependencies between the caller and child process.
It's always recommended to use the p.StandardOutput.ReadToEnd() instead of the p.WaitForExit() in this case. Both commands wait for a process to exit, but the first ensures the deadlock does not occur.
On the other hand, as you are transferring only one file, I'm not sure the psftp output is actually large enough to fill the buffer. If the above suggestion does not help, I'd recommend you to simplify the code by directly running the psftp.exe, removing unnecessary layers of the cmd.exe and the outer batch file.
EDIT: If you never run psftp under a service account, it is possibly prompting for host key confirmation.
The server's host key is not cached in the registry. You
have no guarantee that the server is the computer you
think it is.
The server's dss key fingerprint is:
ssh-dss 1024 0b:77:8b:68:f4:45:b1:3c:87:ad:5c:be:3b:c5:72:78
If you trust this host, enter "y" to add the key to
PuTTY's cache and carry on connecting.
If you want to carry on connecting just once, without
adding the key to the cache, enter "n".
If you do not trust this host, press Return to abandon the
connection.
Store key in cache? (y/n)
You should use the -hostkey switch to explicitly specify a fingerprint of the trusted hostkey.
Alternatively use some native SFTP .NET library.
Side note: Naming the psftp script .bat is confusing. It's not a (Windows) batch file.
Using WinSCP.
For SshHostKeyFingerprint see Server and Protocol Information Dialog
// Setup session options
SessionOptions sessionOptions = new SessionOptions
{
Protocol = Protocol.Sftp,
HostName = "000.00.00.000",
UserName = "xxxxxx",
Password = "xxxxxx",
PortNumber= 22,
SshHostKeyFingerprint = "ssh-rsa 2048 xxxxxxxxxxxxxxx"
};
using (Session session = new Session())
{
// Connect
session.Open(sessionOptions);
// Upload files
//TransferOptions transferOptions = new TransferOptions();
//transferOptions.TransferMode = TransferMode.Binary;
//TransferOperationResult transferResult;
var results = session.ListDirectory("/path");
foreach (RemoteFileInfo item in results.Files)
{
Console.WriteLine(item.Name);
}
// Throw on any error
//transferResult.Check();
// Print results
//foreach (TransferEventArgs transfer in transferResult.Transfers)
//{
// Console.WriteLine("Upload of {0} succeeded", transfer.FileName);
//}
}
i am invoking an exe via c# Diagnostics.Process class and read output from it's StdOut. The process is forcefully terminated in case it doesn't automatically terminates in a specified time, something like:
process.StartInfo.FileName = #"D:\t.exe";
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardInput = false;
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = false;
process.WaitForExit(500);
if (!process.HasExited)
{
process.Kill();
process.WaitForExit();
}
string stdOutContents = process.StandardOutput.ReadToEnd();
Now the problem is the code works succesfully when the exe terminates normally. But in case it fails to terminate normally (usually the exe gets stuck in an infinite loop somewhere), stdOutContents is read as an empty string.
How can i read StdOut after the process is killed (without using process.OutputDataReceived event technique)? (It has been verified that the exe-in-question does always writes something onto StdOut even if it gets stuck somewhere).
Update 1
Details about Exe which is being invoked (refereed as 'native app' across this question)
It is a small utility implemented in c language and compiled using MS C++ compiler. It does its job while simultaneously outputting status information onto the StdOut (using putchar).
There are only two possible cases of operation:
It will run successfully while simultaneously printing some data onto the StdOut.
It will run normally to a certain point (simultaneously outputting data on StdOut) and then get stuck in an infinite loop. (This is an acceptable behavior).
Both scenarios have been verified using cmd.
Details about new attempts
i wrote a c# app (referred as dummy app) which mimics the native app behavior and this code works fine. However when run for the native app, i get nothing at all.
i don't understand why the code cant read the contents outputted by the native app?
i also tried using event handler for OutputDataReceived. It gets called only once with args.Data = null when the code tries to kill the process. Inspecting the behavior for dummy app revealed that when process.kill is called, the handler is invoked with args.Data = null. So this seems to be a standard behavior of sorts for both apps.
i also tried changing the newline characters for native app. Since it is implemented in c language, it uses \n for newline. i tried using both \r\n pair for newline but StdOut is still blank (for case 2).
I had the same interrogation and the doc of Process.Kill says
Data edited by the process or resources allocated to the process can be lost if you call Kill.
Which seems to indicate that you cannot rely on reading the StandardOutput of a process, although it is not clearly stated that the output / error streams are disposed.
I finally got inspired by this answer
How to spawn a process and capture its STDOUT in .NET?
and I use the following code :
var info = new ProcessStartInfo("some.exe");
info.CreateNoWindow = true;
info.RedirectStandardOutput = true;
info.UseShellExecute = false;
using (var p = new Process())
{
p.StartInfo = info;
var output = new StringBuilder();
p.OutputDataReceived += (sender, eventArgs) =>
{
output.AppendLine(eventArgs.Data);
};
p.Start();
p.BeginOutputReadLine();
if (!p.WaitForExit(5000))
{
Console.WriteLine("Taking too long...");
p.Kill();
Console.WriteLine("Process killed, output :\n" + output);
}
}
Same pattern can be used with the ErrorDataReceived
Note that one could miss some unflushed output from the child process, however in my case I don't expect much from a process that requires to be killed, at most some information for debugging purposes.
From the MSDN example of using stdoutput of newly created process:
// This is the code for the base process
Process myProcess = new Process();
// Start a new instance of this program but specify the 'spawned' version.
ProcessStartInfo myProcessStartInfo = new ProcessStartInfo(args[0], "spawn");
myProcessStartInfo.UseShellExecute = false;
myProcessStartInfo.RedirectStandardOutput = true;
myProcess.StartInfo = myProcessStartInfo;
myProcess.Start();
StreamReader myStreamReader = myProcess.StandardOutput;
// Read the standard output of the spawned process.
string myString = myStreamReader.ReadLine();
Console.WriteLine(myString);
myProcess.WaitForExit();
myProcess.Close();
If instead of myStreamReader.ReadLine() I'm using myStreamReader.ReadToEnd() shall I still use myProcess.WaitForExit()?
Or ReadToEnd() will wait until the process is finished?
EDIT:
Sorry for the diversion, to directly answer your question. Yes, you need to call Process.WaitForExit();. This will ensure that the process has yielded all its output before you call ReadToEnd()
ReadToEnd is synchronous function. Hence if you don't call it in your code, it will block your main thread until it captures only the first output from the StandardOutput, then that's it. But using WaitForExit will ensure that you have everything.
Also you might consider doing an asynchronous read of the process's output, see this MSDN Example that implements OutputDataRecieved
"ReadToEnd" is a function stored in "StreamReader" object and I don't think it has something to do with waiting for a process to exit, however the "Process" class might handle that itself. By the way, all the abilities "StreamReader" has are not useful in the situation you mentioned.
In my point of view, "WaitForExit" should be called and as you did "Close" too. Because they will release some system resources that no method else can. As far as I know, "ReadToEnd" method has nothing to do with calling those two.
Cheers
I can run a command line process using process.start().
I can provide input using standard input. After that when the process demands user input again, how can my program know and pass the input to that exe?
There's an example here that sounds similar to what you need, using Process.StandardInput and a StreamWriter.
Process sortProcess;
sortProcess = new Process();
sortProcess.StartInfo.FileName = "Sort.exe";
// Set UseShellExecute to false for redirection.
sortProcess.StartInfo.UseShellExecute = false;
// Redirect the standard output of the sort command.
// This stream is read asynchronously using an event handler.
sortProcess.StartInfo.RedirectStandardOutput = true;
sortOutput = new StringBuilder("");
// Set our event handler to asynchronously read the sort output.
sortProcess.OutputDataReceived += new DataReceivedEventHandler(SortOutputHandler);
// Redirect standard input as well. This stream
// is used synchronously.
sortProcess.StartInfo.RedirectStandardInput = true;
sortProcess.Start();
// Use a stream writer to synchronously write the sort input.
StreamWriter sortStreamWriter = sortProcess.StandardInput;
// Start the asynchronous read of the sort output stream.
sortProcess.BeginOutputReadLine();
Console.WriteLine("Ready to sort up to 50 lines of text");
String inputText;
int numInputLines = 0;
do
{
Console.WriteLine("Enter a text line (or press the Enter key to stop):");
inputText = Console.ReadLine();
if (!String.IsNullOrEmpty(inputText))
{
numInputLines ++;
sortStreamWriter.WriteLine(inputText);
}
}
hope that helps
If I understand correctly I think you should be able to do this by redirecting the Standard Input/Output using RedirectStandardOutput and RedirectStandardInput.
The WinSCP project has a C# sample for how to communicate with it using this way. You can find it here: SFTP file transfers in .NET (though this sample only collects the output without using it at all, but the technique should be the same.)
You want to look up IPC. As robert showed above, the Process class in .NET will help you. But specifically for your problem (how to know when to write data): You can't. Not generically.
If you know the required input (e.g. "yyy"), you can supply it in the STDIN of the created process. You don't have to wait for the program to request this information: It will just read from STDIN when it wants data.
If you need to process the programs output to decide what to write to STDIN, try reading the processes STDOUT. You might run into problems with flushing, though...