5

Is it possible to log message from WinForms app to cmd.exe process started programmatically? I've tried to do all kinds of variations of following code:

private void button1_Click(object sender, EventArgs e)
{
    Log("Button has been pressed");
}

private void Log(string message)
{
    var process = Process.Start(new ProcessStartInfo("cmd.exe", @"/K ""more""")
    {
        UseShellExecute = false,
        RedirectStandardInput = true,
    });
    process.StandardInput.WriteLine(message);
}

Unfortunately, console window blinks for a half of a second and that's it.

Please don't answer what I really want to do, because right now I'm just curious if it's possible :)

prostynick
  • 6,129
  • 4
  • 37
  • 61
  • One correction would be to only create one process. I guess you may have laid it out like that for illustrative purposes, but worth mentioning. – Kieren Johnstone Jul 01 '11 at 10:02
  • Have to tried exploring Log4Net console appender. If you really want to start cmd.exe and write to it I dont think there is anyway to do so without blinking – Ash Jul 01 '11 at 10:09
  • @Kieren Johnstone exactly - for illustrative purposes only :) – prostynick Jul 01 '11 at 10:20

5 Answers5

6

You're in luck. I just solved this for a code generator toy project I had floating around here.

Using AttachConsole or AllocConsole Win32 API's you can achieve the result, by just using System.Console.WriteLine (or Read, or Error.WriteLine etc) like you normally would.

The following snippet figures out when a forms app was started from a Console window, and attaches to it's parent console; If that doesn't work, it will create it's own console, and remember to keep it open (so it doesn't vanish before the user can read the output, for example).

    [DllImport("kernel32.dll")] static extern bool AllocConsole();
    [DllImport("kernel32.dll")] static extern bool AttachConsole(int pw);
    private static bool _hasConsole, _keepConsoleOpen;
    static private void EnsureConsole()
    {
        _hasConsole      =  _hasConsole || AttachConsole(-1);
        _keepConsoleOpen = !_hasConsole || _keepConsoleOpen;
        _hasConsole      =  _hasConsole || AllocConsole();
    }

    [STAThread]
    private static void Main(string[] args)
    {
          EnsureConsole();

          // the usual stuff -- your program

          if (_keepConsoleOpen)
          {
              Console.WriteLine("(press enter)...");
              Console.Read();
          }
    }
sehe
  • 374,641
  • 47
  • 450
  • 633
  • I like your solution! I have seen `AttachConsole(-1)` before, but it requires that the program was started from console, but I haven't seen `AllocConsole()` and that's exactly what I need. Since this answer doesn't tell how to attach to `cmd.exe`, I will accept it if no one propose exact solution. Thanks! – prostynick Jul 01 '11 at 11:33
  • @prostynick: what do you mean with attach to cmd.exe? What behaviour are you missing with my example? Keep in mind that if you now start cmd.exe, it will automatically be attached to you allocated console as a child process. – sehe Jul 01 '11 at 11:37
  • your example works like a charm and that's exactly what I was looking for, but I formulated the question slightly different (ie. how to attach to cmd.exe), so maybe there is someone who is going to write strict answer. If not, I will accept yours :) – prostynick Jul 01 '11 at 11:45
  • This still works fine for WPF projects and it was exactly what I was looking for, thanks. – Ben Feb 02 '12 at 12:11
  • How can you close this Console without closing whole application ? – Mark Aug 26 '13 at 09:18
  • @Mark [the documentation for AllocConsole](http://msdn.microsoft.com/en-us/library/windows/desktop/ms681944(v=vs.85).aspx) links to [FreeConsole](http://msdn.microsoft.com/en-us/library/windows/desktop/ms683150(v=vs.85).aspx) and [more Console Functions](http://msdn.microsoft.com/en-us/library/windows/desktop/ms682073(v=vs.85).aspx) – sehe Aug 26 '13 at 09:20
  • @sehe 2 I mean if user clicked on the close button of the console, the whole project will be closed. – Mark Aug 26 '13 at 09:23
  • @Mark then you might be interested in AttachConsole and some error handling. I would think, though, it's probably easier to (a) design a console view of your own for simple stuff (b) use a dedicated "client" process to interact with the console, with the "server" being the long-running process. – sehe Aug 26 '13 at 09:27
  • @sehe yes, you are right, it's just a shame there is no easier solution for this . thanks :) – Mark Aug 26 '13 at 09:29
2

Not the final answer, but it should help at least a little:

First of all, declare that process outside your Log routine so it doesn't go out of scope when you want to log more.

Anyway I tried the following:

Process process = Process.Start(new ProcessStartInfo("cmd.exe", "/K more") {
  UseShellExecute = false,
  RedirectStandardInput = true,
  RedirectStandardError = true,
  RedirectStandardOutput = false
});

private void button1_Click(object sender, EventArgs e) {
  Log("Button has been pressed");
  MessageBox.Show(process.StandardError.ReadToEnd());
}

private void Log(string message) {
  process.StandardInput.WriteLine(message);
}

After clicking the button, your app will hang (it's waiting for the console to close, ReadToEnd literally reads to the end). When you close the console you will get a messagebox with the error output:

The handle is invalid.
The handle is invalid.
The handle is invalid.
'Button' is not recognized as an internal or external command,
operable program or batch file.
The handle is invalid.
The handle is invalid.

It seems not to be possible to redirect only the input without redirecting the output when running CMD. I'm not sure why, maybe trying different commandline options will help. I also tried removing the More command and logging "dir" instead:

Log("dir");

The result was a bit scary:

The handle is invalid.
...
There is not enough space on the disk.
The handle is invalid.
The handle is invalid.

Interesting I must say! Looks like you've opened a can of worms :-)

Louis Somers
  • 2,560
  • 3
  • 27
  • 57
1

You have to insert a pause command after printing to the console.

The process terminates as soon as the command is executing, resulting in the blink you see.

Zebi
  • 8,682
  • 1
  • 36
  • 42
  • I have created a.bat: cmd.exe /K more {CRLF_HERE} PAUSE and changed code to run it without parameters and it's still blinks and that's all. – prostynick Jul 01 '11 at 10:26
  • @Zebi No, he's running the `more` executable, which reads input from stdin until EOF. `cmd` is just invoking it – Kieren Johnstone Jul 01 '11 at 10:29
0

As far as I know, I believe you get the CMD opened right. But as soon as the command has been executed the console terminates. Thats normal behaviour.

If you execute a .bat/.cmd you could script more than one command and if you made a "pause" after the command, you should be able to see what happend before the console terminates.

BerggreenDK
  • 4,915
  • 9
  • 39
  • 61
0

I think if you build your project as a "Console Application", you get the console and can still open Windows forms. Create a new project of type "Console Application". Add a Windows Form to the project. In the Main function in Program.cs, instantiate and display the form using this code:

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());

Am not sure if this is the ideal way to do it. But it works.

Tundey
  • 2,926
  • 1
  • 23
  • 27