diff --git a/src/Core/ServiceWrapper/Program.cs b/src/Core/ServiceWrapper/Program.cs index f03fd77..1e5a259 100644 --- a/src/Core/ServiceWrapper/Program.cs +++ b/src/Core/ServiceWrapper/Program.cs @@ -4,9 +4,7 @@ using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; -#if NETCOREAPP -using System.Reflection; -#endif +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security.AccessControl; using System.Security.Principal; @@ -20,9 +18,6 @@ using log4net.Core; using log4net.Layout; using winsw.Logging; using winsw.Native; -using winsw.Util; -using WMI; -using ServiceType = WMI.ServiceType; namespace winsw { @@ -45,11 +40,19 @@ namespace winsw Console.Error.WriteLine(message); return -1; } - catch (WmiException e) + catch (Win32Exception e) { - Log.Fatal("WMI Operation failure: " + e.ErrorCode, e); - Console.Error.WriteLine(e); - return (int)e.ErrorCode; + string message = e.Message; + Log.Fatal(message, e); + Console.Error.WriteLine(message); + return e.NativeErrorCode; + } + catch (UserException e) + { + string message = e.Message; + Log.Fatal(message, e); + Console.Error.WriteLine(message); + return -1; } catch (Exception e) { @@ -85,10 +88,6 @@ namespace winsw return; } - // Get service info for the future use - Win32Services svcs = new WmiRoot().GetCollection(); - Win32Service? svc = svcs.Select(descriptor.Id); - var args = new List(Array.AsReadOnly(argsArray)); if (args[0] == "/redirect") { @@ -194,8 +193,9 @@ namespace winsw Log.Info("Installing the service with id '" + descriptor.Id + "'"); - // Check if the service exists - if (svc != null) + using ServiceManager scm = ServiceManager.Open(); + + if (scm.ServiceExists(descriptor.Id)) { Console.WriteLine("Service with id '" + descriptor.Id + "' already exists"); Console.WriteLine("To install the service, delete the existing one or change service Id in the configuration file"); @@ -235,20 +235,14 @@ namespace winsw Security.AddServiceLogonRight(descriptor.ServiceAccountDomain!, descriptor.ServiceAccountName!); } - svcs.Create( + using Service sc = scm.CreateService( descriptor.Id, descriptor.Caption, + descriptor.StartMode, "\"" + descriptor.ExecutablePath + "\"", - ServiceType.OwnProcess, - ErrorControl.UserNotified, - descriptor.StartMode.ToString(), - descriptor.Interactive, + descriptor.ServiceDependencies, username, - password, - descriptor.ServiceDependencies); - - using ServiceManager scm = ServiceManager.Open(); - using Service sc = scm.OpenService(descriptor.Id); + password); sc.SetDescription(descriptor.Description); @@ -258,7 +252,7 @@ namespace winsw sc.SetFailureActions(descriptor.ResetFailureAfter, actions); } - bool isDelayedAutoStart = descriptor.StartMode == StartMode.Automatic && descriptor.DelayedAutoStart; + bool isDelayedAutoStart = descriptor.StartMode == ServiceStartMode.Automatic && descriptor.DelayedAutoStart; if (isDelayedAutoStart) { sc.SetDelayedAutoStart(true); @@ -287,39 +281,40 @@ namespace winsw } Log.Info("Uninstalling the service with id '" + descriptor.Id + "'"); - if (svc is null) - { - Log.Warn("The service with id '" + descriptor.Id + "' does not exist. Nothing to uninstall"); - return; // there's no such service, so consider it already uninstalled - } - - if (svc.Started) - { - // We could fail the opeartion here, but it would be an incompatible change. - // So it is just a warning - Log.Warn("The service with id '" + descriptor.Id + "' is running. It may be impossible to uninstall it"); - } + using ServiceManager scm = ServiceManager.Open(); try { - svc.Delete(); + using Service sc = scm.OpenService(descriptor.Id); + + if (sc.Status == ServiceControllerStatus.Running) + { + // We could fail the opeartion here, but it would be an incompatible change. + // So it is just a warning + Log.Warn("The service with id '" + descriptor.Id + "' is running. It may be impossible to uninstall it"); + } + + sc.Delete(); } - catch (WmiException e) + catch (Win32Exception e) { - if (e.ErrorCode == ReturnValue.ServiceMarkedForDeletion) + switch (e.NativeErrorCode) { - Log.Error("Failed to uninstall the service with id '" + descriptor.Id + "'" - + ". It has been marked for deletion."); + case Errors.ERROR_SERVICE_DOES_NOT_EXIST: + Log.Warn("The service with id '" + descriptor.Id + "' does not exist. Nothing to uninstall"); + break; // there's no such service, so consider it already uninstalled - // TODO: change the default behavior to Error? - return; // it's already uninstalled, so consider it a success - } - else - { - Log.Fatal("Failed to uninstall the service with id '" + descriptor.Id + "'. WMI Error code is '" + e.ErrorCode + "'"); - } + case Errors.ERROR_SERVICE_MARKED_FOR_DELETE: + Log.Error("Failed to uninstall the service with id '" + descriptor.Id + "'" + + ". It has been marked for deletion."); - throw e; + // TODO: change the default behavior to Error? + break; // it's already uninstalled, so consider it a success + + default: + Log.Fatal("Failed to uninstall the service with id '" + descriptor.Id + "'. WMI Error code is '" + e.ErrorCode + "'"); + throw e; + } } } @@ -332,24 +327,28 @@ namespace winsw } Log.Info("Starting the service with id '" + descriptor.Id + "'"); - if (svc is null) - { - ThrowNoSuchService(); - } + + using var svc = new ServiceController(descriptor.Id); try { - svc.StartService(); + svc.Start(); } - catch (WmiException e) + catch (InvalidOperationException e) when (e.InnerException is Win32Exception inner) { - if (e.ErrorCode == ReturnValue.ServiceAlreadyRunning) + switch (inner.NativeErrorCode) { - Log.Info($"The service with ID '{descriptor.Id}' has already been started"); - } - else - { - throw; + case Errors.ERROR_SERVICE_DOES_NOT_EXIST: + ThrowNoSuchService(inner); + break; + + case Errors.ERROR_SERVICE_ALREADY_RUNNING: + Log.Info($"The service with ID '{descriptor.Id}' has already been started"); + break; + + default: + throw; + } } } @@ -363,24 +362,28 @@ namespace winsw } Log.Info("Stopping the service with id '" + descriptor.Id + "'"); - if (svc is null) - { - ThrowNoSuchService(); - } + + using var svc = new ServiceController(descriptor.Id); try { - svc.StopService(); + svc.Stop(); } - catch (WmiException e) + catch (InvalidOperationException e) when (e.InnerException is Win32Exception inner) { - if (e.ErrorCode == ReturnValue.ServiceCannotAcceptControl) + switch (inner.NativeErrorCode) { - Log.Info($"The service with ID '{descriptor.Id}' is not running"); - } - else - { - throw; + case Errors.ERROR_SERVICE_DOES_NOT_EXIST: + ThrowNoSuchService(inner); + break; + + case Errors.ERROR_SERVICE_NOT_ACTIVE: + Log.Info($"The service with ID '{descriptor.Id}' is not running"); + break; + + default: + throw; + } } } @@ -394,21 +397,33 @@ namespace winsw } Log.Info("Stopping the service with id '" + descriptor.Id + "'"); - if (svc is null) - { - ThrowNoSuchService(); - } - if (svc.Started) - { - svc.StopService(); - } + using var svc = new ServiceController(descriptor.Id); - while (svc != null && svc.Started) + try { - Log.Info("Waiting the service to stop..."); - Thread.Sleep(1000); - svc = svcs.Select(descriptor.Id); + svc.Stop(); + + while (!ServiceControllerExtension.TryWaitForStatus(svc, ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(1))) + { + Log.Info("Waiting the service to stop..."); + } + } + catch (InvalidOperationException e) when (e.InnerException is Win32Exception inner) + { + switch (inner.NativeErrorCode) + { + case Errors.ERROR_SERVICE_DOES_NOT_EXIST: + ThrowNoSuchService(inner); + break; + + case Errors.ERROR_SERVICE_NOT_ACTIVE: + break; + + default: + throw; + + } } Log.Info("The service stopped."); @@ -423,23 +438,35 @@ namespace winsw } Log.Info("Restarting the service with id '" + descriptor.Id + "'"); - if (svc is null) + + using var svc = new ServiceController(descriptor.Id); + + try { - ThrowNoSuchService(); + svc.Stop(); + + while (!ServiceControllerExtension.TryWaitForStatus(svc, ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(1))) + { + } + } + catch (InvalidOperationException e) when (e.InnerException is Win32Exception inner) + { + switch (inner.NativeErrorCode) + { + case Errors.ERROR_SERVICE_DOES_NOT_EXIST: + ThrowNoSuchService(inner); + break; + + case Errors.ERROR_SERVICE_NOT_ACTIVE: + break; + + default: + throw; + + } } - if (svc.Started) - { - svc.StopService(); - } - - while (svc.Started) - { - Thread.Sleep(1000); - svc = svcs.Select(descriptor.Id)!; - } - - svc.StartService(); + svc.Start(); } void RestartSelf() @@ -453,8 +480,7 @@ namespace winsw // run restart from another process group. see README.md for why this is useful. - bool result = ProcessApis.CreateProcess(null, descriptor.ExecutablePath + " restart", IntPtr.Zero, IntPtr.Zero, false, ProcessApis.CREATE_NEW_PROCESS_GROUP, IntPtr.Zero, null, default, out _); - if (!result) + if (!ProcessApis.CreateProcess(null, descriptor.ExecutablePath + " restart", IntPtr.Zero, IntPtr.Zero, false, ProcessApis.CREATE_NEW_PROCESS_GROUP, IntPtr.Zero, null, default, out _)) { throw new Exception("Failed to invoke restart: " + Marshal.GetLastWin32Error()); } @@ -463,7 +489,15 @@ namespace winsw void Status() { Log.Debug("User requested the status of the process with id '" + descriptor.Id + "'"); - Console.WriteLine(svc is null ? "NonExistent" : svc.Started ? "Started" : "Stopped"); + using var svc = new ServiceController(descriptor.Id); + try + { + Console.WriteLine(svc.Status == ServiceControllerStatus.Running ? "Started" : "Stopped"); + } + catch (InvalidOperationException e) when (e.InnerException is Win32Exception inner && inner.NativeErrorCode == Errors.ERROR_SERVICE_DOES_NOT_EXIST) + { + Console.WriteLine("NonExistent"); + } } void Test() @@ -474,7 +508,7 @@ namespace winsw return; } - WrapperService wsvc = new WrapperService(descriptor); + using WrapperService wsvc = new WrapperService(descriptor); wsvc.RaiseOnStart(args.ToArray()); Thread.Sleep(1000); wsvc.RaiseOnStop(); @@ -488,7 +522,7 @@ namespace winsw return; } - WrapperService wsvc = new WrapperService(descriptor); + using WrapperService wsvc = new WrapperService(descriptor); wsvc.RaiseOnStart(args.ToArray()); Console.WriteLine("Press any key to stop the service..."); _ = Console.Read(); @@ -530,8 +564,10 @@ namespace winsw } } + /// [DoesNotReturn] - private static void ThrowNoSuchService() => throw new WmiException(ReturnValue.NoSuchService); + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowNoSuchService(Win32Exception inner) => throw new UserException(null, inner); private static void InitLoggers(ServiceDescriptor descriptor, bool enableConsoleLogging) { @@ -588,43 +624,40 @@ namespace winsw BasicConfigurator.Configure( #if NETCOREAPP - LogManager.GetRepository(Assembly.GetExecutingAssembly()), + LogManager.GetRepository(System.Reflection.Assembly.GetExecutingAssembly()), #endif appenders.ToArray()); } - internal static unsafe bool IsProcessElevated() + internal static bool IsProcessElevated() { IntPtr process = ProcessApis.GetCurrentProcess(); if (!ProcessApis.OpenProcessToken(process, TokenAccessLevels.Read, out IntPtr token)) { - ThrowWin32Exception("Failed to open process token."); + Throw.Win32Exception("Failed to open process token."); } try { - if (!SecurityApis.GetTokenInformation( - token, - SecurityApis.TOKEN_INFORMATION_CLASS.TokenElevation, - out SecurityApis.TOKEN_ELEVATION elevation, - sizeof(SecurityApis.TOKEN_ELEVATION), - out _)) + unsafe { - ThrowWin32Exception("Failed to get token information"); - } + if (!SecurityApis.GetTokenInformation( + token, + SecurityApis.TOKEN_INFORMATION_CLASS.TokenElevation, + out SecurityApis.TOKEN_ELEVATION elevation, + sizeof(SecurityApis.TOKEN_ELEVATION), + out _)) + { + Throw.Win32Exception("Failed to get token information."); + } - return elevation.TokenIsElevated != 0; + return elevation.TokenIsElevated != 0; + } } finally { _ = HandleApis.CloseHandle(token); } - - static void ThrowWin32Exception(string message) - { - Win32Exception inner = new Win32Exception(); - throw new Win32Exception(inner.NativeErrorCode, message + ' ' + inner.Message); - } } private static string ReadPassword() diff --git a/src/Core/ServiceWrapper/ServiceControllerExtension.cs b/src/Core/ServiceWrapper/ServiceControllerExtension.cs new file mode 100644 index 0000000..17c0326 --- /dev/null +++ b/src/Core/ServiceWrapper/ServiceControllerExtension.cs @@ -0,0 +1,22 @@ +using System; +using System.ServiceProcess; +using TimeoutException = System.ServiceProcess.TimeoutException; + +namespace winsw +{ + internal static class ServiceControllerExtension + { + internal static bool TryWaitForStatus(/*this*/ ServiceController serviceController, ServiceControllerStatus desiredStatus, TimeSpan timeout) + { + try + { + serviceController.WaitForStatus(desiredStatus, timeout); + return true; + } + catch (TimeoutException) + { + return false; + } + } + } +} diff --git a/src/Core/ServiceWrapper/UserException.cs b/src/Core/ServiceWrapper/UserException.cs new file mode 100644 index 0000000..3234730 --- /dev/null +++ b/src/Core/ServiceWrapper/UserException.cs @@ -0,0 +1,17 @@ +using System; + +namespace winsw +{ + internal sealed class UserException : Exception + { + internal UserException(string message) + : base(message) + { + } + + internal UserException(string? message, Exception inner) + : base(message, inner) + { + } + } +} diff --git a/src/Core/ServiceWrapper/WrapperService.cs b/src/Core/ServiceWrapper/WrapperService.cs index af3a972..6dc356a 100644 --- a/src/Core/ServiceWrapper/WrapperService.cs +++ b/src/Core/ServiceWrapper/WrapperService.cs @@ -428,7 +428,7 @@ namespace winsw IntPtr handle = ServiceHandle; _wrapperServiceStatus.CheckPoint++; // WriteEvent("SignalShutdownComplete " + wrapperServiceStatus.checkPoint + ":" + wrapperServiceStatus.waitHint); - _wrapperServiceStatus.CurrentState = ServiceApis.ServiceState.STOPPED; + _wrapperServiceStatus.CurrentState = ServiceControllerStatus.Stopped; ServiceApis.SetServiceStatus(handle, _wrapperServiceStatus); } diff --git a/src/Core/WinSWCore/Configuration/DefaultSettings.cs b/src/Core/WinSWCore/Configuration/DefaultSettings.cs index 3c7a6f2..7ed874e 100644 --- a/src/Core/WinSWCore/Configuration/DefaultSettings.cs +++ b/src/Core/WinSWCore/Configuration/DefaultSettings.cs @@ -2,8 +2,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.ServiceProcess; using System.Xml; -using WMI; namespace winsw.Configuration { @@ -38,7 +38,7 @@ namespace winsw.Configuration public bool StopParentProcessFirst => false; // Service management - public StartMode StartMode => StartMode.Automatic; + public ServiceStartMode StartMode => ServiceStartMode.Automatic; public bool DelayedAutoStart => false; public string[] ServiceDependencies => new string[0]; public TimeSpan WaitHint => TimeSpan.FromSeconds(15); diff --git a/src/Core/WinSWCore/Configuration/IWinSWConfiguration.cs b/src/Core/WinSWCore/Configuration/IWinSWConfiguration.cs index be9f56b..5161639 100644 --- a/src/Core/WinSWCore/Configuration/IWinSWConfiguration.cs +++ b/src/Core/WinSWCore/Configuration/IWinSWConfiguration.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.ServiceProcess; using System.Xml; -using WMI; namespace winsw.Configuration { @@ -35,7 +35,7 @@ namespace winsw.Configuration bool StopParentProcessFirst { get; } // Service management - StartMode StartMode { get; } + ServiceStartMode StartMode { get; } string[] ServiceDependencies { get; } TimeSpan WaitHint { get; } TimeSpan SleepTime { get; } diff --git a/src/Core/WinSWCore/DynamicProxy.cs b/src/Core/WinSWCore/DynamicProxy.cs deleted file mode 100644 index 0589e27..0000000 --- a/src/Core/WinSWCore/DynamicProxy.cs +++ /dev/null @@ -1,199 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Reflection.Emit; - -namespace DynamicProxy -{ - /// - /// Interface that a user defined proxy handler needs to implement. This interface - /// defines one method that gets invoked by the generated proxy. - /// - public interface IProxyInvocationHandler - { - /// The instance of the proxy - /// The method info that can be used to invoke the actual method on the object implementation - /// Parameters to pass to the method - /// Object - object? Invoke(object proxy, MethodInfo method, object[] parameters); - } - - /// - /// - public static class ProxyFactory - { - private const string ProxySuffix = "Proxy"; - private const string AssemblyName = "ProxyAssembly"; - private const string ModuleName = "ProxyModule"; - private const string HandlerName = "handler"; - - private static readonly Dictionary TypeCache = new Dictionary(); - - private static readonly AssemblyBuilder AssemblyBuilder = -#if VNEXT - AssemblyBuilder.DefineDynamicAssembly( -#else - AppDomain.CurrentDomain.DefineDynamicAssembly( -#endif - new AssemblyName(AssemblyName), AssemblyBuilderAccess.Run); - - private static readonly ModuleBuilder ModuleBuilder = AssemblyBuilder.DefineDynamicModule(ModuleName); - - public static object Create(IProxyInvocationHandler handler, Type objType, bool isObjInterface = false) - { - string typeName = objType.FullName + ProxySuffix; - Type? type = null; - lock (TypeCache) - { - if (!TypeCache.TryGetValue(typeName, out type)) - { - type = CreateType(typeName, isObjInterface ? new Type[] { objType } : objType.GetInterfaces()); - TypeCache.Add(typeName, type); - } - } - - return Activator.CreateInstance(type, new object[] { handler })!; - } - - private static Type CreateType(string dynamicTypeName, Type[] interfaces) - { - Type objType = typeof(object); - Type handlerType = typeof(IProxyInvocationHandler); - - TypeAttributes typeAttributes = TypeAttributes.Public | TypeAttributes.Sealed; - - // Gather up the proxy information and create a new type builder. One that - // inherits from Object and implements the interface passed in - TypeBuilder typeBuilder = ModuleBuilder.DefineType( - dynamicTypeName, typeAttributes, objType, interfaces); - - // Define a member variable to hold the delegate - FieldBuilder handlerField = typeBuilder.DefineField( - HandlerName, handlerType, FieldAttributes.Private | FieldAttributes.InitOnly); - - // build a constructor that takes the delegate object as the only argument - ConstructorInfo baseConstructor = objType.GetConstructor(Type.EmptyTypes)!; - ConstructorBuilder delegateConstructor = typeBuilder.DefineConstructor( - MethodAttributes.Public, CallingConventions.Standard, new Type[] { handlerType }); - - ILGenerator constructorIL = delegateConstructor.GetILGenerator(); - - // Load "this" - constructorIL.Emit(OpCodes.Ldarg_0); - // Load first constructor parameter - constructorIL.Emit(OpCodes.Ldarg_1); - // Set the first parameter into the handler field - constructorIL.Emit(OpCodes.Stfld, handlerField); - // Load "this" - constructorIL.Emit(OpCodes.Ldarg_0); - // Call the super constructor - constructorIL.Emit(OpCodes.Call, baseConstructor); - // Constructor return - constructorIL.Emit(OpCodes.Ret); - - // for every method that the interfaces define, build a corresponding - // method in the dynamic type that calls the handlers invoke method. - foreach (Type interfaceType in interfaces) - { - GenerateMethod(interfaceType, handlerField, typeBuilder); - } - - return typeBuilder.CreateType()!; - } - - /// - /// . - /// - private static readonly MethodInfo InvokeMethod = typeof(IProxyInvocationHandler).GetMethod(nameof(IProxyInvocationHandler.Invoke))!; - - /// - /// . - /// - private static readonly MethodInfo GetMethodFromHandleMethod = typeof(MethodBase).GetMethod(nameof(MethodBase.GetMethodFromHandle), new[] { typeof(RuntimeMethodHandle) })!; - - private static void GenerateMethod(Type interfaceType, FieldBuilder handlerField, TypeBuilder typeBuilder) - { - MethodInfo[] interfaceMethods = interfaceType.GetMethods(); - - for (int i = 0; i < interfaceMethods.Length; i++) - { - MethodInfo methodInfo = interfaceMethods[i]; - - // Get the method parameters since we need to create an array - // of parameter types - ParameterInfo[] methodParams = methodInfo.GetParameters(); - int numOfParams = methodParams.Length; - Type[] methodParameters = new Type[numOfParams]; - - // convert the ParameterInfo objects into Type - for (int j = 0; j < numOfParams; j++) - { - methodParameters[j] = methodParams[j].ParameterType; - } - - // create a new builder for the method in the interface - MethodBuilder methodBuilder = typeBuilder.DefineMethod( - methodInfo.Name, - /*MethodAttributes.Public | MethodAttributes.Virtual | */ methodInfo.Attributes & ~MethodAttributes.Abstract, - CallingConventions.Standard, - methodInfo.ReturnType, methodParameters); - - ILGenerator methodIL = methodBuilder.GetILGenerator(); - - // invoke target: IProxyInvocationHandler - methodIL.Emit(OpCodes.Ldarg_0); - methodIL.Emit(OpCodes.Ldfld, handlerField); - - // 1st parameter: object proxy - methodIL.Emit(OpCodes.Ldarg_0); - - // 2nd parameter: MethodInfo method - methodIL.Emit(OpCodes.Ldtoken, methodInfo); - methodIL.Emit(OpCodes.Call, GetMethodFromHandleMethod); - methodIL.Emit(OpCodes.Castclass, typeof(MethodInfo)); - - // 3rd parameter: object[] parameters - methodIL.Emit(OpCodes.Ldc_I4, numOfParams); - methodIL.Emit(OpCodes.Newarr, typeof(object)); - - // if we have any parameters, then iterate through and set the values - // of each element to the corresponding arguments - for (int j = 0; j < numOfParams; j++) - { - methodIL.Emit(OpCodes.Dup); // copy the array - methodIL.Emit(OpCodes.Ldc_I4, j); - methodIL.Emit(OpCodes.Ldarg, j + 1); // +1 for "this" - if (methodParameters[j].IsValueType) - { - methodIL.Emit(OpCodes.Box, methodParameters[j]); - } - - methodIL.Emit(OpCodes.Stelem_Ref); - } - - // call the Invoke method - methodIL.Emit(OpCodes.Callvirt, InvokeMethod); - - if (methodInfo.ReturnType != typeof(void)) - { - methodIL.Emit(OpCodes.Unbox_Any, methodInfo.ReturnType); - } - else - { - // pop the return value that Invoke returned from the stack since - // the method's return type is void. - methodIL.Emit(OpCodes.Pop); - } - - // Return - methodIL.Emit(OpCodes.Ret); - } - - // Iterate through the parent interfaces and recursively call this method - foreach (Type parentType in interfaceType.GetInterfaces()) - { - GenerateMethod(parentType, handlerField, typeBuilder); - } - } - } -} diff --git a/src/Core/WinSWCore/Native/Errors.cs b/src/Core/WinSWCore/Native/Errors.cs index c52dbe9..0f23279 100644 --- a/src/Core/WinSWCore/Native/Errors.cs +++ b/src/Core/WinSWCore/Native/Errors.cs @@ -5,6 +5,10 @@ internal const int ERROR_ACCESS_DENIED = 5; internal const int ERROR_INVALID_HANDLE = 6; internal const int ERROR_INVALID_PARAMETER = 7; + internal const int ERROR_SERVICE_ALREADY_RUNNING = 1056; + internal const int ERROR_SERVICE_DOES_NOT_EXIST = 1060; + internal const int ERROR_SERVICE_NOT_ACTIVE = 1062; + internal const int ERROR_SERVICE_MARKED_FOR_DELETE = 1072; internal const int ERROR_CANCELLED = 1223; } } diff --git a/src/Core/WinSWCore/Native/Security.cs b/src/Core/WinSWCore/Native/Security.cs index a25006f..7383af6 100644 --- a/src/Core/WinSWCore/Native/Security.cs +++ b/src/Core/WinSWCore/Native/Security.cs @@ -7,6 +7,7 @@ namespace winsw.Native { internal static class Security { + /// internal static void AddServiceLogonRight(string domain, string user) { IntPtr sid = GetAccountSid(domain, user); @@ -22,6 +23,7 @@ namespace winsw.Native } } + /// private static IntPtr GetAccountSid(string domain, string user) { int sidSize = 0; @@ -53,6 +55,7 @@ namespace winsw.Native } } + /// private static void AddAccountRight(IntPtr sid, string rightName) { uint status = LsaOpenPolicy(IntPtr.Zero, default, PolicyAccess.ALL_ACCESS, out IntPtr policyHandle); diff --git a/src/Core/WinSWCore/Native/Service.cs b/src/Core/WinSWCore/Native/Service.cs index bf2c4e2..36e3942 100644 --- a/src/Core/WinSWCore/Native/Service.cs +++ b/src/Core/WinSWCore/Native/Service.cs @@ -1,5 +1,8 @@ using System; +using System.ComponentModel; using System.Security.AccessControl; +using System.ServiceProcess; +using System.Text; using static winsw.Native.ServiceApis; namespace winsw.Native @@ -52,6 +55,7 @@ namespace winsw.Native private ServiceManager(IntPtr handle) => this.handle = handle; + /// internal static ServiceManager Open() { IntPtr handle = OpenSCManager(null, null, ServiceManagerAccess.ALL_ACCESS); @@ -63,6 +67,57 @@ namespace winsw.Native return new ServiceManager(handle); } + /// + internal Service CreateService( + string serviceName, + string displayName, + ServiceStartMode startMode, + string executablePath, + string[] dependencies, + string? username, + string? password) + { + int arrayLength = 1; + for (int i = 0; i < dependencies.Length; i++) + { + arrayLength += dependencies[i].Length + 1; + } + + StringBuilder? array = null; + if (dependencies.Length != 0) + { + array = new StringBuilder(arrayLength); + for (int i = 0; i < dependencies.Length; i++) + { + _ = array.Append(dependencies[i]).Append('\0'); + } + + _ = array.Append('\0'); + } + + IntPtr handle = ServiceApis.CreateService( + this.handle, + serviceName, + displayName, + ServiceAccess.ALL_ACCESS, + ServiceType.Win32OwnProcess, + startMode, + ServiceErrorControl.Normal, + executablePath, + default, + default, + array, + username, + password); + if (handle == IntPtr.Zero) + { + Throw.Win32Exception("Failed to create service."); + } + + return new Service(handle); + } + + /// internal Service OpenService(string serviceName) { IntPtr serviceHandle = ServiceApis.OpenService(this.handle, serviceName, ServiceAccess.ALL_ACCESS); @@ -74,6 +129,18 @@ namespace winsw.Native return new Service(serviceHandle); } + internal bool ServiceExists(string serviceName) + { + IntPtr serviceHandle = ServiceApis.OpenService(this.handle, serviceName, ServiceAccess.ALL_ACCESS); + if (serviceHandle == IntPtr.Zero) + { + return false; + } + + _ = CloseServiceHandle(this.handle); + return true; + } + public void Dispose() { if (this.handle != IntPtr.Zero) @@ -91,6 +158,30 @@ namespace winsw.Native internal Service(IntPtr handle) => this.handle = handle; + /// + internal ServiceControllerStatus Status + { + get + { + if (!QueryServiceStatus(this.handle, out SERVICE_STATUS status)) + { + Throw.Win32Exception("Failed to query service status."); + } + + return status.CurrentState; + } + } + + /// + internal void Delete() + { + if (!DeleteService(this.handle)) + { + Throw.Win32Exception("Failed to delete service."); + } + } + + /// internal void SetDescription(string description) { if (!ChangeServiceConfig2( @@ -102,6 +193,7 @@ namespace winsw.Native } } + /// internal unsafe void SetFailureActions(TimeSpan failureResetPeriod, SC_ACTION[] actions) { fixed (SC_ACTION* actionsPtr = actions) @@ -122,6 +214,7 @@ namespace winsw.Native } } + /// internal void SetDelayedAutoStart(bool enabled) { if (!ChangeServiceConfig2( @@ -133,6 +226,7 @@ namespace winsw.Native } } + /// internal void SetSecurityDescriptor(RawSecurityDescriptor securityDescriptor) { byte[] securityDescriptorBytes = new byte[securityDescriptor.BinaryLength]; diff --git a/src/Core/WinSWCore/Native/ServiceApis.cs b/src/Core/WinSWCore/Native/ServiceApis.cs index 6d9785e..bd50c1d 100644 --- a/src/Core/WinSWCore/Native/ServiceApis.cs +++ b/src/Core/WinSWCore/Native/ServiceApis.cs @@ -1,6 +1,8 @@ using System; using System.Runtime.InteropServices; using System.Security.AccessControl; +using System.ServiceProcess; +using System.Text; namespace winsw.Native { @@ -18,12 +20,34 @@ namespace winsw.Native [DllImport(Libraries.Advapi32)] internal static extern bool CloseServiceHandle(IntPtr objectHandle); + [DllImport(Libraries.Advapi32, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "CreateServiceW")] + internal static extern IntPtr CreateService( + IntPtr databaseHandle, + string serviceName, + string displayName, + ServiceAccess desiredAccess, + ServiceType serviceType, + ServiceStartMode startType, + ServiceErrorControl errorControl, + string binaryPath, + string? loaderOrderGroup, + IntPtr tagId, + StringBuilder? dependencies, // TODO + string? serviceStartName, + string? password); + + [DllImport(Libraries.Advapi32, SetLastError = true)] + internal static extern bool DeleteService(IntPtr serviceHandle); + [DllImport(Libraries.Advapi32, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "OpenSCManagerW")] internal static extern IntPtr OpenSCManager(string? machineName, string? databaseName, ServiceManagerAccess desiredAccess); [DllImport(Libraries.Advapi32, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "OpenServiceW")] internal static extern IntPtr OpenService(IntPtr databaseHandle, string serviceName, ServiceAccess desiredAccess); + [DllImport(Libraries.Advapi32, SetLastError = true)] + internal static extern bool QueryServiceStatus(IntPtr serviceHandle, out SERVICE_STATUS serviceStatus); + [DllImport(Libraries.Advapi32, SetLastError = true)] internal static extern bool SetServiceObjectSecurity(IntPtr serviceHandle, SecurityInfos securityInformation, byte[] securityDescriptor); @@ -73,17 +97,13 @@ namespace winsw.Native PREFERRED_NODE = 9, } - // SERVICE_ - // https://docs.microsoft.com/windows/win32/api/winsvc/ns-winsvc-service_status - internal enum ServiceState : uint + // SERVICE_ERROR_ + internal enum ServiceErrorControl : uint { - STOPPED = 0x00000001, - START_PENDING = 0x00000002, - STOP_PENDING = 0x00000003, - RUNNING = 0x00000004, - CONTINUE_PENDING = 0x00000005, - PAUSE_PENDING = 0x00000006, - PAUSED = 0x00000007, + Ignore = 0x00000000, + Normal = 0x00000001, + Severe = 0x00000002, + Critical = 0x00000003, } // SC_MANAGER_ @@ -121,19 +141,19 @@ namespace winsw.Native // https://docs.microsoft.com/windows/win32/api/winsvc/ns-winsvc-service_failure_actionsw [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - internal unsafe struct SERVICE_FAILURE_ACTIONS + internal struct SERVICE_FAILURE_ACTIONS { public int ResetPeriod; public string RebootMessage; public string Command; public int ActionsCount; - public SC_ACTION* Actions; + public unsafe SC_ACTION* Actions; } internal struct SERVICE_STATUS { public int ServiceType; - public ServiceState CurrentState; + public ServiceControllerStatus CurrentState; public int ControlsAccepted; public int Win32ExitCode; public int ServiceSpecificExitCode; diff --git a/src/Core/WinSWCore/Native/Throw.cs b/src/Core/WinSWCore/Native/Throw.cs index a9aa795..43f3bde 100644 --- a/src/Core/WinSWCore/Native/Throw.cs +++ b/src/Core/WinSWCore/Native/Throw.cs @@ -1,10 +1,13 @@ using System.ComponentModel; using System.Diagnostics; +using System.Runtime.CompilerServices; namespace winsw.Native { internal static class Throw { + /// + [MethodImpl(MethodImplOptions.NoInlining)] internal static void Win32Exception(string message) { Win32Exception inner = new Win32Exception(); diff --git a/src/Core/WinSWCore/ServiceDescriptor.cs b/src/Core/WinSWCore/ServiceDescriptor.cs index 3e76cf5..8fd30fc 100755 --- a/src/Core/WinSWCore/ServiceDescriptor.cs +++ b/src/Core/WinSWCore/ServiceDescriptor.cs @@ -2,12 +2,12 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.ServiceProcess; using System.Text; using System.Xml; using winsw.Configuration; using winsw.Native; using winsw.Util; -using WMI; namespace winsw { @@ -507,7 +507,7 @@ namespace winsw /// /// Start mode of the Service /// - public StartMode StartMode + public ServiceStartMode StartMode { get { @@ -517,12 +517,12 @@ namespace winsw try { - return (StartMode)Enum.Parse(typeof(StartMode), p, true); + return (ServiceStartMode)Enum.Parse(typeof(ServiceStartMode), p, true); } catch { Console.WriteLine("Start mode in XML must be one of the following:"); - foreach (string sm in Enum.GetNames(typeof(StartMode))) + foreach (string sm in Enum.GetNames(typeof(ServiceStartMode))) { Console.WriteLine(sm); } diff --git a/src/Core/WinSWCore/WinSWCore.csproj b/src/Core/WinSWCore/WinSWCore.csproj index 7162559..7b655ad 100644 --- a/src/Core/WinSWCore/WinSWCore.csproj +++ b/src/Core/WinSWCore/WinSWCore.csproj @@ -26,12 +26,14 @@ + + diff --git a/src/Core/WinSWCore/Wmi.cs b/src/Core/WinSWCore/Wmi.cs deleted file mode 100755 index 045f30d..0000000 --- a/src/Core/WinSWCore/Wmi.cs +++ /dev/null @@ -1,219 +0,0 @@ -using System; -using System.Management; -using System.Reflection; -using System.Text; -using DynamicProxy; - -namespace WMI -{ - // https://docs.microsoft.com/windows/win32/cimwin32prov/create-method-in-class-win32-service - public enum ReturnValue : uint - { - Success = 0, - NotSupported = 1, - AccessDenied = 2, - DependentServicesRunning = 3, - InvalidServiceControl = 4, - ServiceCannotAcceptControl = 5, - ServiceNotActive = 6, - ServiceRequestTimeout = 7, - UnknownFailure = 8, - PathNotFound = 9, - ServiceAlreadyRunning = 10, - ServiceDatabaseLocked = 11, - ServiceDependencyDeleted = 12, - ServiceDependencyFailure = 13, - ServiceDisabled = 14, - ServiceLogonFailure = 15, - ServiceMarkedForDeletion = 16, - ServiceNoThread = 17, - StatusCircularDependency = 18, - StatusDuplicateName = 19, - StatusInvalidName = 20, - StatusInvalidParameter = 21, - StatusInvalidServiceAccount = 22, - StatusServiceExists = 23, - ServiceAlreadyPaused = 24, - - NoSuchService = 200 - } - - /// - /// Signals a problem in WMI related operations - /// - public class WmiException : Exception - { - public readonly ReturnValue ErrorCode; - - public WmiException(string message, ReturnValue code) - : base(message) - { - ErrorCode = code; - } - - public WmiException(ReturnValue code) - : this(code.ToString(), code) - { - } - } - - /// - /// Associated a WMI class name to the proxy interface (which should extend from IWmiCollection) - /// - public class WmiClassName : Attribute - { - public readonly string Name; - - public WmiClassName(string name) => Name = name; - } - - /// - /// Marker interface to denote a collection in WMI. - /// - public interface IWmiCollection { } - - /// - /// Marker interface to denote an individual managed object - /// - public interface IWmiObject - { - } - - public sealed class WmiRoot - { - private readonly ManagementScope wmiScope; - - public WmiRoot() - { - ConnectionOptions options = new ConnectionOptions - { - EnablePrivileges = true, - Impersonation = ImpersonationLevel.Impersonate, - Authentication = AuthenticationLevel.PacketPrivacy, - }; - - this.wmiScope = new ManagementScope(@"\\.\root\cimv2", options); - this.wmiScope.Connect(); - } - - private static string Capitalize(string s) - { - return char.ToUpper(s[0]) + s.Substring(1); - } - - private abstract class BaseHandler : IProxyInvocationHandler - { - public abstract object? Invoke(object proxy, MethodInfo method, object[] arguments); - - protected void CheckError(ManagementBaseObject result) - { - uint code = (uint)result["returnValue"]; - if (code != 0) - throw new WmiException((ReturnValue)code); - } - - protected ManagementBaseObject GetMethodParameters(ManagementObject wmiObject, string methodName, ParameterInfo[] methodParameters, object[] arguments) - { - ManagementBaseObject wmiParameters = wmiObject.GetMethodParameters(methodName); - for (int i = 0; i < arguments.Length; i++) - { - string capitalizedName = Capitalize(methodParameters[i].Name!); - wmiParameters[capitalizedName] = arguments[i]; - } - - return wmiParameters; - } - } - - private class InstanceHandler : BaseHandler, IWmiObject - { - private readonly ManagementObject wmiObject; - - public InstanceHandler(ManagementObject wmiObject) => this.wmiObject = wmiObject; - - public override object? Invoke(object proxy, MethodInfo method, object[] arguments) - { - if (method.DeclaringType == typeof(IWmiObject)) - { - return method.Invoke(this, arguments); - } - - // TODO: proper property support - if (method.Name.StartsWith("set_")) - { - this.wmiObject[method.Name.Substring(4)] = arguments[0]; - return null; - } - - if (method.Name.StartsWith("get_")) - { - return this.wmiObject[method.Name.Substring(4)]; - } - - string methodName = method.Name; - using ManagementBaseObject? wmiParameters = arguments.Length == 0 ? null : - this.GetMethodParameters(this.wmiObject, methodName, method.GetParameters(), arguments); - using ManagementBaseObject result = this.wmiObject.InvokeMethod(methodName, wmiParameters, null); - this.CheckError(result); - return null; - } - } - - private class ClassHandler : BaseHandler - { - private readonly ManagementClass wmiClass; - private readonly string className; - - public ClassHandler(ManagementScope wmiScope, string className) - { - this.wmiClass = new ManagementClass(wmiScope, new ManagementPath(className), null); - this.className = className; - } - - public override object? Invoke(object proxy, MethodInfo method, object[] arguments) - { - ParameterInfo[] methodParameters = method.GetParameters(); - - if (method.Name == nameof(Win32Services.Select)) - { - // select method to find instances - StringBuilder query = new StringBuilder("SELECT * FROM ").Append(this.className).Append(" WHERE "); - for (int i = 0; i < arguments.Length; i++) - { - if (i != 0) - query.Append(" AND "); - - query.Append(' ').Append(Capitalize(methodParameters[i].Name!)).Append(" = '").Append(arguments[i]).Append('\''); - } - - using ManagementObjectSearcher searcher = new ManagementObjectSearcher(this.wmiClass.Scope, new ObjectQuery(query.ToString())); - using ManagementObjectCollection results = searcher.Get(); - // TODO: support collections - foreach (ManagementObject wmiObject in results) - { - return ProxyFactory.Create(new InstanceHandler(wmiObject), method.ReturnType, true); - } - - return null; - } - - string methodName = method.Name; - using ManagementBaseObject? wmiParameters = arguments.Length == 0 ? null : - this.GetMethodParameters(this.wmiClass, methodName, methodParameters, arguments); - using ManagementBaseObject result = this.wmiClass.InvokeMethod(methodName, wmiParameters, null); - this.CheckError(result); - return null; - } - } - - /// - /// Obtains an object that corresponds to a table in WMI, which is a collection of a managed object. - /// - public T GetCollection() where T : IWmiCollection - { - WmiClassName className = (WmiClassName)typeof(T).GetCustomAttributes(typeof(WmiClassName), false)[0]; - - return (T)ProxyFactory.Create(new ClassHandler(this.wmiScope, className.Name), typeof(T), true); - } - } -} diff --git a/src/Core/WinSWCore/WmiSchema.cs b/src/Core/WinSWCore/WmiSchema.cs deleted file mode 100755 index 959469b..0000000 --- a/src/Core/WinSWCore/WmiSchema.cs +++ /dev/null @@ -1,66 +0,0 @@ - -namespace WMI -{ - public enum ServiceType - { - KernalDriver = 1, - FileSystemDriver = 2, - Adapter = 4, - RecognizerDriver = 8, - OwnProcess = 16, - ShareProcess = 32, - InteractiveProcess = 256, - } - - public enum ErrorControl - { - UserNotNotified = 0, - UserNotified = 1, - SystemRestartedWithLastKnownGoodConfiguration = 2, - SystemAttemptsToStartWithAGoodConfiguration = 3 - } - - public enum StartMode - { - /// - /// Device driver started by the operating system loader. This value is valid only for driver services. - /// - Boot, - /// - /// Device driver started by the operating system initialization process. This value is valid only for driver services. - /// - System, - /// - /// Service to be started automatically by the Service Control Manager during system startup. - /// - Automatic, - /// - /// Service to be started by the Service Control Manager when a process calls the StartService method. - /// - Manual, - /// - /// Service that can no longer be started. - /// - Disabled, - } - - [WmiClassName("Win32_Service")] - public interface Win32Services : IWmiCollection - { - // ReturnValue Create(bool desktopInteract, string displayName, int errorControl, string loadOrderGroup, string loadOrderGroupDependencies, string name, string pathName, string serviceDependencies, string serviceType, string startMode, string startName, string startPassword); - void Create(string name, string displayName, string pathName, ServiceType serviceType, ErrorControl errorControl, string startMode, bool desktopInteract, string? startName, string? startPassword, string[] serviceDependencies); - - void Create(string name, string displayName, string pathName, ServiceType serviceType, ErrorControl errorControl, string startMode, bool desktopInteract, string[] serviceDependencies); - - Win32Service? Select(string name); - } - - // https://docs.microsoft.com/windows/win32/cimwin32prov/win32-service - public interface Win32Service : IWmiObject - { - bool Started { get; } - void Delete(); - void StartService(); - void StopService(); - } -} diff --git a/src/Test/winswTests/ServiceDescriptorTests.cs b/src/Test/winswTests/ServiceDescriptorTests.cs index aacfd17..301d7ec 100644 --- a/src/Test/winswTests/ServiceDescriptorTests.cs +++ b/src/Test/winswTests/ServiceDescriptorTests.cs @@ -1,9 +1,9 @@ using System; using System.Diagnostics; +using System.ServiceProcess; using NUnit.Framework; using winsw; using winswTests.Util; -using WMI; namespace winswTests { @@ -44,7 +44,7 @@ $@" [Test] public void DefaultStartMode() { - Assert.That(_extendedServiceDescriptor.StartMode, Is.EqualTo(StartMode.Automatic)); + Assert.That(_extendedServiceDescriptor.StartMode, Is.EqualTo(ServiceStartMode.Automatic)); } [Test] @@ -96,7 +96,7 @@ $@" "; _extendedServiceDescriptor = ServiceDescriptor.FromXML(seedXml); - Assert.That(_extendedServiceDescriptor.StartMode, Is.EqualTo(StartMode.Manual)); + Assert.That(_extendedServiceDescriptor.StartMode, Is.EqualTo(ServiceStartMode.Manual)); } [Test]