среда, 25 января 2012 г.

Управление службой .NET через WinApi

Предыстория.
Для службы сбора данных через DDE необходимо активировать "Взаимодействие с рабочим столом" в свойствах службы. По какой-то причине встроенные функции установки службы не позволяют управлять данной возможностью. Имеется в виду классы ServiceInstaller и ServiceProcessInstaller. Из-за этого, а также в виду необходимости перезапуска службы при наступлении определенных событий, не используя при этом bat-файлы, планировщики задач и отдельные программы, появился такой класс:



    internal class ServiceControl {
        private const uint SRV_ERRCTRL = 1;
        private const uint SRV_FULL_ACS = 0xF01FF;
        private const uint SCM_ALL_ACS = 0xF003F;
        private const uint SRV_STRT = 2;
        private const uint SRV_TYPE = 0x110;
        private const int MAX_RETRIES = 60;
        private int _retries;
        private readonly string _srvDispName;
        private readonly string _srvName;
        private readonly string _srvPath;
        private IntPtr _handle = IntPtr.Zero;
        private IntPtr _servHandle = IntPtr.Zero;
        internal ServiceControl(string path, string dispname) {
            _srvPath = Path.GetFullPath(path);
            if(string.IsNullOrEmpty(Path.GetExtension(path))) {
                _srvPath += ".exe";
            }
            _srvName = Path.GetFileNameWithoutExtension(path);
            _srvDispName = dispname;
        }
        private void CloseHandles() {
            if (_servHandle != IntPtr.Zero) {
                Interop.Services.CloseServiceHandle(_servHandle);
            }
            if (_handle != IntPtr.Zero) {
                Interop.Services.CloseServiceHandle(_handle);
            }
        }
        private void CheckServHand() {
            if (_servHandle != IntPtr.Zero) { } else {
                Interop.Services.CloseServiceHandle(_handle);
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
        }
        private void DeleteService() {
            if (!Interop.Services.DeleteService(_servHandle)) {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
        }
        private void OpenSCM() {
            CloseHandles();
            _handle = Interop.Services.OpenSCManager(null, null, SCM_ALL_ACS);
            if (_handle == IntPtr.Zero) {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
        }
        private void OpenService() {
            _servHandle = Interop.Services.OpenService(_handle, _srvName, SRV_FULL_ACS);
            CheckServHand();
        }
        private void CreateService() {
            _servHandle = Interop.Services.CreateService(_handle, _srvName, _srvDispName, SRV_FULL_ACS, SRV_TYPE, SRV_STRT,
                                                    SRV_ERRCTRL, _srvPath, null, null, null, null, null);
            CheckServHand();
        }
        private void StartService() {
            _retries = MAX_RETRIES;
            if (GetStatus() == SERVICE_STATE.SERVICE_STOPPED) {
                Interop.Services.StartService(_servHandle, 0, null);
            }
            while (GetStatus() != SERVICE_STATE.SERVICE_RUNNING && (_retries-- > 0)) {
                Thread.Sleep(250);
            }
        }
        private SERVICE_STATE GetStatus() {
            var ss = new SERVICE_STATUS();
            Interop.Services.QueryServiceStatus(_servHandle, ref ss);
            return ss.dwCurrentState;
        }
        private void StopService() {
            _retries = MAX_RETRIES;
            if (GetStatus() != SERVICE_STATE.SERVICE_STOPPED) {
                var ss=new SERVICE_STATUS();
                Interop.Services.ControlService(_servHandle, SERVICE_CONTROL.STOP, ref ss);
            }
            while (GetStatus() != SERVICE_STATE.SERVICE_STOPPED && (_retries-- > 0)) {
                Thread.Sleep(250);
            }
        }
        public string Control(Ctrl method) {
            var s = "";
            OpenSCM();
            if (method == Ctrl.INSTALL) {
                CreateService();
                method = Ctrl.START;
            }
            OpenService();
            switch (method) {
                case Ctrl.UNINSTALL:
                    DeleteService();
                    break;
                case Ctrl.START:
                    StartService();
                    break;
                case Ctrl.STOP:
                    StopService();
                    break;
                case Ctrl.RESTART:
                    StopService();
                    StartService();
                    break;
                case Ctrl.STATUS:
                    s = GetStatus().ToString();
                    break;
            }
            CloseHandles();
            return s;
        }
    }
    public enum Ctrl {
        INSTALL,
        UNINSTALL,
        START,
        STOP,
        STATUS,
        RESTART
    }

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


Все что нужно будет потом, это:
var sc = new ServiceControl(Environment.GetCommandLineArgs()[0],
                                             "Выводимое имя в управлении службами");
и sc.Control(Ctrl.INSTALL); - для установки или использовать любую другую команду из доступных в перечислении Ctrl.

Саму реализацию внутри службы управления через передаваемые аргументы из командной строки можно сделать, например, так:

[STAThread]
private static void Main(string[] args) {
    try {
        if (args.Length > 0) {
            try {
                var sc = new ServiceControl(Environment.GetCommandLineArgs()[0],
                                            "Выводимое имя");
                Ctrl ct;
                switch (args[0].ToLower().Trim()) {
                    case "/install":
                        ct = Ctrl.INSTALL;
                        break;
                    case "/restart":
                        ct=Ctrl.RESTART;
                        break;
                    case "/uninstall":
                        ct=Ctrl.UNINSTALL;
                        break;
                    case "/start":
                        ct=Ctrl.START;
                        break;
                    case "/stop":
                        ct=Ctrl.STOP;
                        break;
                    case "/state":
                        Console.WriteLine(sc.Control(Ctrl.STATUS));
                        return;
                    case "/qr":
                        sc.Control(Ctrl.RESTART);
                        return;
                    default:
                        return;
                }
                sc.Control(ct);
                Console.WriteLine(Messages.Service[(int)ct]);
            } catch (Exception e) {
                Console.WriteLine(e.Message);
            }
        } else {
            Run(new Service());
        }
    } catch (Exception e) {
        Trace.WriteLog(e);
    }
}
Чтобы перезапустить службу из самой себя достаточно написать:


Process.Start(new ProcessStartInfo(Path.GetFullPath(Environment.GetCommandLineArgs()[0]), "/qr")
            {WindowStyle = ProcessWindowStyle.Hidden});


Комментариев нет:

Отправить комментарий