how to delay shutdown and run a process in window service

I have to run a process ie a application on windows shutdown, is there any method to delay the windows shutdown and run the application in windows service...

protected override void OnShutdown()
{
    // Add your save code here
    // Add your save code here
    StreamWriter str = new StreamWriter("D:\\Log.txt", true);
    str.WriteLine("Service stoped due to on" + DateTime.Now.ToString());
    str.Close();

    base.OnShutdown();
}

I have used function above which overrides the shutdown and i was able to write a log entry to a text file but i was not able to run an application after that On searching i found that the delay was below only some seconds after user fires shutdown

this.RequestAdditionalTime(250000);

this gives an addition time delay of 25 seconds on shutdown event but i was not able to run the application. Can anyone suggest method or ideas to run application on shutdown.


Solution 1:

The ability of applications to block a pending system shutdown was severely restricted in Windows Vista. The details are summarized in two handy articles on MSDN: Shutdown Changes for Windows Vista and Application Shutdown Changes in Windows Vista.

As that page indicates, you shouldn't rely on the ability to block shutdown for any longer than 5 seconds. If you wish to attempt to block a pending shutdown event, your application should use the new ShutdownBlockReasonCreate function, which allows you to register a string that explains to the user the reason why you think the shutdown should be blocked. The user reserves the ability to heed your advice and cancel the shutdown, or throw caution to the wind and cancel anyway.

As soon as your application finishes doing whatever it is that should not be interrupted by a shutdown, you should call the corresponding ShutdownBlockReasonDestroy function, which frees the reason string and indicates that the system can now be shut down.

Also remember that Windows Services now run in an isolated session and are prohibited from interacting with the user. My answer here also provides more details, as well as a pretty diagram.

Basically, this is impossible. Windows is going to fight you tooth and nail over starting up a separate process from your Service, as well as any attempt you make to block a pending shutdown. Ultimately, the user has the power to override anything you try to pull. This sounds like something you should solve using security policies, rather than an application—ask questions about that on Server Fault.

Solution 2:

On Windows Vista SP1 and higher, the new SERVICE_CONTROL_PRESHUTDOWN is available. Unfortunately it is not supported by .NET framework yet, but here is workaround using reflection. Just inherit your service class from ServicePreshutdownBase, override OnStop and periodically call RequestAdditionalTime(). Note that CanShutdown should be set to false.

public class ServicePreshutdownBase : ServiceBase
{
    public bool Preshutdown { get; private set; }

    public ServicePreshutdownBase()
    {
        Version versionWinVistaSp1 = new Version(6, 0, 6001);
        if (Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version >= versionWinVistaSp1)
        {
            var acceptedCommandsField = typeof (ServiceBase).GetField("acceptedCommands", BindingFlags.Instance | BindingFlags.NonPublic);
            if (acceptedCommandsField == null)
                throw new InvalidOperationException("Private field acceptedCommands not found on ServiceBase");

            int acceptedCommands = (int) acceptedCommandsField.GetValue(this);
            acceptedCommands |= 0x00000100; //SERVICE_ACCEPT_PRESHUTDOWN;
            acceptedCommandsField.SetValue(this, acceptedCommands);
        }
    }

    protected override void OnCustomCommand(int command)
    {
        // command is SERVICE_CONTROL_PRESHUTDOWN
        if (command == 0x0000000F)
        {
            var baseCallback = typeof(ServiceBase).GetMethod("ServiceCommandCallback", BindingFlags.Instance | BindingFlags.NonPublic);
            if (baseCallback == null)
                throw new InvalidOperationException("Private method ServiceCommandCallback not found on ServiceBase");
            try
            {
                Preshutdown = true;
                //now pretend stop was called 0x00000001
                baseCallback.Invoke(this, new object[] {0x00000001});
            }
            finally
            {
                Preshutdown = false;
            }
        }
    }
}

Here is example usage:

public partial class Service1 : ServicePreshutdownBase
{
    public Service1()
    {
        InitializeComponent();
        this.CanShutdown = false;
    }
    protected override void OnStop()
    {
        WriteLog(Preshutdown ? "Service OnPreshutdown" : "Service OnStop");
        for (int i = 0; i < 180; i++)
        {
            Thread.Sleep(1000);
            WriteLog("Service stop in progress...");
            RequestAdditionalTime(2000);
        }
        WriteLog(Preshutdown ? "Service preshutdown completed" : "Service stop completed");
    }
}

This will work for 3min 20s, if you need more time, then you need to configure service. The best place to do so is during installation. Just use the ServicePreshutdownInstaller instead of ServiceInstaller and set the PreshutdownTimeout to maximum time you will ever need.

public class ServicePreshutdownInstaller : ServiceInstaller
{
    private int _preshutdownTimeout = 200000;

    /// <summary>
    /// Gets or sets the preshutdown timeout for the service.
    /// </summary>
    /// 
    /// <returns>
    /// The preshutdown timeout of the service. The default is 200000ms (200s).
    /// </returns>
    [DefaultValue(200000)]
    [ServiceProcessDescription("ServiceInstallerPreshutdownTimeout")]
    public int PreshutdownTimeout
    {
        get
        {
            return _preshutdownTimeout;
        }
        set
        {
            _preshutdownTimeout = value;
        }
    }

    public override void Install(System.Collections.IDictionary stateSaver)
    {
        base.Install(stateSaver);

        Version versionWinVistaSp1 = new Version(6, 0, 6001);
        if (Environment.OSVersion.Platform != PlatformID.Win32NT || Environment.OSVersion.Version < versionWinVistaSp1)
        {
            //Preshutdown is not supported
            return;
        }

        Context.LogMessage(string.Format("Setting preshutdown timeout {0}ms to service {1}", PreshutdownTimeout, ServiceName));
        IntPtr service = IntPtr.Zero;
        IntPtr sCManager = IntPtr.Zero;
        try
        {
            // Open the service control manager
            sCManager = OpenSCManager(null, null, ServiceControlAccessRights.SC_MANAGER_CONNECT);
            if (sCManager == IntPtr.Zero)
                throw new Win32Exception(Marshal.GetLastWin32Error(), "Unable to open Service Control Manager.");
            // Open the service
            service = OpenService(sCManager, ServiceName, ServiceAccessRights.SERVICE_CHANGE_CONFIG);
            if (service == IntPtr.Zero) throw new Win32Exception();
            // Set up the preshutdown timeout structure
            SERVICE_PRESHUTDOWN_INFO preshutdownInfo = new SERVICE_PRESHUTDOWN_INFO();
            preshutdownInfo.dwPreshutdownTimeout = (uint)_preshutdownTimeout;
            // Make the change
            int changeResult = ChangeServiceConfig2(
                service,
                ServiceConfig2InfoLevel.SERVICE_CONFIG_PRESHUTDOWN_INFO,
                ref preshutdownInfo);
            // Check that the change occurred
            if (changeResult == 0)
            {
                throw new Win32Exception(Marshal.GetLastWin32Error(), "Unable to change the Service configuration.");
            }

            Context.LogMessage(string.Format("Preshutdown timeout {0}ms set to service {1}", PreshutdownTimeout, ServiceName));
        }
        finally
        {
            // Clean up
            if (service != IntPtr.Zero)CloseServiceHandle(service);
            if (sCManager != IntPtr.Zero)Marshal.FreeHGlobal(sCManager);
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct SERVICE_PRESHUTDOWN_INFO
    {
        public UInt32 dwPreshutdownTimeout;
    }

    [Flags]
    public enum ServiceControlAccessRights : int
    {
        SC_MANAGER_CONNECT = 0x0001, // Required to connect to the service control manager. 
        SC_MANAGER_CREATE_SERVICE = 0x0002, // Required to call the CreateService function to create a service object and add it to the database. 
        SC_MANAGER_ENUMERATE_SERVICE = 0x0004, // Required to call the EnumServicesStatusEx function to list the services that are in the database. 
        SC_MANAGER_LOCK = 0x0008, // Required to call the LockServiceDatabase function to acquire a lock on the database. 
        SC_MANAGER_QUERY_LOCK_STATUS = 0x0010, // Required to call the QueryServiceLockStatus function to retrieve the lock status information for the database
        SC_MANAGER_MODIFY_BOOT_CONFIG = 0x0020, // Required to call the NotifyBootConfigStatus function. 
        SC_MANAGER_ALL_ACCESS = 0xF003F // Includes STANDARD_RIGHTS_REQUIRED, in addition to all access rights in this table. 
    }

    [Flags]
    public enum ServiceAccessRights : int
    {
        SERVICE_QUERY_CONFIG = 0x0001, // Required to call the QueryServiceConfig and QueryServiceConfig2 functions to query the service configuration. 
        SERVICE_CHANGE_CONFIG = 0x0002, // Required to call the ChangeServiceConfig or ChangeServiceConfig2 function to change the service configuration. Because this grants the caller the right to change the executable file that the system runs, it should be granted only to administrators. 
        SERVICE_QUERY_STATUS = 0x0004, // Required to call the QueryServiceStatusEx function to ask the service control manager about the status of the service. 
        SERVICE_ENUMERATE_DEPENDENTS = 0x0008, // Required to call the EnumDependentServices function to enumerate all the services dependent on the service. 
        SERVICE_START = 0x0010, // Required to call the StartService function to start the service. 
        SERVICE_STOP = 0x0020, // Required to call the ControlService function to stop the service. 
        SERVICE_PAUSE_CONTINUE = 0x0040, // Required to call the ControlService function to pause or continue the service. 
        SERVICE_INTERROGATE = 0x0080, // Required to call the ControlService function to ask the service to report its status immediately. 
        SERVICE_USER_DEFINED_CONTROL = 0x0100, // Required to call the ControlService function to specify a user-defined control code.
        SERVICE_ALL_ACCESS = 0xF01FF // Includes STANDARD_RIGHTS_REQUIRED in addition to all access rights in this table. 
    }

    public enum ServiceConfig2InfoLevel : int
    {
        SERVICE_CONFIG_DESCRIPTION = 0x00000001, // The lpBuffer parameter is a pointer to a SERVICE_DESCRIPTION structure.
        SERVICE_CONFIG_FAILURE_ACTIONS = 0x00000002, // The lpBuffer parameter is a pointer to a SERVICE_FAILURE_ACTIONS structure.
        SERVICE_CONFIG_PRESHUTDOWN_INFO = 0x00000007 // The lpBuffer parameter is a pointer to a SERVICE_PRESHUTDOWN_INFO structure.
    }

    [DllImport("advapi32.dll", EntryPoint = "OpenSCManager")]
    public static extern IntPtr OpenSCManager(
        string machineName,
        string databaseName,
        ServiceControlAccessRights desiredAccess);

    [DllImport("advapi32.dll", EntryPoint = "CloseServiceHandle")]
    public static extern int CloseServiceHandle(IntPtr hSCObject);

    [DllImport("advapi32.dll", EntryPoint = "OpenService")]
    public static extern IntPtr OpenService(
        IntPtr hSCManager,
        string serviceName,
        ServiceAccessRights desiredAccess);

    [DllImport("advapi32.dll", EntryPoint = "ChangeServiceConfig2")]
    public static extern int ChangeServiceConfig2(
        IntPtr hService,
        ServiceConfig2InfoLevel dwInfoLevel,
        ref SERVICE_PRESHUTDOWN_INFO lpInfo);
}

Solution 3:

I have a similar problem, and there is one trick that might work in your case. You can start application in question before shutdown is initiated with CREATE_SUSPENDED flag (see this). This will ensure that the process will be created, but never run. On shutdown you can ResumeThread that process, and it will go on with execution.

Note, that it might be possible that the process will not be able to initialize and run anyway, since during shutdown some OS functions will fail.

Another implication is: the process which is supposed to run on shutdown will show in task manager. It would be possible to kill that process.