Search code examples
.netwindows-task-scheduler

Windows Task Scheduler, handle stop request in .NET for an application?


in .NET, when you program a windows service, you can handler external requests to stop so you can postpone and finish some processing instead of just killing the process outright.

Is there something similar when it comes to a Scheduled Task trying to stop a console application? The settings in Task Scheduler has the "If the running task does not stop when requested, force it to stop" option under the Settings tab. Which to me suggests it first requests the process to stop and gives it a chance before killing it. I did see something referencing the Task Scheduler killing an app if it doesn't complete after 3 minutes.

If there isn't built in functionality in .NET to handle the stop request, is there Windows API external features we can tap into such as Windows messages etc?


Solution

  • According to the description here: https://learn.microsoft.com/en-us/windows/win32/api/taskschd/nf-taskschd-itasksettings-get_allowhardterminate

    Gets or sets a Boolean value that indicates that the task may be terminated by the Task Scheduler service using TerminateProcess. The service will try to close the running task by sending the WM_CLOSE notification, and if the task does not respond, the task will be terminated only if this property is set to true.

    Task Scheduler will first send a WM_CLOSE message to the program's main window. If this does not work, and the task allows "hard terminate", the process will be terminated using TerminateProcess.

    For console programs, Task Scheduler will try to close their console windows. By default, console program will be terminated immediately when its console window is closed, or when Ctrl+C or Ctrl+Break is pressed. If you want to handle those events, you can use SetConsoleCtrlHandler to add a handler function.

    .NET has Console.CancelKeyPress event which can handle Ctrl+C and Ctrl+Break presses as well, and it actually uses SetConsoleCtrlHandler under the hood. However, it only handles CTRL_C_EVENT and CTRL_BREAK_EVENT, so events such as closing the console window are still not handled.

    Here's my example that uses SetConsoleCtrlHandler to handle exiting events in a console application.

    using System;
    using System.Runtime.InteropServices;
    using System.Threading;
    
    namespace TestConsoleExit
    {
        internal class Program
        {
            enum CtrlType : int
            {
                CtrlCEvent,
                CtrlBreakEvent,
                CtrlCloseEvent,
                CtrlLogoffEvent,
                CtrlShutdownEvent
            }
            delegate bool HandlerRoutine(CtrlType dwCtrlType);
    
            [DllImport("kernel32")]
            static extern bool SetConsoleCtrlHandler(HandlerRoutine HandlerRoutine, bool Add);
    
            static void Main(string[] args)
            {
                SetConsoleCtrlHandler(ExitHandler, true);
                for (; ; )
                {
                    Console.ReadLine();
                }
            }
    
            static bool ExitHandler(CtrlType ctrlType)
            {
                Console.WriteLine();
                Console.WriteLine(ctrlType);
                if (ctrlType == CtrlType.CtrlCEvent || ctrlType == CtrlType.CtrlBreakEvent)
                {
                    // you can return true to cancel exiting when handling Ctrl+C and Ctrl+Break
                    return true;
                }
                else
                {
                    // you cannot cancel exiting in other events, but you can delay it for a few seconds
                    Thread.Sleep(3000);
                    return false;
                }
            }
        }
    }
    

    Note that Task Scheduler only gives your program about one second to finish processing WM_CLOSE or CTRL_CLOSE_EVENT. After one second, your program will be terminated.