tags:

views:

1938

answers:

6

Is there any way to stop a .NET console app from being closed? I've got an app that follows this pattern:

while (true)
{
    string x = Console.ReadLine();
    StartLongRunningTaskOnSeparateThread(x);
}

The problem is that it's possible to close the console window (and therefore cut off the long running task). Is there an equivilent of the Forms.OnClosing event for console apps?

EDIT - I'm not trying to create something that's impossible to kill, just maybe give a warning message e..g "hey i'm not done yet. are you sure you want to close me?"

EDIT2 - Preventing an untimely exit via the 'x' button is more important than blocking Ctrl-C (I used Console.TreatControlCAsInput = true;). Assume for this purpose that anyone who fires up task manager wants to kill the program so much that they deserve to be able to. And that the end user would rather see a warning than accidentally cancel their long running task.

A: 

I don't know of any equivalent function of Forms.OnClosing for a Console application. However even Forms.OnClosing is not a foolproof way to prevent an application from closing. I can just as easily find the program in task manager and kill it. This will completely bypass the Forms.OnClosing trick and rudely shut down your application. There is nothing you can do to prevent this.

Can you give us a little more context on why you want to do this?

JaredPar
+3  A: 

It's not foolproof as obviously task manager or a shutdown is enough to stop the task, however, were you to write the application as a Service then at least it wouldn't be sitting on the desktop with a tempting "x" for people to hit?

EDIT: Based on your comment. A console app requiring user input that you can't rewrite will always be closable using the "x".

A rewrite as a service is not a huge task. A service is really nothing more than an executable with some wrapper code.

The difficulty comes with the user input. However, you could write your long running thread as a service yet have a console app to pass the instruction to run the thread.

Alternatively, if you just want to stop people accidently pressing the "x", why not fill the first 10 lines of the console app with "IMPORTANT PROCESS DO NOT CLOSE!!"

Robin Day
1. Don't want to rewrite 2. Service no good as needs user input
SillyMonkey
+1  A: 

Is there any way to stop a .NET console app from being closed?

No. You can block control-C (see Console.CancelKeyPress event), but not control-break.

If you need something that is hard to stop you need to look at creating a Windows service, with an ACL to block it being stopped (except by the system... so the system can shutdown).

Addition: If user interaction is required, then split the application into two parts. A service which keeps running, exposing an inter-process entry point. And a UI which provides interactivity and uses inter-process communications to communicate with the service.

Richard
+2  A: 

Why don't you write a small Winforms frontend? It's easier to provide visual feedback when the user is closing. You can disable de minimize/maximize/close buttons.

I mean, there's no penalty in running a superthin winforms app that can perform your stuff.

I assume you're on Windows, but if this were Mono.NET you could use GTK or similar.

Martín Marconcini
+4  A: 

Here is my attempt to solve this problem. Task Manager can still close the application though. But, my thought was to try and detect when it is being closed and then relaunch it. However, I didn't implement that part.

The following program will detect a CTRL-C && CTRL-BREAK and will keep on going.

EDIT: Removed the "X" Button

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace DoNotCloseMe
{
    class Program
    {

        const string _title = "DO NOT CLOSE - Important Program";

        static void Main(string[] args)
        {

            Console.Title = _title;

            IntPtr hMenu = Process.GetCurrentProcess().MainWindowHandle;
            IntPtr hSystemMenu = GetSystemMenu(hMenu, false);

            EnableMenuItem(hSystemMenu, SC_CLOSE, MF_GRAYED);
            RemoveMenu(hSystemMenu, SC_CLOSE, MF_BYCOMMAND);

            WriteConsoleHeader();

            //This function only seems to be called once.
            //After calling MainLoop() below, CTRL-C && CTRL-BREAK cause Console.ReadLine() to return NULL 
            Console.CancelKeyPress += (sender, e) =>
            {
                Console.WriteLine("Clean-up code invoked in CancelKeyPress handler.");
                Console.WriteLine("Key Pressed: {0}", e.SpecialKey.ToString());
                System.Threading.Thread.Sleep(1000);
                MainLoop();
                // The application terminates directly after executing this delegate.
            };

            MainLoop();

        }

        private static void MainLoop()
        {
            while (true)
            {
                WriteConsoleHeader();
                string x = Console.ReadLine();
                if (!String.IsNullOrEmpty(x))
                {
                    switch (x.ToUpperInvariant())
                    {
                        case "EXIT":
                        case "QUIT":
                            System.Environment.Exit(0);
                            break;
                        default:
                            StartLongRunningTaskOnSeparateThread(x);
                            break;
                    }

                }
            }
        }

        private static void StartLongRunningTaskOnSeparateThread(string command)
        {
            var bg = new System.ComponentModel.BackgroundWorker();
            bg.WorkerReportsProgress = false;
            bg.WorkerSupportsCancellation = false;

            bg.DoWork += (sender, args) =>
            {
                var sleepTime = (new Random()).Next(5000);
                System.Threading.Thread.Sleep(sleepTime);
            };


            bg.RunWorkerCompleted += (sender, args) =>
            {
                Console.WriteLine("Commmand Complete: {0}", command);
            };

            bg.RunWorkerAsync();

        }

        private static void WriteConsoleHeader()
        {
            Console.Clear();
            Console.WriteLine(new string('*', Console.WindowWidth - 1));
            Console.WriteLine(_title);
            Console.WriteLine(new string('*', Console.WindowWidth - 1));
            Console.WriteLine("Please do not close this program.");
            Console.WriteLine("It is maintaining the space/time continuum.");
            Console.WriteLine("If you close it, Q will not be happy and you will be assimilated.");
            Console.WriteLine(new string('*', Console.WindowWidth - 1));
            Console.WriteLine("Development Mode: Use \"EXIT\" or \"QUIT\" to exit application.");
            Console.WriteLine(new string('*', Console.WindowWidth - 1));
        }

        #region "Unmanaged"

        [DllImport("user32.dll")]
        static extern bool EnableMenuItem(IntPtr hMenu, uint uIDEnableItem, uint uEnable);

        [DllImport("user32.dll")]
        static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);

        [DllImport("user32.dll")]
        static extern IntPtr RemoveMenu(IntPtr hMenu, uint nPosition, uint wFlags);

        internal const uint SC_CLOSE = 0xF060;
        internal const uint MF_GRAYED = 0x00000001;
        internal const uint MF_BYCOMMAND = 0x00000000;

        #endregion
    }
}
beach
@SM - As noted in the answer. But I gave you a plan of attack. ;)
beach
@SM - Removed the "X" button. Task Manager can still kill it though.
beach
A: 

I wouldn't prevent a user from closing the application. What I would do is give them a warning when you start the process saying "This might take several minutes. Please wait..."

Then if they decide to kill it they kill it.

Personally I dislike it when a process takes away too much control from me.

David Basarab