Search code examples
c#windows-services

Setting recovery options on windows services


I've recently written a small class to help me change recovery options on a windows service (most of code I found online somewhere). The code creates a FailureAction for the first, second, and subsequent failures. Each Failure object contains a type (None, Restart, Reboot, RunCommand), and a Delay (int) in milliseconds. These objects are packaged inside a struct and passed into ChangeServiceConfig2 (WinAPI P/Invoke). However, when I actually right-click on a service on the console and go to the Recovery tab, you can only set the delay ("Restart server after" field) once for all failures (first, second and subsequent). When I set this programmatically, it takes the delay from the first FailureAction and ignores all others. Does anyone know why this is the case? Why do we have to pass in a delay value for all FailureAction objects when only the first one gets used? Am I misunderstanding something?

Also, setting dwResetPeriod/"Reset fail count after" doesn't seem to have any effect.

Code:

public class ServiceConfigurator
{
    private const int SERVICE_ALL_ACCESS = 0xF01FF;
    private const int SC_MANAGER_ALL_ACCESS = 0xF003F;
    private const int SERVICE_CONFIG_DESCRIPTION = 0x1;
    private const int SERVICE_CONFIG_FAILURE_ACTIONS = 0x2;
    private const int SERVICE_NO_CHANGE = -1;
    private const int ERROR_ACCESS_DENIED = 5;

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    private struct SERVICE_FAILURE_ACTIONS
    {
        public int dwResetPeriod;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string lpRebootMsg;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string lpCommand;
        public int cActions;
        public IntPtr lpsaActions;
    }

    [DllImport("advapi32.dll", EntryPoint = "ChangeServiceConfig2")]
    private static extern bool ChangeServiceFailureActions(IntPtr hService, int dwInfoLevel, [MarshalAs(UnmanagedType.Struct)] ref SERVICE_FAILURE_ACTIONS lpInfo);
    [DllImport("advapi32.dll", EntryPoint = "ChangeServiceConfig2")]
    private static extern bool ChangeServiceDescription(IntPtr hService, int dwInfoLevel, [MarshalAs(UnmanagedType.Struct)] ref SERVICE_DESCRIPTION lpInfo);

    [DllImport("kernel32.dll")]
    private static extern int GetLastError();

    private IntPtr _ServiceHandle;
    public IntPtr ServiceHandle { get { return _ServiceHandle; } }

    public ServiceConfigurator(ServiceController svcController)
    {
        this._ServiceHandle = svcController.ServiceHandle.DangerousGetHandle();
    }

    public void SetRecoveryOptions(FailureAction pFirstFailure, FailureAction pSecondFailure, FailureAction pSubsequentFailures, int pDaysToResetFailureCount = 0)
    {
        int NUM_ACTIONS = 3;
        int[] arrActions = new int[NUM_ACTIONS * 2];
        int index = 0;
        arrActions[index++] = (int)pFirstFailure.Type;
        arrActions[index++] = pFirstFailure.Delay;
        arrActions[index++] = (int)pSecondFailure.Type;
        arrActions[index++] = pSecondFailure.Delay;
        arrActions[index++] = (int)pSubsequentFailures.Type;
        arrActions[index++] = pSubsequentFailures.Delay;

        IntPtr tmpBuff = Marshal.AllocHGlobal(NUM_ACTIONS * 8);

        try
        {
            Marshal.Copy(arrActions, 0, tmpBuff, NUM_ACTIONS * 2);
            SERVICE_FAILURE_ACTIONS sfa = new SERVICE_FAILURE_ACTIONS();
            sfa.cActions = 3;
            sfa.dwResetPeriod = pDaysToResetFailureCount;
            sfa.lpCommand = null;
            sfa.lpRebootMsg = null;
            sfa.lpsaActions = new IntPtr(tmpBuff.ToInt32());

            bool success = ChangeServiceFailureActions(_ServiceHandle, SERVICE_CONFIG_FAILURE_ACTIONS, ref sfa);
            if(!success)
            {
                if(GetLastError() == ERROR_ACCESS_DENIED)
                    throw new Exception("Access denied while setting failure actions.");
                else
                    throw new Exception("Unknown error while setting failure actions.");
            }
        }
        finally
        {
            Marshal.FreeHGlobal(tmpBuff);
            tmpBuff = IntPtr.Zero;
        }
    }
}

Trevor


Solution

  • I have discovered that all flavors of win 7 have no ability to restart some services even when their restart upon failure is set to yes.

    I ended up writing a service to monitor start and stop status as well as (heuristics derived) responsiveness (eg hung not hung). Would you like that services code posted here?

    Edit: Added the code

    Ok, the following is the service code I referred to. Bear in mind:

    • This code is designed to point to "C:\appMon" for logging, and cpu util. threshold values.
    • This code is ugly in that it was cranked out while I was slammed with other priorities - as a result, it could be rewritten to handle any number of services, user defined log and cfg paths, etc.
    • The service it was written for is the notorious windows spooler (spoolsv.exe).

    To install the service, use installutil.exe from MS. Make sure the service runs as a "local system account" or it will not be able to start/stop services.

    appMon.cs:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Diagnostics;
    using System.ServiceProcess;
    using System.Text;
    using System.Timers;
    using System.Windows.Forms;
    using System.IO;
    using System.Net.Mail;
    using System.Threading;
    using System.Management;
    
    namespace appMon
    {
        public class appMon : ServiceBase
        {
            public const string serviceName = "appMon";     
            public appMon()
            {
                InitializeComponent();          
            }       
            private void InitializeComponent()
            {
                this.ServiceName = serviceName;         
            }       
            /// <summary>
            /// Clean up any resources being used.
            /// </summary>
            protected override void Dispose(bool disposing)
            {
                // free instantiated object resources not handled by garbage collector
                base.Dispose(disposing);
            }
            public static string getCurrUser()
            {// gets the owner of explorer.exe/UI to determine current logged in user
                String User = String.Empty;
                String Domain = String.Empty;
                String OwnerSID = String.Empty;
                string processname = String.Empty;
                int PID = Process.GetProcessesByName("explorer")[0].Id;
                ObjectQuery sq = new ObjectQuery
                    ("Select * from Win32_Process Where ProcessID = '" + PID + "'");
                ManagementObjectSearcher searcher = new ManagementObjectSearcher(sq);            
                foreach (ManagementObject oReturn in searcher.Get())
                {
                    string[] o = new String[2];
                    oReturn.InvokeMethod("GetOwner", (object[])o);
                    User = o[0];
                    System.IO.StreamWriter sr = new System.IO.StreamWriter(@"C:\user.txt");
                    sr.WriteLine("\\" + o[2] + "\\" + o[1] + "\\" + o[0]);
                    return User;
                }
                return User;
            }
            public static int readConfigFile()
            {
                int cputime = 5; // 5 min dflt
                try
                {
                    string readcfg;
                    readcfg = File.ReadAllText(@"c:\appMon\cpuUtilization.txt");
                    cputime = Convert.ToInt16(readcfg);
                    return cputime;
                }
                catch (Exception e)
                {
                    MessageBox.Show(e.ToString());
                    return cputime;  // 5 min dflt
                }
            }
            public static void logEvents(bool spoolerHang, bool appHang, string msg)
            {
                try
                {
                    StreamWriter sw;
                    sw = File.AppendText(@"c:\appMon\appMonLog.txt");
                    sw.WriteLine(@"appMon spoolsv.exe event: " + "," + System.Environment.MachineName + "," + System.DateTime.Now + "," + msg);
                    sw.Close();
                }
                catch (Exception e)
                {
                    MessageBox.Show(e.ToString());
                }
            }
            /// <summary>
            /// Start this service.
            /// </summary> 
            protected override void OnStart(string[] args)
            {// upon appMon load, a polling interval is set (in milliseconds 1000 ms = 1 s)
                System.Timers.Timer pollTimer = new System.Timers.Timer();
                pollTimer.Elapsed += new ElapsedEventHandler(pollTimer_Elapsed);
                pollTimer.Interval = 20000; // 20 sec
                pollTimer.Enabled = true;
            }
            public static void StartService(string serviceName, int timeoutMilliseconds)
            {
                ServiceController service = new ServiceController(serviceName);
                try
                {
                    TimeSpan timeout = TimeSpan.FromMilliseconds(timeoutMilliseconds);
                    if (service.Status == ServiceControllerStatus.Stopped) // if service is not running...
                    {
                        service.Start(); // ...start the service
                    }
                }
                catch(Exception e)
                {
                    MessageBox.Show(e.ToString());
                }
            }
            public static void StopService(string serviceName, int timeoutMilliseconds)
            {
                ServiceController service = new ServiceController(serviceName);
                try
                {
                    TimeSpan timeout = TimeSpan.FromMilliseconds(timeoutMilliseconds);
                    if (service.Status == ServiceControllerStatus.Running) // if service is running...
                    {
                        service.Stop(); //...stop the service
                    }
                    service.WaitForStatus(ServiceControllerStatus.Stopped, timeout);
                }
                catch (Exception e)
                {
                    MessageBox.Show(e.ToString());
                }
            }
            public static void delFiles(string path)
            {
                string[] filePaths = Directory.GetFiles(path);
                foreach (string filePath in filePaths)
                {
                    try
                    {
                        File.Delete(filePath);
                    }
                    catch (Exception e)
                    {
                        // TODO:  !log to file instead!
                        MessageBox.Show("error deleting files: " + e.ToString());
                    }
                }
            }
            public static void getServiceInfo(string serviceName)
            {
                ServiceController service = new ServiceController(serviceName);
                ServiceController[] depServices = service.ServicesDependedOn;
                List<string> depServicesList = new List<string>();
                foreach (ServiceController sc in depServices)
                {
                    depServicesList.Add(sc.ServicesDependedOn.ToString());
                    logEvents(false, false, sc.ServicesDependedOn.ToString());
                }
    
            }
            void pollTimer_Elapsed(object sender, ElapsedEventArgs e)
            {// polling interval has elapsed            
                getServiceInfo("spooler");
                ServiceController serviceSpooler = new ServiceController("spooler");
                if (serviceSpooler.Status == ServiceControllerStatus.Stopped)
                {
                    logEvents(true, false, "Print Spooler is: " + serviceSpooler.Status.ToString());
                    serviceSpooler.Refresh();
                    serviceSpooler.Start();
                    logEvents(true, false, "Print Spooler is: " + serviceSpooler.Status.ToString());
                }            
                int cputime = readConfigFile();
                // get active processes (exe's, including services)
                Process[] processlist = Process.GetProcesses();
                // iterate through process list
                foreach (Process theprocess in processlist)
                {
                    // assign local variable to iterator - cures the foreach "gotcha"
                    Process p = theprocess;
                    if (p.ProcessName == "spoolsv") // "spoolsv" = windows name for spoolsv.exe aka "spooler"
                    {
                        if (p.TotalProcessorTime.Minutes > cputime) // has current spooler thread occupied >= cputime # mins of CPU time? 
                        {
                            logEvents(true, false, "spoolsv.exe CPU time (mins): " + p.TotalProcessorTime.Minutes.ToString());
                            p.Refresh();
                            StopService("spooler", 0);
                            StartService("spooler", 0);
                        }
                    }
                }
            }
            /// <summary>
            /// Stop this service.
            /// </summary>
            /// 
            protected override void OnStop()
            {
            }
        }
    }