如何延迟关闭并在窗口服务中运行进程

时间:2011-03-07 08:02:02

标签: c# .net winforms windows-services

我必须运行一个进程,即Windows关闭时的应用程序,是否有任何方法可以延迟Windows关闭并在Windows服务中运行应用程序......

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();
}

我使用上面的函数覆盖了关闭,我能够写一个文本文件的日志条目,但我之后无法运行应用程序在搜索时我发现延迟低于用户后的几秒钟火灾关闭

  

this.RequestAdditionalTime(250000);

这会在关闭事件时给出25秒的添加时间延迟,但是我无法运行该应用程序。任何人都可以建议在关机时运行应用程序的方法或想法。

5 个答案:

答案 0 :(得分:11)

Windows Vista严重限制了应用程序阻止挂起的系统关闭的能力。详细信息在MSDN上的两篇方便文章中进行了总结:Shutdown Changes for Windows VistaApplication Shutdown Changes in Windows Vista

如该页面所示,您不应该依赖于阻止关机超过5秒的能力。如果您希望尝试阻止挂起的关闭事件,您的应用程序应该使用新的ShutdownBlockReasonCreate function,它允许您注册一个字符串,向用户解释您认为应该阻止关闭的原因。用户保留注意您的建议并取消关机的能力,或者保持警惕并取消。

一旦你的应用程序完成做任何不应该被关闭中断的事情,你应该调用相应的ShutdownBlockReasonDestroy function,它释放原因字符串并指示系统现在可以关闭了。

另请注意Windows Services now run in an isolated session并禁止与用户互动。 My answer here还提供了更多详细信息以及漂亮的图表。

基本上,这是不可能的。 Windows将为您在服务中启动一个单独的进程以及阻止挂起关闭的任何尝试而斗争。最终,用户可以覆盖您尝试拉动的任何内容。这听起来像是您应该使用安全策略解决的问题,而不是Server Fault上有关该问题的应用问题。

答案 1 :(得分:4)

在Windows Vista SP1及更高版本上,新的SERVICE_CONTROL_PRESHUTDOWN可用。不幸的是,.NET框架尚不支持它,但这里是使用反射的解决方法。只需从ServicePreshutdownBase继承您的服务类,覆盖OnStop并定期调用RequestAdditionalTime()。请注意,CanShutdown应设置为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;
            }
        }
    }
}

以下是示例用法:

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");
    }
}

这将工作3分20秒,如果您需要更多时间,那么您需要配置服务。这样做的最佳位置是安装期间。只需使用ServicePreshutdownInstaller代替ServiceInstaller,并将PreshutdownTimeout设置为您需要的最长时间。

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);
}

答案 2 :(得分:1)

我有一个similar problem,并且有一个技巧可能适用于你的情况。在使用CREATE_SUSPENDED标志启动关闭之前,您可以启动问题中的应用程序(请参阅this)。这将确保创建流程,但永远不会运行。在关闭时,您可以ResumeThread进行处理,并继续执行。

请注意,该进程可能无法初始化和运行,因为在关闭期间某些OS功能将失败。

另一个含义是:应该在关闭时运行的进程将显示在任务管理器中。有可能杀死这个过程。

答案 3 :(得分:0)

这是关机事件跟踪器上的article。您可以在Windows XP中激活它。它会提示用户输入关闭的原因。

答案 4 :(得分:0)

命名空间WindowsService1 {     [StructLayout(LayoutKind.Sequential)]     public struct SERVICE_STATUS     {         public int serviceType;         public int currentState;         public int controlsAccepted;         public int win32ExitCode;         public int serviceSpecificExitCode;         public int checkPoint;         public int waitHint;     }

public enum SERVICE_STATE : uint
{
    SERVICE_STOPPED = 0x00000001,
    SERVICE_START_PENDING = 0x00000002,
    SERVICE_STOP_PENDING = 0x00000003,
    SERVICE_RUNNING = 0x00000004,
    SERVICE_CONTINUE_PENDING = 0x00000005,
    SERVICE_PAUSE_PENDING = 0x00000006,
    SERVICE_PAUSED = 0x00000007
}

public enum ControlsAccepted
{
    ACCEPT_STOP = 1,
    ACCEPT_PAUSE_CONTINUE = 2,
    ACCEPT_SHUTDOWN = 4,
    ACCEPT_PRESHUTDOWN = 0xf,
    ACCEPT_POWER_EVENT = 64,
    ACCEPT_SESSION_CHANGE = 128
}

[Flags]
public enum SERVICE_CONTROL : uint
{
    STOP = 0x00000001,
    PAUSE = 0x00000002,
    CONTINUE = 0x00000003,
    INTERROGATE = 0x00000004,
    SHUTDOWN = 0x00000005,
    PARAMCHANGE = 0x00000006,
    NETBINDADD = 0x00000007,
    NETBINDREMOVE = 0x00000008,
    NETBINDENABLE = 0x00000009,
    NETBINDDISABLE = 0x0000000A,
    DEVICEEVENT = 0x0000000B,
    HARDWAREPROFILECHANGE = 0x0000000C,
    POWEREVENT = 0x0000000D,
    SESSIONCHANGE = 0x0000000E
}

public enum INFO_LEVEL : uint
{
    SERVICE_CONFIG_DESCRIPTION = 0x00000001,
    SERVICE_CONFIG_FAILURE_ACTIONS = 0x00000002,
    SERVICE_CONFIG_DELAYED_AUTO_START_INFO = 0x00000003,
    SERVICE_CONFIG_FAILURE_ACTIONS_FLAG = 0x00000004,
    SERVICE_CONFIG_SERVICE_SID_INFO = 0x00000005,
    SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO = 0x00000006,
    SERVICE_CONFIG_PRESHUTDOWN_INFO = 0x00000007,
    SERVICE_CONFIG_TRIGGER_INFO = 0x00000008,
    SERVICE_CONFIG_PREFERRED_NODE = 0x00000009
}

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

[Flags]
public enum SERVICE_ACCESS : uint
{
    STANDARD_RIGHTS_REQUIRED = 0xF0000,
    SERVICE_QUERY_CONFIG = 0x00001,
    SERVICE_CHANGE_CONFIG = 0x00002,
    SERVICE_QUERY_STATUS = 0x00004,
    SERVICE_ENUMERATE_DEPENDENTS = 0x00008,
    SERVICE_START = 0x00010,
    SERVICE_STOP = 0x00020,
    SERVICE_PAUSE_CONTINUE = 0x00040,
    SERVICE_INTERROGATE = 0x00080,
    SERVICE_USER_DEFINED_CONTROL = 0x00100,
    SERVICE_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED |
      SERVICE_QUERY_CONFIG |
      SERVICE_CHANGE_CONFIG |
      SERVICE_QUERY_STATUS |
      SERVICE_ENUMERATE_DEPENDENTS |
      SERVICE_START |
      SERVICE_STOP |
      SERVICE_PAUSE_CONTINUE |
      SERVICE_INTERROGATE |
      SERVICE_USER_DEFINED_CONTROL)
}

[Flags]
public enum SCM_ACCESS : uint
{
    STANDARD_RIGHTS_REQUIRED = 0xF0000,
    SC_MANAGER_CONNECT = 0x00001,
    SC_MANAGER_CREATE_SERVICE = 0x00002,
    SC_MANAGER_ENUMERATE_SERVICE = 0x00004,
    SC_MANAGER_LOCK = 0x00008,
    SC_MANAGER_QUERY_LOCK_STATUS = 0x00010,
    SC_MANAGER_MODIFY_BOOT_CONFIG = 0x00020,
    SC_MANAGER_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED |
      SC_MANAGER_CONNECT |
      SC_MANAGER_CREATE_SERVICE |
      SC_MANAGER_ENUMERATE_SERVICE |
      SC_MANAGER_LOCK |
      SC_MANAGER_QUERY_LOCK_STATUS |
      SC_MANAGER_MODIFY_BOOT_CONFIG
}

public partial class Service1 : ServiceBase
{        
    [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    internal static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess);

    [DllImport("advapi32.dll")]
    internal static extern bool SetServiceStatus(IntPtr hServiceStatus, ref SERVICE_STATUS lpServiceStatus);

    [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool ChangeServiceConfig2(IntPtr hService, int dwInfoLevel, IntPtr lpInfo);

    [DllImport("advapi32.dll", EntryPoint = "OpenSCManagerW", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern IntPtr OpenSCManager(string machineName, string databaseName, uint dwAccess);

    const int SERVICE_ACCEPT_PRESHUTDOWN = 0x100;
    const int SERVICE_CONTROL_PRESHUTDOWN = 0xf;

    public Service1()
    {
        InitializeComponent();
        CanShutdown = true;
        tim = new Timer();
        tim.Interval = 5000;
        tim.Elapsed += tim_Elapsed;
        FieldInfo acceptedCommandsFieldInfo = typeof(ServiceBase).GetField("acceptedCommands", BindingFlags.Instance | BindingFlags.NonPublic);
        int value = (int)acceptedCommandsFieldInfo.GetValue(this);
        acceptedCommandsFieldInfo.SetValue(this, value | SERVICE_ACCEPT_PRESHUTDOWN);
        StreamWriter writer = new StreamWriter("D:\\LogConst.txt", true);
        try
        {
            IntPtr hMngr = OpenSCManager("localhost", null, (uint)SCM_ACCESS.SC_MANAGER_ALL_ACCESS);
            IntPtr hSvc = OpenService(hMngr, "WindowsService1", (uint)SCM_ACCESS.SC_MANAGER_ALL_ACCESS);
            SERVICE_PRESHUTDOWN_INFO spi = new SERVICE_PRESHUTDOWN_INFO();
            spi.dwPreshutdownTimeout = 5000;

            IntPtr lpInfo = Marshal.AllocHGlobal(Marshal.SizeOf(spi));
            if (lpInfo == IntPtr.Zero)
            {
                writer.WriteLine(String.Format("Unable to allocate memory for service action, error was: 0x{0:X} -- {1}", Marshal.GetLastWin32Error(), DateTime.Now.ToLongTimeString()));
            }
            Marshal.StructureToPtr(spi, lpInfo, false);
            // apply the new timeout value
            if (!ChangeServiceConfig2(hSvc, (int)INFO_LEVEL.SERVICE_CONFIG_PRESHUTDOWN_INFO, lpInfo))
                writer.WriteLine(DateTime.Now.ToLongTimeString() + " Failed to change service timeout");
            else
                writer.WriteLine(DateTime.Now.ToLongTimeString() + " change service timeout : " + spi.dwPreshutdownTimeout);
        }
        catch (Exception ex)
        {
            writer.WriteLine(DateTime.Now.ToLongTimeString() + " " + ex.Message);
        }
        writer.Close();
    }

    void tim_Elapsed(object sender, ElapsedEventArgs e)
    {
        result = false;
        StreamWriter writer = new StreamWriter("D:\\hede.txt", true);
        writer.WriteLine(DateTime.Now.ToLongTimeString());
        //System.Threading.Thread.Sleep(5000);
        writer.Close();
        result = true;
        tim.Stop();
    }

    Timer tim;
    bool result = false;

    protected override void OnStart(string[] args)
    {
        RequestAdditionalTime(1000);
        tim.Start();
    }

    protected override void OnStop()
    {
    }

    protected override void OnCustomCommand(int command)
    {
        StreamWriter writer = new StreamWriter("D:\\Log.txt", true);
        try
        {
            if (command == SERVICE_CONTROL_PRESHUTDOWN)
            {
                int checkpoint = 1;
                writer.WriteLine(DateTime.Now.ToLongTimeString());
                while (!result)
                {
                    SERVICE_STATUS myServiceStatus = new SERVICE_STATUS();
                    myServiceStatus.currentState = (int)SERVICE_STATE.SERVICE_STOP_PENDING;

                    myServiceStatus.serviceType = 16;
                    myServiceStatus.serviceSpecificExitCode = 0;
                    myServiceStatus.checkPoint = checkpoint;
                    SetServiceStatus(this.ServiceHandle, ref myServiceStatus);
                    checkpoint++;
                }
                writer.WriteLine(DateTime.Now.ToLongTimeString());
            }
        }
        catch (Exception ex)
        {
            writer.WriteLine(DateTime.Now.ToLongTimeString() + " " + ex.Message);
        }
        writer.Close();
        base.OnCustomCommand(command);
    }
}

}