29

Background: I am struggling to add command line and batch processing capabilities to an existing WPF Windows Application. When I detect some options at startup I suppress the window from appearing, do some processing and quit immedietally. Now, because there's no UI I'd like to output some messages to stdout/stderr. Consider following code:

namespace WpfConsoleTest
{
    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            Console.WriteLine("Start");
            System.Threading.Thread.Sleep(1000);
            Console.WriteLine("Stop");
            Shutdown(0);
        }
    }
}

When I run from command line I would expect following output:

Start
Stop

But instead:

C:\test>WpfConsoleTest.exe

C:\test>

You can redirect output, though:

C:\test>WpfConsoleTest.exe > out.txt

C:\test>type out.txt
Start
Stop

Redirecting to CON does not work, unfortunately:

C:\test>WpfConsoleTest.exe > CON

C:\test>

Another problem is that WpfConsoleTest.exe quits immedietaly after start. So:

C:\test>WpfConsoleTest.exe > out.txt & type out.txt

C:\test>

But:

C:\test>WpfConsoleTest.exe > out.txt & ping localhost > nul & type out.txt
Start
Stop

The best solution I was able to come with so far is to use start /B /wait:

C:\test>start /B /wait WpfConsoleTest.exe > out.txt & type out.txt
Start
Stop

This approach is mostly ok - if you wrap it up in a bat you can preserve error code and so on. The one huge downfall is that you get output after application ends, i.e. there's no way you can track progress, you have to wait for whatever is going on to finish.

Therefore, my question is: How to output to parent console from WPF Windows Application? Also, why is so hard to grab stdout/stderr from WPF?

I know that I could change application type to Console Application in project settings, but this has a nasty side effect - console window is visible all the time, even if you simply double click the exe. This solution also won't do, because it create a new console, even if application was run from cmd.

EDIT: to clarify, I want my app to output to the existing console if there is one and not to create a new one if it is missing.

Community
  • 1
  • 1
gwiazdorrr
  • 6,181
  • 2
  • 27
  • 36

6 Answers6

27

After digging up a bit, I found this answer. The code is now:

namespace WpfConsoleTest
{
    public partial class App : Application
    {
        [DllImport("Kernel32.dll")]
        public static extern bool AttachConsole(int processId);

        protected override void OnStartup(StartupEventArgs e)
        {
            AttachConsole(-1);
            Console.WriteLine("Start");
            System.Threading.Thread.Sleep(1000);
            Console.WriteLine("Stop");
            Shutdown(0);
        }
    }
}

Calling the exe directly still has a nasty side effect, connected with the call returning immediately:

C:\test>WpfConsoleTest.exe

C:\test>Start
Stop

^^^^
The cursor will stay here waiting for the user to press enter!

The solution is, once again, to use start:

C:\test>start /wait WpfConsoleTest.exe
Start
Stop

Thanks for input!

Community
  • 1
  • 1
gwiazdorrr
  • 6,181
  • 2
  • 27
  • 36
  • Thank you, gwiazdorrr. This is probably the better solution. But I am still not happy with the nasty effect it causes. Have you find a better solution maybe? – Boris Modylevsky Oct 13 '13 at 06:49
  • @BorisModylevsky: I'm afraid no, I settled with the `start` version invoked by a batch script. – gwiazdorrr Oct 14 '13 at 07:40
  • 1
    Great, much better than changing the app to Console and hiding the Console window on startup (terrible experience, since the Console window will appear briefly before the UI opens) – Aviad Ezra Sep 20 '15 at 18:53
  • 1
    The problem with AttachConsole is that after the app completes, user has to click 'enter' to continue to work with the console (it seems hang). You can call SendKeys.SendWait("{ENTER}"); at the end to release the console. – Aviad Ezra Sep 20 '15 at 19:34
  • I did that but I don't see the console window – STF Jul 25 '17 at 08:41
  • So glad I found this, came across some hideously complex workarounds. Worked perfectly for me to get stdout from a C# WPF Windows App. The console wait for input at the end didn't affect my use case as I'm launching the WPF app from another WPF app and re-directing stdout from one WPF app to the other. – Richard Moore Mar 20 '20 at 18:24
  • Before exiting I would print text "Paused. Press enter to resume." or something like that so it's clear to the user to press enter. – Wes Sep 25 '20 at 20:58
18

What I've done in the past is to make it a console application and call P/Invoke to hide the app's associated Console window once I've shown my WPF Window:

//added using statements per comments...
using System.Diagnostics;
using System.Runtime.InteropServices;

    internal sealed class Win32
    {
        [DllImport("user32.dll")]
        static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

        public static void HideConsoleWindow()
        {
            IntPtr hWnd = Process.GetCurrentProcess().MainWindowHandle;

            if (hWnd != IntPtr.Zero)
            {
                ShowWindow(hWnd, 0); // 0 = SW_HIDE
            }
        }
    }
Steve Danner
  • 21,818
  • 7
  • 41
  • 51
  • This is the best solution so far. I just don't like the console window appearing prior to the window being shown, I think it may be disturbing and confusing to common user. – gwiazdorrr May 02 '12 at 19:26
  • 3
    Cool solution, although I think I agree with @gwiazdorrr, but for utility projects, I think it's acceptable... just not sure about pure "production" projects. – Richard B Mar 21 '13 at 15:14
  • 1
    Add `using System.Diagnostics;` and `using System.Runtime.InteropServices;` for anyone wanting to get it to compile and wondering what namespaces the commands are in. – SharpC Feb 13 '15 at 11:18
  • Solution bellow is much better. Keep it a Window app and AttachConsole at startup. This way the console window will not appear briefly before the UI (doesn't look professional) – Aviad Ezra Sep 20 '15 at 18:55
  • Powershell will not show any output if I choose this solution. – Leo Li Jan 19 '23 at 10:51
8

A WPF application will not have a console by default, but you can easily create one for it and then write to it just like in a console app. I've used this helper class before:

public class ConsoleHelper
{
    /// <summary>
    /// Allocates a new console for current process.
    /// </summary>
    [DllImport("kernel32.dll")]
    public static extern Boolean AllocConsole();

    /// <summary>
    /// Frees the console.
    /// </summary>
    [DllImport("kernel32.dll")]
    public static extern Boolean FreeConsole();
}

To use this in your example, you should be able to do this:

namespace WpfConsoleTest
{
    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            ConsoleHelper.AllocConsole(); 
            Console.WriteLine("Start");
            System.Threading.Thread.Sleep(1000);
            Console.WriteLine("Stop");
            ConsoleHelper.FreeConsole();
            Shutdown(0);
        }
    }
}

And now, if you run it from the command line, you should see "Start" and "Stop" written to the console.

Matt Burland
  • 44,552
  • 18
  • 99
  • 171
  • 1
    Yes, but what I will see is a **new** console window, even if I start it from command line. I want to see the output in the **existing** console. – gwiazdorrr May 02 '12 at 15:19
  • 1
    Hmmm...That worked fine for me in WinForms, but it seems WPF does something different. You might try AttachConsole (http://msdn.microsoft.com/en-us/library/windows/desktop/ms681952%28v=vs.85%29.aspx), but you'll need to get a handle for the process that started your app (presumably cmd.exe) – Matt Burland May 02 '12 at 17:19
  • To get the parent process, this might help: http://www.rhyous.com/2010/04/30/how-to-get-the-parent-process-that-launched-a-c-application/ – Matt Burland May 02 '12 at 17:23
  • Works just great. Just for some newbies... This is the code required... protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); ConsoleHelper.AllocConsole(); } protected override void OnExit(ExitEventArgs e) { ConsoleHelper.FreeConsole(); base.OnExit(e); } – Eric Ouellet Feb 16 '16 at 03:37
  • 2
    This proposal is not the solution for all. Here we always create a new console with AllocConsole regardless whether we already have one open. That means you still cannot console.writeline to the command line when you start your WPF application from the command line and on program end the messages are all gone (since the extra console was closed). – codingdave Nov 24 '16 at 14:31
  • I did that but I just see the console window with no word. I tried also to color the console - but it stays black. – STF Jul 25 '17 at 08:29
  • This only works once. If I run it again I get crash. How it fix? – Manul74 Dec 07 '20 at 06:12
  • Use this solution, I'm not able to redirect the output to file, I mean, if I call start > result.txt, I will see noting in result.txt but directly see it on console. Any other idea? – Leo Li Jan 19 '23 at 10:51
0

Till now, I have found no best solution if I want to have

  1. Be able to redirect the output to other output stream, such as a file using ">" in command line
  2. Support powershell output

So maybe the correct answer is:

  1. Split your GUI app with your Console App for example, GUI called Tool.exe, and Console App called ToolConsole.exe.
  2. Mark Tool.exe type to be "Windows Application" while ToolConsole.exe to be "Console Application" in VS
  3. Put a bath file called "Tool.bat" and it can have the following content:
ToolConsole.exe %1 %2 %3 %4
  1. Then when you call Tool Param1 Param2... you are actually called ToolConsole.exe and when you directly runs Tool.exe by double click it, you are using Tool.exe GUI

That is my solution, if anyone have an idea to solve all cases I mentioned, glad to hear.

Leo Li
  • 488
  • 3
  • 7
0

Adapted from the answers from @gwiazdorrr and @MattBurland, converted into VB.NET. Show how to deal with issues related to debugging from Visual Studio.

' VB
''' <summary>Get the process ID.</summary>
Public Declare Function GetCurrentProcessId Lib "kernel32" () As Integer

''' <summary>Allocates a new console for current process.</summary>
Public Declare Function AttachConsole Lib "kernel32" (processID As Integer) As Boolean

''' <summary>Frees the console.</summary>
Public Declare Function FreeConsole Lib "kernel32" () As Boolean

''' <summary>Get a list of the process IDs currently attached to the console.</summary>
Public Declare Function GetConsoleProcessList Lib "kernel32" (processList As UInt32(), processCount As UInt32) As UInt32

Private Sub ShowCommandLine()
    ' Attach a console to this WPF application
    AttachConsole(-1)

    ' Output will not be visible if debugging from Visual Studio. Check whether a console is attached and
    ' that a debugger is not attached.
    Dim processList As UInt32()
    ReDim processList(64)
    Dim processesAttachedToConole As UInt32 = GetConsoleProcessList(processList, 64)
    Dim debuggerAttached As Boolean = System.Diagnostics.Debugger.IsAttached
    If (processesAttachedToConole = 0) OrElse (debuggerAttached) Then
        ' Don't display anything on the console window
        ' You could pop up a WPF message here...
    Else
        ' Show help in the console window
        Console.WriteLine()

        ' Version information (by way of example)
        Dim myAssembly As Assembly = Assembly.GetEntryAssembly()
        Dim myName As String = myAssembly.GetName().Name
        Dim myVersion As FileVersionInfo = FileVersionInfo.GetVersionInfo(myAssembly.Location)
        Console.WriteLine(String.Format("{0} v{1}", myName, myVersion.FileVersion))
        Console.WriteLine()

        ' Force a RETURN keypress, otherwise the user must hit RETURN themselves
        SendKeys.SendWait("{ENTER}")
    End If

    ' Detach the console
    FreeConsole()
End Sub
AlainD
  • 5,413
  • 6
  • 45
  • 99
-1

In the project properties, you can change the project type from a Windows application to a Console application, and AFAIK the only difference is that you get a console to use throughout the application's lifetime. I haven't tried it with WPF though.

If you want to open and close a console, you should use Alloc/FreeConsole, but it is usually simpler to change the project type.

Kendall Frey
  • 43,130
  • 20
  • 110
  • 148
  • As I wrote, changing to Console Application will create a console window if you double click the exe. What I want is the output to be printed into **existing** console, if there isn't one do not create a new one. – gwiazdorrr May 02 '12 at 15:21