I am developing a C# component for Grasshopper for Rhino. As I am running some pretty heavy iterative analysis I would like to output results continuously to a cmd window just to make sure that the analysis is actually running.
Here's what I tried:
using System.Diagnostics;
Result results = new Result();
Process cmd = new Process();
cmd.StartInfo.FileName = "cmd.exe";
cmd.StartInfo.RedirectStandardInput = true;
cmd.StartInfo.RedirectStandardOutput = true;
cmd.StartInfo.CreateNoWindow = false;
cmd.StartInfo.UseShellExecute = false;
cmd.Start();
do {
results = RunHeavyOperation(results);
cmd.StandardInput.WriteLine("echo " + results.usefulInfo);
} while (!results.conditionForEnd);
cmd.WaitForExit();
Result RunHeavyOperation(Result previousResults) {
Result res = doHeavyStuff(previousResults);
return res;
}
I realise that I am missing part, but what is it?
Your approach is wrong: You currently don't write to a console window. Instead you created a process by starting cmd.exe and write to the standard input pipe of that process. cmd.exe is not aware of that. It's not the same as typing in a console via your keyboard, and even that can have strange effects.
Imagine you output a new line character, so cmd.exe might try to "execute" what you output before as a command.
The correct way is to invoke AllocConsole. With this call you can create a console window for your process and use it simply via Console.WriteLine().
When you finished your work and logging, you'll eventually need to close and free this console again via FreeConsole.
So import these two native methods:
internal sealed class NativeMethods
{
[DllImport("kernel32.dll")]
public static extern bool AllocConsole();
[DllImport("kernel32.dll")]
public static extern bool FreeConsole();
}
And use them in your code:
NativeMethods.AllocConsole();
// start work
Console.WriteLine("log messages...");
// finished work
NativeMethods.FreeConsole();
Note that FreeConsole() will close the console window, so all your log messages get lost. And a console only has a so large buffer and you can't scroll back to older messages if the leave the buffer.
So it may be a better idea to simply write your log messages into a file that you can analyze later.
Create a Console App in Visual Studio. In VS 2017 it looks like this (on the start page):
Console apps open a command console automatically. Then you can write to it with
Console.WriteLine("hello");
The console closes automatically when the program terminates. So make sure to call Console.ReadKey(); at the end, if you want to keep it open.
Related
I have a third party DOS process which writes data about its progress to the command line.
I want to react on the progress. Normally I would use a Process with RedirectStandardOutput = true and RedirectStandardError = true and then
.OutputDataReceived +=xyzOutputDataReceived;
.ErrorDataReceived += xyzErrorDataReceived;
.Start();
.BeginOutputReadLine();
.BeginErrorReadLine();
Normally this works. and I got what i need as DataReceivedEventArg.
In this case the process seems to update the same line it has written (how is that possible?), so it writes 15 %, 15% changes to 18% and so on. Only at the end of execution it seems that the data is flushed to StandardOutput.
Also if i just try to pipe data to a text file (eg odb.exe >> output.txt) it shows nothing.
Is there any way to get the temporary data?
The question is not about getting the Standard Output, this works fine (synchronously and asynchronously). It is about how to get output from a process which I cannot change, and which does not seem to flush it's output to the standard stream.
Like juharr says, you need to use Win32 to screen scrape the console.
Fortunately you don't need to write that code yourself. You can use the buffer-reader from this post: https://stackoverflow.com/a/12366307/5581231
The BufferReader reads from standardout. I suppose you are writing a wpf or winforms application so we'll also have to get a reference to the console window of the DOS application. For this, we will use the Win32 API call AttachConsole.
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
private static extern bool AttachConsole(int pid);
I wrote a small example program that demonstrates the usage. It starts the exe and attaches to its console. It then scrapes the entire window once a second, and dumps the output to the debugger output window. You should be able to modify this to search the console content for any keywords etc. that you can use to track the progress of the program. Or you could dump it to a textfield or something in your UI, possibly after comparing it for changes?
var process = Process.Start(#"..path to your exe....");
//Wait for the DOS exe to start, and create its console window
while (process.MainWindowHandle == IntPtr.Zero)
{
Thread.Sleep(500);
}
//Attach to the console of our DOS exe
if (!AttachConsole(process.Id))
throw new Exception("Couldn't attach to console");
while (true)
{
var strings = ConsoleReader.ReadFromBuffer(0, 0,
(short)Console.BufferWidth,
short)Console.BufferHeight);
foreach (var str in strings.
Select(s => s?.Trim()).
Where(s => !String.IsNullOrEmpty(s)))
{
Debug.WriteLine(str);
}
Thread.Sleep(1000);
}
Good Luck!
Rules that i need to hold on to are the following:
run command prompt command as administrator
change directory (path is always the same)
run command net start something
Any thoughts on how can i do this programatically using C# ?
Just for the record i am running console application built in .net 4.0
I am using this function
private static void commandtorun(string commandexecuted)
{
string currentstatus;
ProcessStartInfo startInfo = new ProcessStartInfo();
Process myprocess = new Process();
try
{
startInfo.FileName = "cmd"; //
startInfo.RedirectStandardInput = true;
startInfo.RedirectStandardOutput = true;
startInfo.UseShellExecute = false; //'required to redirect
//startInfo.CreateNoWindow = true; // '<---- creates no window, obviously
myprocess.StartInfo = startInfo; //
myprocess.Start(); //
System.IO.StreamReader SR;
System.IO.StreamWriter SW;
Thread.Sleep(200);
SR = myprocess.StandardOutput;
SW = myprocess.StandardInput;
SW.WriteLine(commandexecuted); // 'the command you wish to run.....
SW.WriteLine("exit"); // 'exits command prompt window
Thread.Sleep(200);
currentstatus = SR.ReadToEnd();
SW.Close();
SR.Close();
}
catch (Exception e)
{
Console.WriteLine("{0} Exception caught.", e);
Console.ReadLine();
}
// throw new NotImplementedException();
}
and i get access is denied when i call the function using this command:
commandtorun(#"cd c:\Program Files (x86)\folder1\folder2"); // change the folder because cmd always runs on different path
commandtorun("net start something"); run the COMMAND
I tried to run both commands in one statement but got the same error.
There are lots of problems here.
You want to run the other process elevated, but your code does not take steps to make that happen. In order to do that you need to invoke your process with the runas verb, and UseShellExecute set to true. But you also want to re-direct which is why you set UseShellExecute to false.
I see no compelling reason to redirect, so I think you can use true for UseShellExecute. You are pumping an exit command into cmd.exe to terminate the process. You don't need to do that. Simply pass cmd.exe the /c argument and the process will close when it is done.
These changes will allow you to remove those calls to Sleep(). Any time you call Sleep() you should ask yourself why you are doing it and if it can be avoided. Very few problems have Sleep() as the optimum solution.
You are trying to specify the working directory by executing cd. That again is the wrong way to do it. You can specify the working directory in the ProcessStartInfo object. That's how you should do it.
Your design has you executing each command in a separate process. That's a really poor way to go. You want to execute all the commands one after the other in a single instance of cmd.exe. Put the commands into a .bat or .cmd file and get cmd.exe to process that.
Imagine if you carried on processing each command as a separate process. How would the second process remember the working directory change that you made? Would you really want your user to see a UAC dialog for each separate command?
Having said all of that though, you are going about this the wrong way. You are just trying to start a service. Yes, you do that with net start when you are working in the command line. But from a program you use the service API to start a service. In other words I believe that you should throw away all of this code and call the service API.
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How do I show console output/window in a forms application?
Is there a way for a c# winforms program to write to the console window?
There are basically two things that can happen here.
Console output
It is possible for a winforms program to attach itself to the console window that created it (or to a different console window, or indeed to a new console window if desired). Once attached to the console window Console.WriteLine() etc works as expected. One gotcha to this approach is that the program returns control to the console window immediately, and then carries on writing to it, so the user can also type away in the console window. You can use start with the /wait parameter to handle this I think.
Start commsnd syntax
Redirected console output
This is when someone pipes the output from your program somewhere else, eg.
yourapp > file.txt
Attaching to a console window in this case effectively ignores the piping. To make this work you can call Console.OpenStandardOutput() to get a handle to the stream that the output should be piped to. This only works if the output is piped, so if you want to handle both of the scenarios you need to open the standard output and write to it and attach to the console window. This does mean that the output is sent to the console window and to the pipe but its the best solution I could find. Below the code I use to do this.
// This always writes to the parent console window and also to a redirected stdout if there is one.
// It would be better to do the relevant thing (eg write to the redirected file if there is one, otherwise
// write to the console) but it doesn't seem possible.
public class GUIConsoleWriter : IConsoleWriter
{
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
private static extern bool AttachConsole(int dwProcessId);
private const int ATTACH_PARENT_PROCESS = -1;
StreamWriter _stdOutWriter;
// this must be called early in the program
public GUIConsoleWriter()
{
// this needs to happen before attachconsole.
// If the output is not redirected we still get a valid stream but it doesn't appear to write anywhere
// I guess it probably does write somewhere, but nowhere I can find out about
var stdout = Console.OpenStandardOutput();
_stdOutWriter = new StreamWriter(stdout);
_stdOutWriter.AutoFlush = true;
AttachConsole(ATTACH_PARENT_PROCESS);
}
public void WriteLine(string line)
{
_stdOutWriter.WriteLine(line);
Console.WriteLine(line);
}
}
What I want to have happen is that the console window just goes away, or better yet that it is hidden, but I want my application to keep running. Is that possible? I want to be able to use Console.WriteLine and have the console serve as an output window. I want to be able to hide and show it, and I don't want the whole app to die just because the console was closed.
EDIT
Code:
internal class SomeClass {
[DllImport("kernel32")]
private static extern bool AllocConsole();
private static void Main() {
AllocConsole();
while(true) continue;
}
}
EDIT 2
I tried the accepted solution here [ Capture console exit C# ], per the suggestion in the comments on this question. The example code is bugged in that the DLLImport needs to be "kernel32.dll" or "kernel32", not "Kernel32". After making that change, I'm getting a message to my handler for CTRL_CLOSE_EVENT when I click the X on the console window. However, calling FreeConsole and/or returning true doesn't prevent the application from terminating.
Ah, yes, this is one of the caveats of using the Windows console subsystem. When the user closes the console window (regardless of how the console was allocated), all of the processes that are attached to the console are terminated. That behavior makes obvious sense for console applications (i.e., those that specifically target the console subsystem, as opposed to standard Windows applications), but it can be a major pain in cases like yours.
The only workaround that I know of is to use the SetConsoleCtrlHandler function, which allows you to register a handler function for Ctrl+C and Ctrl+Break signals, as well as system events like the user closing the console window, the user logging off, or the system shutting down. The documentation says that if you're only interested in ignoring these events, you can pass null for the first argument. For example:
[DllImport("kernel32")]
static extern bool SetConsoleCtrlHandler(HandlerRoutine HandlerRoutine, bool Add);
delegate bool HandlerRoutine(uint dwControlType);
static void Main()
{
AllocConsole();
SetConsoleCtrlHandler(null, true);
while (true) continue;
}
That works perfectly for Ctrl+C and Ctrl+Break signals (which would have otherwise caused your application to terminate as well), but it doesn't work for the one you're asking about, which is the CTRL_CLOSE_EVENT, generated by the system when the user closes the console window.
Honestly, I don't know how to prevent that. Even the sample in the SDK doesn't actually allow you to ignore the CTRL_CLOSE_EVENT. I tried it in a little test app, and it beeps when you close the window and prints the message, but the process still gets terminated.
Perhaps more worryingly, the documentation makes me think it is not possible to prevent this:
The system generates CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, and CTRL_SHUTDOWN_EVENT signals when the user closes the console, logs off, or shuts down the system so that the process has an opportunity to clean up before termination. Console functions, or any C run-time functions that call console functions, may not work reliably during processing of any of the three signals mentioned previously. The reason is that some or all of the internal console cleanup routines may have been called before executing the process signal handler.
It's that last sentence that catches my eye. If the console subsystem starts cleaning up after itself immediately in response to the user attempting to close the window, it may not be possible to halt it after the fact.
(At least now you understand the problem. Maybe someone else can come along with a solution!)
Unfortunately there's nothing you can do to really alter this behaviour.
Console windows are "special" in that they're hosted by another process and do not allow sub-classing. This limits your ability to modify their behaviour.
From what I know, your two options are:
1. Disable the close button altogether. You can do this with the following code fragment:
HWND hwnd = ::GetConsoleWindow();
if (hwnd != NULL)
{
HMENU hMenu = ::GetSystemMenu(hwnd, FALSE);
if (hMenu != NULL) DeleteMenu(hMenu, SC_CLOSE, MF_BYCOMMAND);
}
2. Stop using consoles altogether, and implement your own text output solution.
Option #2 is the more complicated option but would provide you the greatest control. I found an article on CodeProject that implements a console-like application using a rich edit control to display the text (rich edit controls have the ability to stream text like the console, so they are well suited to this sort of application).
On closing the console window obtained using AllocConsole or AttachConsole, the associated process will exit. There is no escape from that.
Prior to Windows Vista, closing the console window would present a confirmation dialogue to the user asking him whether the process should be terminated or not but Windows Vista and later do not provide any such dialogue and the process gets terminated.
One possible solution to work around this is avoiding AttachConsole altogether and achieving the desired functionality through other means.
For instance in the case described by OP, console window was needed to output some text on Console using Console static class.
This can be achieved very easily using inter-process communication. For example a console application can be developed to act as an echo server
namespace EchoServer
{
public class PipeServer
{
public static void Main()
{
var pipeServer = new NamedPipeServerStream(#"Com.MyDomain.EchoServer.PipeServer", PipeDirection.In);
pipeServer.WaitForConnection();
StreamReader reader = new StreamReader(pipeServer);
try
{
int i = 0;
while (i >= 0)
{
i = reader.Read();
if (i >= 0)
{
Console.Write(Convert.ToChar(i));
}
}
}
catch (IOException)
{
//error handling code here
}
finally
{
pipeServer.Close();
}
}
}
}
and then instead of allocating/attaching a console to the current application, the echo server can be started from within the application and Console's output stream can be redirected to write to the pipe server.
class Program
{
private static NamedPipeClientStream _pipeClient;
static void Main(string[] args)
{
//Current application is a Win32 application without any console window
var processStartInfo = new ProcessStartInfo("echoserver.exe");
Process serverProcess = new Process {StartInfo = processStartInfo};
serverProcess.Start();
_pipeClient = new NamedPipeClientStream(".", #"Com.MyDomain.EchoServer.PipeServer", PipeDirection.Out, PipeOptions.None);
_pipeClient.Connect();
StreamWriter writer = new StreamWriter(_pipeClient) {AutoFlush = true};
Console.SetOut(writer);
Console.WriteLine("Testing");
//Do rest of the work.
//Also detect that the server has terminated (serverProcess.HasExited) and then close the _pipeClient
//Also remember to terminate the server process when current process exits, serverProcess.Kill();
while (true)
continue;
}
}
This is just one of the possible solutions. In essence the work around is to allot the console window to its own process so that it can terminate without affecting the parent process.
You can do this by disabling keyboard mouse input by external program called Keyfreez.
you can use it multiple times in your program where no user input required. And if any user input require u can add a process Takskkill /f /IM .
https://www.sordum.org/7921/bluelife-keyfreeze-v1-4-block-keyboard-and-mouse/
Hope this helps all of you
I have seen many examples of a hybrid gui/cli app for C#. I have implemented such an app, but I am having a hard time figuring out how to prevent the .exe, when run on the command line, from not returning back to the prompt right away.
//defines for commandline output
[DllImport("kernel32.dll")]
static extern bool AttachConsole(int dwProcessId);
private const int ATTACH_PARENT_PROCESS = -1;
[STAThreadAttribute]
static void Main(string[] args)
{
// load cli
// redirect console output to parent process;
// must be before any calls to Console.WriteLine()
AttachConsole(ATTACH_PARENT_PROCESS);
if (args.Length == 0)
{
//loads gui
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new form_Main());
}
else
{
cli_main cli = new cli_main();
cli.start_cli(args);
Console.WriteLine("finished");
System.Windows.Forms.SendKeys.SendWait("{ENTER}");
Application.Exit();
}
}
I get the following output
C:\Users\john\Documents\Visual Studio 2010\Projects\test\test\test\bin\Debug>test.exe -param1 test
-param2 test2
C:\Users\john\Documents\Visual Studio 2010\Projects\test\test\test\bin\Debug>Output was successful. File saved as: c:\test\test.html
finished
The line "finished" is a string I output when I know I have reach end of my main code... this works fine in Winforms, my project is Winforms and I started it as a gui but now I am trying to make it hybrid gui/cli
And it seems to be running my main code and threads I see them in debugging and it creates my final output file...
I just am puzzled as to how to keep the .exe when executed from cmd line with it's parameters, to not return to the command prompt?? And have it wait with a blinking cursor, then output the line about the html file and then the line "finished", then finally go back to the command prompt.
I've tried numerous things like removing
System.Windows.Forms.SendKeys.SendWait("{ENTER}");
Application.Exit();
and instead of using Application.Exit(); use Environment.Exit(0); but it always returns to the command prompt right away, also I tried putting in sleep for 5 secs after the line
cli.start_cli(args);
but that didn't work either, I guess I don't understand how it can return to command prompt right away and it hasn't even it the line
Console.WriteLine("finished");
FWIW, I tried the first approach as well. I ended up just hiding the console window using:
IntPtr handle = GetConsoleWindow();
if (handle != IntPtr.Zero)
{
ShowWindow(handle, 0); // 0=SW_HIDE
}
This completely hides the window, even from the Taskbar. It flashes for a brief second but that's acceptable in my case