14

I would like to have a single executable file that I can use to either open a graphical app (the default use case, when clicking on the .exe), or that I can use to run command line tasks.

Is this possible?

If so, how would I have to modify my app.xaml/app.xaml.cs so it only opens the graphical view on specific conditions (e.g. no commandline parameters)?

Wilbert
  • 7,251
  • 6
  • 51
  • 91
  • You can pass a flag to indicate command line mode and when present, use AttachConsole to attach your app to the console that launched it. See http://www.codeproject.com/Tips/68979/Attaching-a-Console-to-a-WinForms-application for and example – user469104 Aug 25 '15 at 12:42

5 Answers5

11

First you have to use a WPF Application project and change the app.xml so that you can override the window creation.

<Application x:Class="WpfApplication1.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WpfApplication1">
    <Application.Resources>

    </Application.Resources>
</Application>

Note this is missing the StartupUri property.

Then, on your App.xaml.cs you can do something like this:

public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            if (condition)
            {
                var window = new MainWindow();

                window.ShowDialog();
            }
            else
            {
                AllocConsole();
            }
        }

        [DllImport("Kernel32.dll")]
        static extern void AllocConsole();
    }
Bruno Klein
  • 3,217
  • 5
  • 29
  • 39
6

@BrunoKlein's answer will work, and I based my answer on his solution. Quoting @BrunoKlein,

First you have to use a WPF Application project and change the app.xaml so that you can override the window creation.

<Application x:Class="WpfApplication1.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WpfApplication1">
    <Application.Resources>

    </Application.Resources>
</Application>

Note this is missing the StartupUri property.

Now, even simpler (this works in Visual Studio 2015 at least), go to the project properties, and change the output type from Windows Application to Console Application. This makes the project build as a console app, but still has the capabilities of a Windows Application.

Windows Application (Class Library is highlighted in this photo, select Console Application instead)

You did it! Done.

Now, instead of having a void Main(string[] args), your "main" method is the OnStautup method of your autogenerated App class:

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        string[] args = e.Args;

        if (SomeConditionBasedOnTheArgs(args))
        {
            // Instantiate view, call View.Show()
        }
        else
        {
            // Process the args
        }
    }
}

Note the one difference between this answer and @BrunoKlein's answer is that this one will always "show" a console if it is run from explorer/start menu/desktop, but if run from the command line, it will run and direct all of its standard output to that console, just like any normal console application.

Michael Plautz
  • 3,578
  • 4
  • 27
  • 40
5

As @BrunoKlein suggested, I remove the StartupUri property from App.xml and then override the OnStartup method. However I use AttachConsole instead, as I found that AllocConsole caused an extra console window to appear when run from command prompt.

It is also important to call FreeConsole and Shutdown to exit cleanly.

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        if (e.Args.Length == 0)
        {
            // No command line arguments, so run as normal Windows application.
            var mainWindow = new MainWindow();
            mainWindow.ShowDialog();
        }
        else
        {
            // Command-line arguments were supplied, so run in console mode.
            try
            {
                const int ATTACH_PARENT_PROCESS = -1;
                if (AttachConsole(ATTACH_PARENT_PROCESS))
                {
                    CommandLineVersionOfApp.ConsoleMain(e.Args);
                }
            }
            finally
            {
                FreeConsole();
                Shutdown();
            }
        }
    }

    [DllImport("kernel32")]
    private static extern bool AttachConsole(int dwProcessId);

    [DllImport("kernel32")]
    private static extern bool FreeConsole();
}
GrahamS
  • 9,980
  • 9
  • 49
  • 63
  • This is great! the only caveat I found is that using an exit code (`Shutdown(1)`) does not affect `%ErrorLevel%` – itsho Dec 12 '22 at 11:25
4

You can check whether the application has been executed from a console. If not, you can allocate a console dynamically:

if (GetConsoleWindow() == IntPtr.Zero)
   AllocConsole();

where

[DllImport("kernel32.dll")]
public static extern IntPtr GetConsoleWindow();

[DllImport("kernel32.dll")]
public static extern bool AllocConsole();
György Kőszeg
  • 17,093
  • 6
  • 37
  • 65
  • If the app has been started from console but did not get any commandline params, I still want to open the window.... – Wilbert Aug 25 '15 at 13:45
  • Just do so. :) You must check the command line parameters first, then. Anyway, before allocating a console you might to check whether the app already has one. – György Kőszeg Aug 25 '15 at 13:53
2

I kept getting a bug when using the above two solutions. Described here and it was resolved in the same manner. Visual Studio 2017 Debug Error: To prevent an unsafe abort when evaluating the function *.toString all threads where allowed to run

Tools → Options → Debugging → General → Check 'Use Managed Compatibility Mode'

Continuing my search for a solution that did not require me to switch off certain debug options I found this article that worked very well for me. https://benohead.com/blog/2015/04/22/c-wpf-console-hybrid-application/

Should it ever get removed, add a Program.cs, swap Windows App to Console application and make App.Program the startup object. Then in Program.cs, add and adjust below following code. The DLL imports take care of being able to hide the console window. All credit goes to @Benohead.

I made one change to default to GUI mode, and make arguments trigger the console app. The console window will show and close shortly prior to going into GUI mode.

using System;
using System.Runtime.InteropServices;

namespace HybridApp
{
    public class Program
    {
        [DllImport("kernel32.dll")]
        private static extern IntPtr GetConsoleWindow();

        [DllImport("user32.dll")]
        private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

        [STAThread]
        public static void Main(string[] args)
        {
            if (args.Length == 0)
            {
                // GUI mode
                ShowWindow(GetConsoleWindow(), 0 /*SW_HIDE*/);
                App.Main();
            }
            else if (args.Length > 0 && args[0] == "-c")
            {
                // console mode
                Console.WriteLine("Console mode active!");
                Console.ReadLine();
            }
        }
    }
}
Peter Vlaar
  • 445
  • 4
  • 6