Configurator/Core/Controllers/ExpandedServiceController.cs (216 lines of code) (raw):

/* Copyright (c) 2023, 2024, Oracle and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by the Free Software Foundation. This program is designed to work with certain software (including but not limited to OpenSSL) that is licensed under separate terms, as designated in a particular file or component or in included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the separately licensed software that they have either included with the program or referenced in the documentation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ using System; using System.ComponentModel; using System.Security; using System.Security.Permissions; using System.ServiceProcess; using System.Threading; using Microsoft.Win32; using MySql.Configurator.Base.Classes; using MySql.Configurator.Core.Logging; namespace MySql.Configurator.Core.Controllers { //// Wrapper for the ServiceController object that adds ability to Add and remove services and use service BinaryPath public class ExpandedServiceController : ServiceController { #region Private //// Parameters private string _binaryPath; //// Methods private static Win32Exception CreateSafeWin32Exception() { Win32Exception exception; new SecurityPermission(PermissionState.Unrestricted).Assert(); try { exception = new Win32Exception(); } finally { CodeAccessPermission.RevertAssert(); } return exception; } private static IntPtr GetDataBaseHandleWithAllAccess(string machineName) { IntPtr zero; if (machineName.Equals(".") || (machineName.Length == 0)) { zero = Win32.OpenSCManager(null, null, (Win32.SC_MANAGER_ALL | Win32.ACCESS_TYPE_DELETE)); } else { zero = Win32.OpenSCManager(machineName, null, (Win32.SC_MANAGER_ALL | Win32.ACCESS_TYPE_DELETE)); } if (zero == IntPtr.Zero) { Exception innerException = CreateSafeWin32Exception(); throw new InvalidOperationException("Failed to open connection to service database.", innerException); } return zero; } private void GetBinaryPath() { var serviceEntry = Registry.LocalMachine; try { serviceEntry = serviceEntry.OpenSubKey("SYSTEM\\CurrentControlSet\\Services\\" + ServiceName, false); if (serviceEntry != null) { _binaryPath = serviceEntry.GetValue("ImagePath", "").ToString(); } } catch (Exception e) { Logger.LogException(e); _binaryPath = string.Empty; } finally { serviceEntry?.Close(); } } #endregion #region Public public const int DEFAULT_POLLING_DELAY_MILLISECONDS = 1000; //// WaitingForStatus polling delay to avoid pounding the CPU while waiting for status. 500 ms is the default value. public int PollingDelay { get; set; } //// Wrapper for existing constructors public ExpandedServiceController(string name) : base(name) { PollingDelay = DEFAULT_POLLING_DELAY_MILLISECONDS; GetBinaryPath(); } //// Configure constructors used to create new services and cope an existing one. public ExpandedServiceController(string name, string displayName, string fileName, string username, string password, bool startAtStartUp) { PollingDelay = DEFAULT_POLLING_DELAY_MILLISECONDS; //// Create the new Service. IntPtr scm = GetDataBaseHandleWithAllAccess(MachineName); int startType = startAtStartUp ? Win32.START_TYPE_AUTO : Win32.START_TYPE_DEMAND; if (username != null) { username = username.Contains(@"\") ? username : @".\" + username; } try { IntPtr service = Win32.OpenService(scm, name, Win32.ACCESS_TYPE_ALL); if (service == IntPtr.Zero) { try { service = Win32.CreateService(scm, name, displayName, Win32.ACCESS_TYPE_ALL, Win32.SERVICE_TYPE_WIN32_OWN_PROCESS, startType, Win32.ERROR_CONTROL_NORMAL, fileName, null, IntPtr.Zero, null, username, password); } finally { Win32.CloseServiceHandle(service); } } if (service == IntPtr.Zero) { throw CreateSafeWin32Exception(); } } finally { Win32.CloseServiceHandle(scm); } ServiceName = name; DisplayName = displayName; GetBinaryPath(); } public ExpandedServiceController(ServiceController defaultBase) : base(defaultBase.ServiceName, defaultBase.MachineName) { PollingDelay = DEFAULT_POLLING_DELAY_MILLISECONDS; GetBinaryPath(); } public void Update(string newname, string cmdline, string username, string password, bool startup) { IntPtr scm = GetDataBaseHandleWithAllAccess(MachineName); int startType = startup ? Win32.START_TYPE_AUTO : Win32.START_TYPE_DEMAND; if (username != null) { username = username.Contains(@"\") ? username : @".\" + username; } try { IntPtr service = Win32.OpenService(scm, ServiceName, Win32.ACCESS_TYPE_ALL); if (service == IntPtr.Zero) { return; } try { bool b = Win32.ChangeServiceConfig(service, Win32.SERVICE_TYPE_WIN32_OWN_PROCESS, (uint)startType, Win32.ERROR_CONTROL_NORMAL, cmdline, null, IntPtr.Zero, null, username, password, newname); if (!b) { throw CreateSafeWin32Exception(); } } finally { Win32.CloseServiceHandle(service); } } finally { Win32.CloseServiceHandle(scm); } } public void Remove() { string serviceName = ServiceName; string machineName = MachineName; if (Status == ServiceControllerStatus.Running) { Stop(); } WaitForStatus(ServiceControllerStatus.Stopped); Close(); IntPtr scm = GetDataBaseHandleWithAllAccess(machineName); try { IntPtr service = Win32.OpenService(scm, serviceName, Win32.ACCESS_TYPE_ALL | Win32.ACCESS_TYPE_DELETE); if (service == IntPtr.Zero) { throw CreateSafeWin32Exception(); } try { if (!Win32.DeleteService(service)) { throw CreateSafeWin32Exception(); } } finally { Win32.CloseServiceHandle(service); } } finally { Win32.CloseServiceHandle(scm); } } public string BinaryPath { get { return _binaryPath; } set { _binaryPath = value; RegistryKey serviceEntry = Registry.LocalMachine; try { serviceEntry = serviceEntry.OpenSubKey("SYSTEM\\CurrentControlSet\\Services\\" + ServiceName, true); serviceEntry?.SetValue("ImagePath", _binaryPath); } catch (Exception e) { Logger.LogException(e); } finally { serviceEntry?.Close(); } } } public void WaitForStatus(ServiceControllerStatus statusToWaitFor, CancellationToken cancellationToken) { Logger.LogVerbose($"{DateTime.Now} - Waiting for service status change to {statusToWaitFor}."); while (!cancellationToken.IsCancellationRequested) { Refresh(); if (Status == statusToWaitFor) { return; } //// Delay added to avoid pounding CPU while waiting for status. Thread.Sleep(PollingDelay); } //// Cancel Requested throw new OperationCanceledException(cancellationToken); } #endregion } }