Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stop a .net console app from being closed

Tags:

.net

console

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.

like image 257
SillyMonkey Avatar asked May 18 '09 13:05

SillyMonkey


3 Answers

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
    }
}
like image 119
beach Avatar answered Oct 24 '22 01:10

beach


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!!"

like image 3
Robin Day Avatar answered Oct 24 '22 00:10

Robin Day


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.

like image 3
Martin Marconcini Avatar answered Oct 23 '22 23:10

Martin Marconcini