5
votes

I have WCF service that is hosted on windows service. I installed this service using Windows installer. Sometimes, when i stop service using C# code, it stucks on stopping. So i thought, why not kill service if service is not stopping within 2 minutes. My code is below to stop service:

var service = ServiceController.GetServices()
                .FirstOrDefault(s => s.ServiceName == serviceName);
            try
            {
                if (service == null || service.Status != ServiceControllerStatus.Running) return;
                if(service.CanStop)
                {
                    session.LogInfo($"Stopping '{serviceName}'.");
                    TimeSpan timeout = TimeSpan.FromMilliseconds(ServiceStopTime);
                    service.Stop();
                    service.WaitForStatus(ServiceControllerStatus.Stopped, timeout);
                    session.LogInfo($"'{serviceName}' stopped successfully.");
                }

It is working as expected. I want to kill my process if service does not stop. Here is my code to kill process.

var processName = GetProcessNameByWindowsService(serviceName);
            if (processName == null) return;
            Process[] procs = Process.GetProcessesByName(processName);
            if (procs.Length > 0)
            {
                foreach (Process proc in procs)
                {
                    session.LogInfo($"Killing Process'{processName}'.");
                    proc.Kill();
                    session.LogInfo($"'{processName}' killed successfully.");
                }
            }

It is working as expected too but the problem is when i kill the process, the service does not stop. It assigns new process to service and service keep runs. After googled and investing some time i found the cause that is the window service recovery option which is restart the service if it fails. I want to change/set the recovery option for service in case of first failure, second failure and subsequent failure to take no action using C# code. I googled but did not find anything. So i want to know how i can change the recovery option of installed window service using C#?

1
Why not spend time fixing whatever issues there are with about it not being able to shut down cleanly in 30 seconds instead?Damien_The_Unbeliever
You are right. But unfortunately the service code is not in my hand. I have asked to do this. I suggested exactly what you have said but i asked to kill process if not stopped within 2 minutes.umer
So, if an administrator is stopping this service themselves for some reason, the service might also go into this unresponsive state? Or during windows shutdown? The service as described is broken. It sounds like you're working with someone unable to accept this basic fact.Damien_The_Unbeliever
Agreed. I explained him but client wants to do everything programmaticallyumer

1 Answers

10
votes

After investing time finally i have found the solution with the help of this link. I have written two helper classes to set/update recovery option of windows service. First of all i wrote a static helper class which is below:

using System;
using System.Runtime.InteropServices;

namespace HRTC.CustomActions.Helpers
{
    public static class ServiceRecoveryOptionHelper
    {
        //Action Enum
        public enum RecoverAction
        {
            None = 0, Restart = 1, Reboot = 2, RunCommand = 3
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]

        public struct ServiceFailureActions
        {
            public int dwResetPeriod;
            [MarshalAs(UnmanagedType.LPWStr)]

            public string lpRebootMsg;
            [MarshalAs(UnmanagedType.LPWStr)]

            public string lpCommand;
            public int cActions;
            public IntPtr lpsaActions;
        }

        [StructLayout(LayoutKind.Sequential)]
        public class ScAction
        {
            public int type;
            public uint dwDelay;
        }

        // Win32 function to open the service control manager
        [DllImport("advapi32.dll")]
        public static extern IntPtr OpenSCManager(string lpMachineName, string lpDatabaseName, int dwDesiredAccess);

        // Win32 function to open a service instance
        [DllImport("advapi32.dll")]
        public static extern IntPtr OpenService(IntPtr hScManager, string lpServiceName, int dwDesiredAccess);

        // Win32 function to change the service config for the failure actions.
        [DllImport("advapi32.dll", EntryPoint = "ChangeServiceConfig2")]

        public static extern bool ChangeServiceFailureActions(IntPtr hService, int dwInfoLevel,
            [MarshalAs(UnmanagedType.Struct)]
            ref ServiceFailureActions lpInfo);

        [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true, EntryPoint = "QueryServiceConfig2W")]
        public static extern Boolean QueryServiceConfig2(IntPtr hService, UInt32 dwInfoLevel, IntPtr buffer, UInt32 cbBufSize, out UInt32 pcbBytesNeeded);

        [DllImport("kernel32.dll")]
        public static extern int GetLastError();
    }
    public class FailureAction
    {
        // Default constructor
        public FailureAction() { }

        // Constructor
        public FailureAction(ServiceRecoveryOptionHelper.RecoverAction actionType, int actionDelay)
        {
            Type = actionType;
            Delay = actionDelay;
        }

        // Property to set recover action type
        public ServiceRecoveryOptionHelper.RecoverAction Type { get; set; } = ServiceRecoveryOptionHelper.RecoverAction.None;

        // Property to set recover action delay
        public int Delay { get; set; }
    }
}

Then i already have static class for windows services that have different methods like to start windows service, stop windows service and install service etc. I added new static method in this class to change recovery option of windows service which receive 4 parameters. First one is the service name, and other three are the recovery options of first,second and subsequent recovery options respectively. Below is it's implementation.

using System;
using System.Collections;
using System.Runtime.InteropServices;

namespace HRTC.CustomActions.Helpers
{
    public class LocalServiceHelper
    {
        //Change service recovery option settings
        private const int ServiceAllAccess = 0xF01FF;
        private const int ScManagerAllAccess = 0xF003F;
        private const int ServiceConfigFailureActions = 0x2;
        private const int ErrorAccessDenied = 5;


        public static void ChangeRevoveryOption(string serviceName, ServiceRecoveryOptionHelper.RecoverAction firstFailureAction,
            ServiceRecoveryOptionHelper.RecoverAction secondFailureAction, ServiceRecoveryOptionHelper.RecoverAction thirdFailureAction)
        {
            try
            {
                // Open the service control manager
                var scmHndl = ServiceRecoveryOptionHelper.OpenSCManager(null, null, ScManagerAllAccess);
                if (scmHndl.ToInt32() <= 0)
                    return;

                // Open the service
                var svcHndl = ServiceRecoveryOptionHelper.OpenService(scmHndl, serviceName, ServiceAllAccess);

                if (svcHndl.ToInt32() <= 0)
                    return;

                var failureActions = new ArrayList
                {
                    // First Failure Actions and Delay (msec)
                    new FailureAction(firstFailureAction, 0),
                    // Second Failure Actions and Delay (msec)
                    new FailureAction(secondFailureAction, 0),
                    // Subsequent Failures Actions and Delay (msec)
                    new FailureAction(thirdFailureAction, 0)
                };

                var numActions = failureActions.Count;
                var myActions = new int[numActions * 2];
                var currInd = 0;

                foreach (FailureAction fa in failureActions)
                {
                    myActions[currInd] = (int) fa.Type;
                    myActions[++currInd] = fa.Delay;
                    currInd++;
                }

                // Need to pack 8 bytes per struct
                var tmpBuf = Marshal.AllocHGlobal(numActions * 8);

                // Move array into marshallable pointer
                Marshal.Copy(myActions, 0, tmpBuf, numActions * 2);

                // Set the SERVICE_FAILURE_ACTIONS struct
                var config =
                    new ServiceRecoveryOptionHelper.ServiceFailureActions
                    {
                        cActions = 3,
                        dwResetPeriod = 0,
                        lpCommand = null,
                        lpRebootMsg = null,
                        lpsaActions = new IntPtr(tmpBuf.ToInt32())
                    };

                // Call the ChangeServiceFailureActions() abstraction of ChangeServiceConfig2()
                var result =
                    ServiceRecoveryOptionHelper.ChangeServiceFailureActions(svcHndl, ServiceConfigFailureActions,
                        ref config);

                //Check the return
                if (!result)
                {
                    var err = ServiceRecoveryOptionHelper.GetLastError();
                    if (err == ErrorAccessDenied)
                    {
                        throw new Exception("Access Denied while setting Failure Actions");

                    }

                    // Free the memory
                    Marshal.FreeHGlobal(tmpBuf);
                }
            }
            catch (Exception)
            {
                throw new Exception("Unable to set service recovery options");
            }
        }
    }

}

That's it. You just only need to call the method to change recovery option of windows service. For example:

LocalServiceHelper.ChangeRevoveryOption("ServiceName",
                    ServiceRecoveryOptionHelper.RecoverAction.Restart,
                    ServiceRecoveryOptionHelper.RecoverAction.Restart,
                    ServiceRecoveryOptionHelper.RecoverAction.None);

It will update the recovery option of windows service as you will mention when calling the method. Hope this help. Happy codding! :)