diff --git a/src/WinSW.Core/CommandException.cs b/src/WinSW.Core/CommandException.cs
new file mode 100644
index 0000000..5bb9514
--- /dev/null
+++ b/src/WinSW.Core/CommandException.cs
@@ -0,0 +1,22 @@
+using System;
+
+namespace WinSW
+{
+ internal sealed class CommandException : Exception
+ {
+ internal CommandException(Exception inner)
+ : base(inner.Message, inner)
+ {
+ }
+
+ internal CommandException(string message)
+ : base(message)
+ {
+ }
+
+ internal CommandException(string message, Exception inner)
+ : base(message, inner)
+ {
+ }
+ }
+}
diff --git a/src/WinSW.Core/Configuration/DefaultSettings.cs b/src/WinSW.Core/Configuration/DefaultSettings.cs
index a55d21e..3c116dc 100644
--- a/src/WinSW.Core/Configuration/DefaultSettings.cs
+++ b/src/WinSW.Core/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
{
@@ -14,9 +14,9 @@ namespace WinSW.Configuration
{
public static LogDefaults DefaultLogSettings { get; } = new LogDefaults();
- public string Id => throw new InvalidOperationException(nameof(this.Id) + " must be specified.");
+ public string Name => throw new InvalidOperationException(nameof(this.Name) + " must be specified.");
- public string Caption => throw new InvalidOperationException(nameof(this.Caption) + " must be specified.");
+ public string DisplayName => throw new InvalidOperationException(nameof(this.DisplayName) + " must be specified.");
public string Description => throw new InvalidOperationException(nameof(this.Description) + " must be specified.");
@@ -49,7 +49,7 @@ namespace WinSW.Configuration
public bool StopParentProcessFirst => true;
// Service management
- public StartMode StartMode => StartMode.Automatic;
+ public ServiceStartMode StartMode => ServiceStartMode.Automatic;
public bool DelayedAutoStart => false;
diff --git a/src/WinSW.Core/Configuration/IWinSWConfiguration.cs b/src/WinSW.Core/Configuration/IWinSWConfiguration.cs
index 7c6080f..b064097 100644
--- a/src/WinSW.Core/Configuration/IWinSWConfiguration.cs
+++ b/src/WinSW.Core/Configuration/IWinSWConfiguration.cs
@@ -1,17 +1,17 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.ServiceProcess;
using System.Xml;
-using WMI;
namespace WinSW.Configuration
{
public interface IWinSWConfiguration
{
// TODO: Document the parameters && refactor
- string Id { get; }
+ string Name { get; }
- string Caption { get; }
+ string DisplayName { get; }
string Description { get; }
@@ -44,7 +44,7 @@ namespace WinSW.Configuration
bool StopParentProcessFirst { get; }
// Service management
- StartMode StartMode { get; }
+ ServiceStartMode StartMode { get; }
string[] ServiceDependencies { get; }
diff --git a/src/WinSW.Core/Configuration/YamlConfiguration.cs b/src/WinSW.Core/Configuration/YamlConfiguration.cs
index 3226bce..009c614 100644
--- a/src/WinSW.Core/Configuration/YamlConfiguration.cs
+++ b/src/WinSW.Core/Configuration/YamlConfiguration.cs
@@ -2,10 +2,10 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
+using System.ServiceProcess;
using System.Xml;
using WinSW.Native;
using WinSW.Util;
-using WMI;
using YamlDotNet.Serialization;
using static WinSW.Download;
@@ -458,7 +458,7 @@ namespace WinSW.Configuration
return Environment.ExpandEnvironmentVariables(str);
}
- public string Id => this.IdYaml is null ? this.Defaults.Id : ExpandEnv(this.IdYaml);
+ public string Name => this.IdYaml is null ? this.Defaults.Name : ExpandEnv(this.IdYaml);
public string Description => this.DescriptionYaml is null ? this.Defaults.Description : ExpandEnv(this.DescriptionYaml);
@@ -468,7 +468,7 @@ namespace WinSW.Configuration
this.Defaults.ExecutablePath :
ExpandEnv(this.ExecutablePathYaml);
- public string Caption => this.NameYaml is null ? this.Defaults.Caption : ExpandEnv(this.NameYaml);
+ public string DisplayName => this.NameYaml is null ? this.Defaults.DisplayName : ExpandEnv(this.NameYaml);
public bool HideWindow => this.HideWindowYaml is null ? this.Defaults.HideWindow : (bool)this.HideWindowYaml;
@@ -482,7 +482,7 @@ namespace WinSW.Configuration
}
}
- public StartMode StartMode
+ public ServiceStartMode StartMode
{
get
{
@@ -495,12 +495,12 @@ namespace WinSW.Configuration
try
{
- return (StartMode)Enum.Parse(typeof(StartMode), p, true);
+ return (ServiceStartMode)Enum.Parse(typeof(ServiceStartMode), p, true);
}
catch
{
Console.WriteLine("Start mode in YAML 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/WinSW.Core/DynamicProxy.cs b/src/WinSW.Core/DynamicProxy.cs
deleted file mode 100644
index 9d7ec5a..0000000
--- a/src/WinSW.Core/DynamicProxy.cs
+++ /dev/null
@@ -1,205 +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();
-
- 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)
- {
- var objType = typeof(object);
- var handlerType = typeof(IProxyInvocationHandler);
-
- var 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
- var typeBuilder = ModuleBuilder.DefineType(
- dynamicTypeName, typeAttributes, objType, interfaces);
-
- // Define a member variable to hold the delegate
- var handlerField = typeBuilder.DefineField(
- HandlerName, handlerType, FieldAttributes.Private | FieldAttributes.InitOnly);
-
- // build a constructor that takes the delegate object as the only argument
- var baseConstructor = objType.GetConstructor(Type.EmptyTypes)!;
- var delegateConstructor = typeBuilder.DefineConstructor(
- MethodAttributes.Public, CallingConventions.Standard, new Type[] { handlerType });
-
- var 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 (var 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)
- {
- var interfaceMethods = interfaceType.GetMethods();
-
- for (int i = 0; i < interfaceMethods.Length; i++)
- {
- var methodInfo = interfaceMethods[i];
-
- // Get the method parameters since we need to create an array
- // of parameter types
- var methodParams = methodInfo.GetParameters();
- int numOfParams = methodParams.Length;
- var 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
- var methodBuilder = typeBuilder.DefineMethod(
- methodInfo.Name,
- /*MethodAttributes.Public | MethodAttributes.Virtual | */ methodInfo.Attributes & ~MethodAttributes.Abstract,
- CallingConventions.Standard,
- methodInfo.ReturnType,
- methodParameters);
-
- var 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 (var parentType in interfaceType.GetInterfaces())
- {
- GenerateMethod(parentType, handlerField, typeBuilder);
- }
- }
- }
-}
diff --git a/src/WinSW.Core/Extensions/WinSWExtensionManager.cs b/src/WinSW.Core/Extensions/WinSWExtensionManager.cs
index a81f168..18fdebe 100644
--- a/src/WinSW.Core/Extensions/WinSWExtensionManager.cs
+++ b/src/WinSW.Core/Extensions/WinSWExtensionManager.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.IO;
using System.Xml;
using log4net;
using WinSW.Configuration;
diff --git a/src/WinSW.Core/Native/Errors.cs b/src/WinSW.Core/Native/Errors.cs
index 490f289..a45f76f 100644
--- a/src/WinSW.Core/Native/Errors.cs
+++ b/src/WinSW.Core/Native/Errors.cs
@@ -7,6 +7,11 @@ namespace WinSW.Native
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_SERVICE_EXISTS = 1073;
internal const int ERROR_CANCELLED = 1223;
}
}
diff --git a/src/WinSW.Core/Native/Security.cs b/src/WinSW.Core/Native/Security.cs
index 92f51a8..3f30055 100644
--- a/src/WinSW.Core/Native/Security.cs
+++ b/src/WinSW.Core/Native/Security.cs
@@ -41,7 +41,7 @@ namespace WinSW.Native
{
if (!LookupAccountName(null, accountName, sid, ref sidSize, domainName, ref domainNameLength, out _))
{
- Throw.Win32Exception("Failed to find the account.");
+ Throw.Command.Win32Exception("Failed to find the account.");
}
return sid;
diff --git a/src/WinSW.Core/Native/Service.cs b/src/WinSW.Core/Native/Service.cs
index cdc8692..d941498 100644
--- a/src/WinSW.Core/Native/Service.cs
+++ b/src/WinSW.Core/Native/Service.cs
@@ -1,5 +1,8 @@
using System;
+using System.Runtime.InteropServices;
using System.Security.AccessControl;
+using System.ServiceProcess;
+using System.Text;
using static WinSW.Native.ServiceApis;
namespace WinSW.Native
@@ -52,28 +55,72 @@ namespace WinSW.Native
private ServiceManager(IntPtr handle) => this.handle = handle;
- internal static ServiceManager Open()
+ internal static ServiceManager Open(ServiceManagerAccess access = ServiceManagerAccess.All)
{
- var handle = OpenSCManager(null, null, ServiceManagerAccess.ALL_ACCESS);
+ var handle = OpenSCManager(null, null, access);
if (handle == IntPtr.Zero)
{
- Throw.Win32Exception("Failed to open the service control manager database.");
+ Throw.Command.Win32Exception("Failed to open the service control manager database.");
}
return new ServiceManager(handle);
}
- internal Service OpenService(string serviceName)
+ internal Service CreateService(
+ string serviceName,
+ string displayName,
+ bool interactive,
+ ServiceStartMode startMode,
+ string executablePath,
+ string[] dependencies,
+ string? username,
+ string? password)
{
- var serviceHandle = ServiceApis.OpenService(this.handle, serviceName, ServiceAccess.ALL_ACCESS);
+ var handle = ServiceApis.CreateService(
+ this.handle,
+ serviceName,
+ displayName,
+ ServiceAccess.All,
+ ServiceType.Win32OwnProcess | (interactive ? ServiceType.InteractiveProcess : default),
+ startMode,
+ ServiceErrorControl.Normal,
+ executablePath,
+ default,
+ default,
+ Service.GetNativeDependencies(dependencies),
+ username,
+ password);
+ if (handle == IntPtr.Zero)
+ {
+ Throw.Command.Win32Exception("Failed to create service.");
+ }
+
+ return new Service(handle);
+ }
+
+ internal Service OpenService(string serviceName, ServiceAccess access = ServiceAccess.All)
+ {
+ var serviceHandle = ServiceApis.OpenService(this.handle, serviceName, access);
if (serviceHandle == IntPtr.Zero)
{
- Throw.Win32Exception("Failed to open the service.");
+ Throw.Command.Win32Exception("Failed to open the service.");
}
return new Service(serviceHandle);
}
+ internal bool ServiceExists(string serviceName)
+ {
+ var serviceHandle = ServiceApis.OpenService(this.handle, serviceName, ServiceAccess.All);
+ if (serviceHandle == IntPtr.Zero)
+ {
+ return false;
+ }
+
+ _ = CloseServiceHandle(this.handle);
+ return true;
+ }
+
public void Dispose()
{
if (this.handle != IntPtr.Zero)
@@ -91,6 +138,67 @@ namespace WinSW.Native
internal Service(IntPtr handle) => this.handle = handle;
+ internal ServiceControllerStatus Status
+ {
+ get
+ {
+ if (!QueryServiceStatus(this.handle, out var status))
+ {
+ Throw.Command.Win32Exception("Failed to query service status.");
+ }
+
+ return status.CurrentState;
+ }
+ }
+
+ internal static StringBuilder? GetNativeDependencies(string[] dependencies)
+ {
+ 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');
+ }
+
+ return array;
+ }
+
+ internal void SetStatus(IntPtr statusHandle, ServiceControllerStatus state)
+ {
+ if (!QueryServiceStatus(this.handle, out var status))
+ {
+ Throw.Command.Win32Exception("Failed to query service status.");
+ }
+
+ status.CheckPoint = 0;
+ status.WaitHint = 0;
+ status.CurrentState = state;
+
+ if (!SetServiceStatus(statusHandle, status))
+ {
+ Throw.Command.Win32Exception("Failed to set service status.");
+ }
+ }
+
+ internal void Delete()
+ {
+ if (!DeleteService(this.handle))
+ {
+ Throw.Command.Win32Exception("Failed to delete service.");
+ }
+ }
+
internal void SetDescription(string description)
{
if (!ChangeServiceConfig2(
@@ -98,7 +206,7 @@ namespace WinSW.Native
ServiceConfigInfoLevels.DESCRIPTION,
new SERVICE_DESCRIPTION { Description = description }))
{
- Throw.Win32Exception("Failed to configure the description.");
+ Throw.Command.Win32Exception("Failed to configure the description.");
}
}
@@ -118,7 +226,7 @@ namespace WinSW.Native
Actions = actionsPtr,
}))
{
- Throw.Win32Exception("Failed to configure the failure actions.");
+ Throw.Command.Win32Exception("Failed to configure the failure actions.");
}
}
}
@@ -130,7 +238,7 @@ namespace WinSW.Native
ServiceConfigInfoLevels.DELAYED_AUTO_START_INFO,
new SERVICE_DELAYED_AUTO_START_INFO { DelayedAutostart = enabled }))
{
- Throw.Win32Exception("Failed to configure the delayed auto-start setting.");
+ Throw.Command.Win32Exception("Failed to configure the delayed auto-start setting.");
}
}
@@ -140,7 +248,7 @@ namespace WinSW.Native
securityDescriptor.GetBinaryForm(securityDescriptorBytes, 0);
if (!SetServiceObjectSecurity(this.handle, SecurityInfos.DiscretionaryAcl, securityDescriptorBytes))
{
- Throw.Win32Exception("Failed to configure the security descriptor.");
+ Throw.Command.Win32Exception("Failed to configure the security descriptor.");
}
}
diff --git a/src/WinSW.Core/Native/ServiceApis.cs b/src/WinSW.Core/Native/ServiceApis.cs
index 42d0e7b..9575609 100644
--- a/src/WinSW.Core/Native/ServiceApis.cs
+++ b/src/WinSW.Core/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,16 +20,38 @@ 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? loadOrderGroup,
+ 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);
- [DllImport(Libraries.Advapi32)]
+ [DllImport(Libraries.Advapi32, SetLastError = true)]
internal static extern bool SetServiceStatus(IntPtr serviceStatusHandle, in SERVICE_STATUS serviceStatus);
// SERVICE_
@@ -35,27 +59,27 @@ namespace WinSW.Native
[Flags]
internal enum ServiceAccess : uint
{
- QUERY_CONFIG = 0x0001,
- CHANGE_CONFIG = 0x0002,
- QUERY_STATUS = 0x0004,
- ENUMERATE_DEPENDENTS = 0x0008,
- START = 0x0010,
- STOP = 0x0020,
- PAUSE_CONTINUE = 0x0040,
- INTERROGATE = 0x0080,
- USER_DEFINED_CONTROL = 0x0100,
+ QueryConfig = 0x0001,
+ ChangeConfig = 0x0002,
+ QueryStatus = 0x0004,
+ EnumerateDependents = 0x0008,
+ Start = 0x0010,
+ Stop = 0x0020,
+ PauseContinue = 0x0040,
+ Interrogate = 0x0080,
+ UserDefinedControl = 0x0100,
- ALL_ACCESS =
+ All =
SecurityApis.StandardAccess.REQUIRED |
- QUERY_CONFIG |
- CHANGE_CONFIG |
- QUERY_STATUS |
- ENUMERATE_DEPENDENTS |
- START |
- STOP |
- PAUSE_CONTINUE |
- INTERROGATE |
- USER_DEFINED_CONTROL,
+ QueryConfig |
+ ChangeConfig |
+ QueryStatus |
+ EnumerateDependents |
+ Start |
+ Stop |
+ PauseContinue |
+ Interrogate |
+ UserDefinedControl,
}
// SERVICE_CONFIG_
@@ -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_
@@ -91,21 +111,42 @@ namespace WinSW.Native
[Flags]
internal enum ServiceManagerAccess : uint
{
- CONNECT = 0x0001,
- CREATE_SERVICE = 0x0002,
- ENUMERATE_SERVICE = 0x0004,
- LOCK = 0x0008,
- QUERY_LOCK_STATUS = 0x0010,
- MODIFY_BOOT_CONFIG = 0x0020,
+ Connect = 0x0001,
+ CreateService = 0x0002,
+ EnumerateService = 0x0004,
+ Lock = 0x0008,
+ QueryLockStatus = 0x0010,
+ ModifyBootConfig = 0x0020,
- ALL_ACCESS =
+ All =
SecurityApis.StandardAccess.REQUIRED |
- CONNECT |
- CREATE_SERVICE |
- ENUMERATE_SERVICE |
- LOCK |
- QUERY_LOCK_STATUS |
- MODIFY_BOOT_CONFIG,
+ Connect |
+ CreateService |
+ EnumerateService |
+ Lock |
+ QueryLockStatus |
+ ModifyBootConfig,
+ }
+
+ // SERVICE_
+ internal enum ServiceState : uint
+ {
+ Active = 0x00000001,
+ Inactive = 0x00000002,
+ All = 0x00000003,
+ }
+
+ internal unsafe struct QUERY_SERVICE_CONFIG
+ {
+ public ServiceType ServiceType;
+ public ServiceStartMode StartType;
+ public ServiceErrorControl ErrorControl;
+ public char* BinaryPathName;
+ public char* LoadOrderGroup;
+ public uint TagId;
+ public char* Dependencies;
+ public char* ServiceStartName;
+ public char* DisplayName;
}
internal struct SERVICE_DELAYED_AUTO_START_INFO
@@ -121,19 +162,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 ServiceType ServiceType;
+ public ServiceControllerStatus CurrentState;
public int ControlsAccepted;
public int Win32ExitCode;
public int ServiceSpecificExitCode;
diff --git a/src/WinSW.Core/Native/Throw.cs b/src/WinSW.Core/Native/Throw.cs
index 7fe07d4..1674096 100644
--- a/src/WinSW.Core/Native/Throw.cs
+++ b/src/WinSW.Core/Native/Throw.cs
@@ -1,16 +1,74 @@
-using System.ComponentModel;
+using System;
+using System.ComponentModel;
using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
namespace WinSW.Native
{
internal static class Throw
{
- internal static void Win32Exception(string message)
+ internal static class Command
{
- var inner = new Win32Exception();
- Debug.Assert(inner.NativeErrorCode != 0);
- Debug.Assert(message.EndsWith("."));
- throw new Win32Exception(inner.NativeErrorCode, message + ' ' + inner.Message);
+ [DoesNotReturn]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ internal static void Exception(Exception inner)
+ {
+ throw new CommandException(inner);
+ }
+
+ [DoesNotReturn]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ internal static void Exception(string message)
+ {
+ Debug.Assert(message.EndsWith("."));
+ throw new CommandException(message);
+ }
+
+ [DoesNotReturn]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ internal static void Exception(string message, Exception inner)
+ {
+ Debug.Assert(message.EndsWith("."));
+ throw new CommandException(message + ' ' + inner.Message, inner);
+ }
+
+ [DoesNotReturn]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ internal static void Win32Exception(int error)
+ {
+ Debug.Assert(error != 0);
+ throw new CommandException(new Win32Exception(error));
+ }
+
+ [DoesNotReturn]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ internal static void Win32Exception(int error, string message)
+ {
+ Debug.Assert(error != 0);
+ var inner = new Win32Exception(error);
+ Debug.Assert(message.EndsWith("."));
+ throw new CommandException(message + ' ' + inner.Message, inner);
+ }
+
+ [DoesNotReturn]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ internal static void Win32Exception()
+ {
+ var inner = new Win32Exception();
+ Debug.Assert(inner.NativeErrorCode != 0);
+ throw new CommandException(inner);
+ }
+
+ [DoesNotReturn]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ internal static void Win32Exception(string message)
+ {
+ var inner = new Win32Exception();
+ Debug.Assert(inner.NativeErrorCode != 0);
+ Debug.Assert(message.EndsWith("."));
+ throw new CommandException(message + ' ' + inner.Message, inner);
+ }
}
}
}
diff --git a/src/WinSW.Core/NullableAttributes.cs b/src/WinSW.Core/NullableAttributes.cs
index a2a05fe..d4a0ad3 100644
--- a/src/WinSW.Core/NullableAttributes.cs
+++ b/src/WinSW.Core/NullableAttributes.cs
@@ -1,13 +1,25 @@
-#if !NETCOREAPP
-#pragma warning disable SA1502 // Element should not be on a single line
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#if !NET
namespace System.Diagnostics.CodeAnalysis
{
/// Specifies that null is allowed as an input even if the corresponding type disallows it.
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
- internal sealed class AllowNullAttribute : Attribute { }
+ internal sealed class AllowNullAttribute : Attribute
+ {
+ }
/// Specifies that an output may be null even if the corresponding type disallows it.
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
- internal sealed class MaybeNullAttribute : Attribute { }
+ internal sealed class MaybeNullAttribute : Attribute
+ {
+ }
+
+ /// Applied to a method that will never return under any circumstance.
+ [AttributeUsage(AttributeTargets.Method, Inherited = false)]
+ internal sealed class DoesNotReturnAttribute : Attribute
+ {
+ }
}
#endif
diff --git a/src/WinSW.Core/ServiceDescriptor.cs b/src/WinSW.Core/ServiceDescriptor.cs
index 3216760..109be12 100644
--- a/src/WinSW.Core/ServiceDescriptor.cs
+++ b/src/WinSW.Core/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
{
@@ -56,13 +56,13 @@ namespace WinSW
Environment.SetEnvironmentVariable("BASE", d.FullName);
// ditto for ID
- Environment.SetEnvironmentVariable("SERVICE_ID", this.Id);
+ Environment.SetEnvironmentVariable("SERVICE_ID", this.Name);
// New name
Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameExecutablePath, this.ExecutablePath);
// Also inject system environment variables
- Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameServiceId, this.Id);
+ Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameServiceId, this.Name);
this.environmentVariables = this.LoadEnvironmentVariables();
}
@@ -455,16 +455,16 @@ namespace WinSW
}
}
- public string Id => this.SingleElement("id");
+ public string Name => this.SingleElement("id");
- public string Caption => this.SingleElement("name");
+ public string DisplayName => this.SingleElement("name");
public string Description => this.SingleElement("description");
///
/// Start mode of the Service
///
- public StartMode StartMode
+ public ServiceStartMode StartMode
{
get
{
@@ -476,12 +476,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);
}
@@ -607,7 +607,7 @@ namespace WinSW
return false;
}
- public ServiceAccount ServiceAccount
+ public Configuration.ServiceAccount ServiceAccount
{
get
{
diff --git a/src/WinSW.Core/ServiceDescriptorYaml.cs b/src/WinSW.Core/ServiceDescriptorYaml.cs
index e982979..5305e72 100644
--- a/src/WinSW.Core/ServiceDescriptorYaml.cs
+++ b/src/WinSW.Core/ServiceDescriptorYaml.cs
@@ -26,13 +26,13 @@ namespace WinSW
Environment.SetEnvironmentVariable("BASE", d.FullName);
// ditto for ID
- Environment.SetEnvironmentVariable("SERVICE_ID", this.Configurations.Id);
+ Environment.SetEnvironmentVariable("SERVICE_ID", this.Configurations.Name);
// New name
Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameExecutablePath, Defaults.ExecutablePath);
// Also inject system environment variables
- Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameServiceId, this.Configurations.Id);
+ Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameServiceId, this.Configurations.Name);
this.Configurations.LoadEnvironmentVariables();
}
diff --git a/src/WinSW.Core/WinSW.Core.csproj b/src/WinSW.Core/WinSW.Core.csproj
index 7ca8c79..d64883e 100644
--- a/src/WinSW.Core/WinSW.Core.csproj
+++ b/src/WinSW.Core/WinSW.Core.csproj
@@ -19,9 +19,8 @@
-
-
-
+
+
@@ -31,12 +30,12 @@
+
-
-
+
diff --git a/src/WinSW.Core/Wmi.cs b/src/WinSW.Core/Wmi.cs
deleted file mode 100644
index ed68947..0000000
--- a/src/WinSW.Core/Wmi.cs
+++ /dev/null
@@ -1,227 +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)
- {
- this.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) => this.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()
- {
- var 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)
- {
- var 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 var wmiParameters = arguments.Length == 0 ? null :
- this.GetMethodParameters(this.wmiObject, methodName, method.GetParameters(), arguments);
- using var 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)
- {
- var methodParameters = method.GetParameters();
-
- if (method.Name == nameof(IWin32Services.Select))
- {
- // select method to find instances
- var 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 var searcher = new ManagementObjectSearcher(this.wmiClass.Scope, new ObjectQuery(query.ToString()));
- using var 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 var wmiParameters = arguments.Length == 0 ? null :
- this.GetMethodParameters(this.wmiClass, methodName, methodParameters, arguments);
- using var 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
- {
- var 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/WinSW.Core/WmiSchema.cs b/src/WinSW.Core/WmiSchema.cs
deleted file mode 100644
index a9e962f..0000000
--- a/src/WinSW.Core/WmiSchema.cs
+++ /dev/null
@@ -1,72 +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 IWin32Services : 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);
-
- IWin32Service? Select(string name);
- }
-
- // https://docs.microsoft.com/windows/win32/cimwin32prov/win32-service
- public interface IWin32Service : IWmiObject
- {
- bool Started { get; }
-
- void Delete();
-
- void StartService();
-
- void StopService();
- }
-}
diff --git a/src/WinSW.Plugins/RunawayProcessKillerExtension.cs b/src/WinSW.Plugins/RunawayProcessKillerExtension.cs
index 648648d..85e1434 100644
--- a/src/WinSW.Plugins/RunawayProcessKillerExtension.cs
+++ b/src/WinSW.Plugins/RunawayProcessKillerExtension.cs
@@ -1,6 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Text;
@@ -188,7 +186,7 @@ namespace WinSW.Plugins
this.Pidfile = XmlHelper.SingleElement(node, "pidfile", false)!;
this.StopTimeout = TimeSpan.FromMilliseconds(int.Parse(XmlHelper.SingleElement(node, "stopTimeout", false)!));
this.StopParentProcessFirst = bool.Parse(XmlHelper.SingleElement(node, "stopParentFirst", false)!);
- this.ServiceId = descriptor.Id;
+ this.ServiceId = descriptor.Name;
// TODO: Consider making it documented
string? checkWinSWEnvironmentVariable = XmlHelper.SingleElement(node, "checkWinSWEnvironmentVariable", true);
this.CheckWinSWEnvironmentVariable = checkWinSWEnvironmentVariable is null ? true : bool.Parse(checkWinSWEnvironmentVariable);
diff --git a/src/WinSW.Tests/Configuration/ExamplesTest.cs b/src/WinSW.Tests/Configuration/ExamplesTest.cs
index 29ee0f2..f495f23 100644
--- a/src/WinSW.Tests/Configuration/ExamplesTest.cs
+++ b/src/WinSW.Tests/Configuration/ExamplesTest.cs
@@ -19,8 +19,8 @@ namespace winswTests.Configuration
{
var desc = Load("allOptions");
- Assert.That(desc.Id, Is.EqualTo("myapp"));
- Assert.That(desc.Caption, Is.EqualTo("MyApp Service (powered by WinSW)"));
+ Assert.That(desc.Name, Is.EqualTo("myapp"));
+ Assert.That(desc.DisplayName, Is.EqualTo("MyApp Service (powered by WinSW)"));
Assert.That(desc.Description, Is.EqualTo("This service is a service created from a sample configuration"));
Assert.That(desc.Executable, Is.EqualTo("%BASE%\\myExecutable.exe"));
@@ -32,8 +32,8 @@ namespace winswTests.Configuration
{
var desc = Load("minimal");
- Assert.That(desc.Id, Is.EqualTo("myapp"));
- Assert.That(desc.Caption, Is.EqualTo("MyApp Service (powered by WinSW)"));
+ Assert.That(desc.Name, Is.EqualTo("myapp"));
+ Assert.That(desc.DisplayName, Is.EqualTo("MyApp Service (powered by WinSW)"));
Assert.That(desc.Description, Is.EqualTo("This service is a service created from a minimal configuration"));
Assert.That(desc.Executable, Is.EqualTo("%BASE%\\myExecutable.exe"));
diff --git a/src/WinSW.Tests/Extensions/RunawayProcessKillerTest.cs b/src/WinSW.Tests/Extensions/RunawayProcessKillerTest.cs
index f534f47..3b3b119 100644
--- a/src/WinSW.Tests/Extensions/RunawayProcessKillerTest.cs
+++ b/src/WinSW.Tests/Extensions/RunawayProcessKillerTest.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using NUnit.Framework;
diff --git a/src/WinSW.Tests/ServiceDescriptorTests.cs b/src/WinSW.Tests/ServiceDescriptorTests.cs
index e3cb2a7..c64cf68 100644
--- a/src/WinSW.Tests/ServiceDescriptorTests.cs
+++ b/src/WinSW.Tests/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(this._extendedServiceDescriptor.StartMode, Is.EqualTo(StartMode.Automatic));
+ Assert.That(this._extendedServiceDescriptor.StartMode, Is.EqualTo(ServiceStartMode.Automatic));
}
[Test]
@@ -96,7 +96,7 @@ $@"
";
this._extendedServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
- Assert.That(this._extendedServiceDescriptor.StartMode, Is.EqualTo(StartMode.Manual));
+ Assert.That(this._extendedServiceDescriptor.StartMode, Is.EqualTo(ServiceStartMode.Manual));
}
[Test]
diff --git a/src/WinSW.Tests/ServiceDescriptorYamlTest.cs b/src/WinSW.Tests/ServiceDescriptorYamlTest.cs
index 2b65dcc..44b7235 100644
--- a/src/WinSW.Tests/ServiceDescriptorYamlTest.cs
+++ b/src/WinSW.Tests/ServiceDescriptorYamlTest.cs
@@ -1,9 +1,9 @@
using System;
using System.Diagnostics;
+using System.ServiceProcess;
using NUnit.Framework;
using WinSW;
using WinSW.Configuration;
-using WMI;
namespace winswTests
{
@@ -43,7 +43,7 @@ workingdirectory: {ExpectedWorkingDirectory}";
[Test]
public void DefaultStartMode()
{
- Assert.That(this._extendedServiceDescriptor.StartMode, Is.EqualTo(StartMode.Automatic));
+ Assert.That(this._extendedServiceDescriptor.StartMode, Is.EqualTo(ServiceStartMode.Automatic));
}
[Test]
@@ -73,7 +73,7 @@ arguments: My Arguments
startMode: manual";
this._extendedServiceDescriptor = ServiceDescriptorYaml.FromYaml(yaml).Configurations;
- Assert.That(this._extendedServiceDescriptor.StartMode, Is.EqualTo(StartMode.Manual));
+ Assert.That(this._extendedServiceDescriptor.StartMode, Is.EqualTo(ServiceStartMode.Manual));
}
[Test]
@@ -470,7 +470,7 @@ description: This is test winsw";
Assert.That(() =>
{
- _ = ServiceDescriptorYaml.FromYaml(yml).Configurations.Id;
+ _ = ServiceDescriptorYaml.FromYaml(yml).Configurations.Name;
}, Throws.TypeOf());
}
diff --git a/src/WinSW.Tests/Util/ServiceDescriptorAssert.cs b/src/WinSW.Tests/Util/ServiceDescriptorAssert.cs
index 14e9254..e75704b 100644
--- a/src/WinSW.Tests/Util/ServiceDescriptorAssert.cs
+++ b/src/WinSW.Tests/Util/ServiceDescriptorAssert.cs
@@ -1,5 +1,4 @@
using System.Collections.Generic;
-using System.Reflection;
using NUnit.Framework;
using WinSW;
using WinSW.Configuration;
@@ -55,14 +54,14 @@ namespace winswTests.Util
get
{
var properties = AllProperties;
- properties.Remove("Id");
- properties.Remove("Caption");
- properties.Remove("Description");
- properties.Remove("Executable");
- properties.Remove("BaseName");
- properties.Remove("BasePath");
- properties.Remove("Log");
- properties.Remove("ServiceAccount");
+ properties.Remove(nameof(IWinSWConfiguration.Name));
+ properties.Remove(nameof(IWinSWConfiguration.DisplayName));
+ properties.Remove(nameof(IWinSWConfiguration.Description));
+ properties.Remove(nameof(IWinSWConfiguration.Executable));
+ properties.Remove(nameof(IWinSWConfiguration.BaseName));
+ properties.Remove(nameof(IWinSWConfiguration.BasePath));
+ properties.Remove(nameof(IWinSWConfiguration.Log));
+ properties.Remove(nameof(IWinSWConfiguration.ServiceAccount));
return properties;
}
}
diff --git a/src/WinSW/FormatExtensions.cs b/src/WinSW/FormatExtensions.cs
new file mode 100644
index 0000000..8fb3319
--- /dev/null
+++ b/src/WinSW/FormatExtensions.cs
@@ -0,0 +1,20 @@
+using System.ServiceProcess;
+using WinSW.Configuration;
+
+namespace WinSW
+{
+ internal static class FormatExtensions
+ {
+ internal static string Format(IWinSWConfiguration config)
+ {
+ string name = config.Name;
+ string displayName = config.DisplayName;
+ return $"{(string.IsNullOrEmpty(displayName) ? name : displayName)} ({name})";
+ }
+
+ internal static string Format(ServiceController controller)
+ {
+ return $"{controller.DisplayName} ({controller.ServiceName})";
+ }
+ }
+}
diff --git a/src/WinSW/Program.cs b/src/WinSW/Program.cs
index ab7beb6..a9eecad 100644
--- a/src/WinSW/Program.cs
+++ b/src/WinSW/Program.cs
@@ -2,12 +2,10 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
using System.IO;
#if NET
using System.Reflection;
#endif
-using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;
using System.ServiceProcess;
@@ -21,8 +19,10 @@ using log4net.Layout;
using WinSW.Configuration;
using WinSW.Logging;
using WinSW.Native;
-using WMI;
-using ServiceType = WMI.ServiceType;
+using static WinSW.FormatExtensions;
+using static WinSW.ServiceControllerExtension;
+using static WinSW.Native.ServiceApis;
+using TimeoutException = System.ServiceProcess.TimeoutException;
namespace WinSW
{
@@ -35,7 +35,6 @@ namespace WinSW
try
{
Run(args);
- Log.Debug("Completed. Exit code is 0");
return 0;
}
catch (InvalidDataException e)
@@ -45,16 +44,27 @@ namespace WinSW
Console.Error.WriteLine(message);
return -1;
}
- catch (WmiException e)
+ catch (CommandException e)
{
- Log.Fatal("WMI Operation failure: " + e.ErrorCode, e);
- Console.Error.WriteLine(e);
- return (int)e.ErrorCode;
+ string message = e.Message;
+ Log.Fatal(message);
+ return e.InnerException is Win32Exception inner ? inner.NativeErrorCode : -1;
+ }
+ catch (InvalidOperationException e) when (e.InnerException is Win32Exception inner)
+ {
+ string message = e.Message;
+ Log.Fatal(message);
+ return inner.NativeErrorCode;
+ }
+ catch (Win32Exception e)
+ {
+ string message = e.Message;
+ Log.Fatal(message, e);
+ return e.NativeErrorCode;
}
catch (Exception e)
{
Log.Fatal("Unhandled exception", e);
- Console.Error.WriteLine(e);
return -1;
}
}
@@ -94,10 +104,6 @@ namespace WinSW
return;
}
- // Get service info for the future use
- var svcs = new WmiRoot().GetCollection();
- var svc = svcs.Select(descriptor.Id);
-
var args = new List(Array.AsReadOnly(argsArray));
if (args[0] == "/redirect")
{
@@ -148,11 +154,11 @@ namespace WinSW
return;
case "stop":
- Stop();
+ Stop(true);
return;
case "stopwait":
- StopWait();
+ Stop(false);
return;
case "restart":
@@ -201,14 +207,15 @@ namespace WinSW
return;
}
- Log.Info("Installing the service with id '" + descriptor.Id + "'");
+ Log.Info($"Installing service '{Format(descriptor)}'...");
- // Check if the service exists
- if (svc != null)
+ using var scm = ServiceManager.Open(ServiceManagerAccess.CreateService);
+
+ if (scm.ServiceExists(descriptor.Name))
{
- 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");
- throw new Exception("Installation failure: Service with id '" + descriptor.Id + "' already exists");
+
+ Log.Error($"A service with ID '{descriptor.Name}' already exists.");
+ Throw.Command.Win32Exception(Errors.ERROR_SERVICE_EXISTS, "Failed to install the service.");
}
string? username = null;
@@ -244,22 +251,21 @@ namespace WinSW
Security.AddServiceLogonRight(descriptor.ServiceAccount.ServiceAccountDomain!, descriptor.ServiceAccount.ServiceAccountName!);
}
- svcs.Create(
- descriptor.Id,
- descriptor.Caption,
- "\"" + descriptor.ExecutablePath + "\"",
- ServiceType.OwnProcess,
- ErrorControl.UserNotified,
- descriptor.StartMode.ToString(),
+ using var sc = scm.CreateService(
+ descriptor.Name,
+ descriptor.DisplayName,
descriptor.Interactive,
+ descriptor.StartMode,
+ $"\"{descriptor.ExecutablePath}\"",
+ descriptor.ServiceDependencies,
username,
- password,
- descriptor.ServiceDependencies);
+ password);
- using var scm = ServiceManager.Open();
- using var sc = scm.OpenService(descriptor.Id);
-
- sc.SetDescription(descriptor.Description);
+ string description = descriptor.Description;
+ if (description.Length != 0)
+ {
+ sc.SetDescription(description);
+ }
var actions = descriptor.FailureActions;
if (actions.Length > 0)
@@ -267,7 +273,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);
@@ -280,11 +286,13 @@ namespace WinSW
sc.SetSecurityDescriptor(new RawSecurityDescriptor(securityDescriptor));
}
- string eventLogSource = descriptor.Id;
+ string eventLogSource = descriptor.Name;
if (!EventLog.SourceExists(eventLogSource))
{
EventLog.CreateEventSource(eventLogSource, "Application");
}
+
+ Log.Info($"Service '{Format(descriptor)}' was installed successfully.");
}
void Uninstall()
@@ -295,40 +303,42 @@ namespace WinSW
return;
}
- 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");
- }
+ Log.Info($"Uninstalling service '{Format(descriptor)}'...");
+ using var scm = ServiceManager.Open(ServiceManagerAccess.Connect);
try
{
- svc.Delete();
+ using var sc = scm.OpenService(descriptor.Name);
+
+ if (sc.Status != ServiceControllerStatus.Stopped)
+ {
+ // We could fail the opeartion here, but it would be an incompatible change.
+ // So it is just a warning
+ Log.Warn($"Service '{Format(descriptor)}' is started. It may be impossible to uninstall it.");
+ }
+
+ sc.Delete();
+
+ Log.Info($"Service '{Format(descriptor)}' was uninstalled successfully.");
}
- catch (WmiException e)
+ catch (CommandException e) when (e.InnerException is Win32Exception inner)
{
- if (e.ErrorCode == ReturnValue.ServiceMarkedForDeletion)
+ switch (inner.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($"Service '{Format(descriptor)}' does not exist.");
+ 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(e.Message);
- throw;
+ // TODO: change the default behavior to Error?
+ break; // it's already uninstalled, so consider it a success
+
+ default:
+ Throw.Command.Exception("Failed to uninstall the service.", inner);
+ break;
+ }
}
}
@@ -340,30 +350,28 @@ namespace WinSW
return;
}
- Log.Info("Starting the service with id '" + descriptor.Id + "'");
- if (svc is null)
- {
- ThrowNoSuchService();
- }
+ using var svc = new ServiceController(descriptor.Name);
try
{
- svc.StartService();
+ Log.Info($"Starting service '{Format(svc)}'...");
+ svc.Start();
+
+ Log.Info($"Service '{Format(svc)}' started successfully.");
}
- catch (WmiException e)
+ catch (InvalidOperationException e)
+ when (e.InnerException is Win32Exception inner && inner.NativeErrorCode == Errors.ERROR_SERVICE_DOES_NOT_EXIST)
{
- if (e.ErrorCode == ReturnValue.ServiceAlreadyRunning)
- {
- Log.Info($"The service with ID '{descriptor.Id}' has already been started");
- }
- else
- {
- throw;
- }
+ Throw.Command.Exception(inner);
+ }
+ catch (InvalidOperationException e)
+ when (e.InnerException is Win32Exception inner && inner.NativeErrorCode == Errors.ERROR_SERVICE_ALREADY_RUNNING)
+ {
+ Log.Info($"Service '{Format(svc)}' has already started.");
}
}
- void Stop()
+ void Stop(bool noWait)
{
if (!elevated)
{
@@ -371,56 +379,37 @@ namespace WinSW
return;
}
- Log.Info("Stopping the service with id '" + descriptor.Id + "'");
- if (svc is null)
- {
- ThrowNoSuchService();
- }
+ using var svc = new ServiceController(descriptor.Name);
try
{
- svc.StopService();
- }
- catch (WmiException e)
- {
- if (e.ErrorCode == ReturnValue.ServiceCannotAcceptControl)
+ Log.Info($"Stopping service '{Format(svc)}'...");
+ svc.Stop();
+
+ if (!noWait)
{
- Log.Info($"The service with ID '{descriptor.Id}' is not running");
+ try
+ {
+ WaitForStatus(svc, ServiceControllerStatus.Stopped, ServiceControllerStatus.StopPending);
+ }
+ catch (TimeoutException)
+ {
+ Throw.Command.Exception("Failed to stop the service.");
+ }
}
- else
- {
- throw;
- }
- }
- }
- void StopWait()
- {
- if (!elevated)
+ Log.Info($"Service '{Format(svc)}' stopped successfully.");
+ }
+ catch (InvalidOperationException e)
+ when (e.InnerException is Win32Exception inner && inner.NativeErrorCode == Errors.ERROR_SERVICE_DOES_NOT_EXIST)
{
- Elevate();
- return;
+ Throw.Command.Exception(inner);
}
-
- Log.Info("Stopping the service with id '" + descriptor.Id + "'");
- if (svc is null)
+ catch (InvalidOperationException e)
+ when (e.InnerException is Win32Exception inner && inner.NativeErrorCode == Errors.ERROR_SERVICE_NOT_ACTIVE)
{
- ThrowNoSuchService();
+ Log.Info($"Service '{Format(svc)}' has already stopped.");
}
-
- if (svc.Started)
- {
- svc.StopService();
- }
-
- while (svc != null && svc.Started)
- {
- Log.Info("Waiting the service to stop...");
- Thread.Sleep(1000);
- svc = svcs.Select(descriptor.Id);
- }
-
- Log.Info("The service stopped.");
}
void Restart()
@@ -431,47 +420,115 @@ namespace WinSW
return;
}
- Log.Info("Restarting the service with id '" + descriptor.Id + "'");
- if (svc is null)
+
+ using var svc = new ServiceController(descriptor.Name);
+
+ List? startedDependentServices = null;
+
+ try
+ {
+ if (HasAnyStartedDependentService(svc))
+ {
+ startedDependentServices = new();
+ foreach (var service in svc.DependentServices)
+ {
+ if (service.Status != ServiceControllerStatus.Stopped)
+ {
+ startedDependentServices.Add(service);
+ }
+ }
+ }
+
+ Log.Info($"Stopping service '{Format(svc)}'...");
+ svc.Stop();
+
+ try
+ {
+ WaitForStatus(svc, ServiceControllerStatus.Stopped, ServiceControllerStatus.StopPending);
+ }
+ catch (TimeoutException)
+ {
+ Throw.Command.Exception("Failed to stop the service.");
+ }
+ }
+ catch (InvalidOperationException e)
+ when (e.InnerException is Win32Exception inner && inner.NativeErrorCode == Errors.ERROR_SERVICE_DOES_NOT_EXIST)
+ {
+ Throw.Command.Exception(inner);
+ }
+ catch (InvalidOperationException e)
+ when (e.InnerException is Win32Exception inner && inner.NativeErrorCode == Errors.ERROR_SERVICE_NOT_ACTIVE)
{
- ThrowNoSuchService();
}
- if (svc.Started)
+ Log.Info($"Starting service '{Format(svc)}'...");
+ svc.Start();
+
+ try
{
- svc.StopService();
+ WaitForStatus(svc, ServiceControllerStatus.Running, ServiceControllerStatus.StartPending);
+ }
+ catch (TimeoutException)
+ {
+ Throw.Command.Exception("Failed to start the service.");
}
- while (svc.Started)
+ if (startedDependentServices != null)
{
- Thread.Sleep(1000);
- svc = svcs.Select(descriptor.Id)!;
+ foreach (var service in startedDependentServices)
+ {
+ if (service.Status == ServiceControllerStatus.Stopped)
+ {
+ Log.Info($"Starting service '{Format(service)}'...");
+ service.Start();
+ }
+ }
}
- svc.StartService();
+ Log.Info($"Service '{Format(svc)}' restarted successfully.");
}
void RestartSelf()
{
if (!elevated)
{
- throw new UnauthorizedAccessException("Access is denied.");
+ Throw.Command.Win32Exception(Errors.ERROR_ACCESS_DENIED);
}
- Log.Info("Restarting the service with id '" + descriptor.Id + "'");
+ Log.Info("Restarting the service with id '" + descriptor.Name + "'");
// 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 var processInfo))
{
- throw new Exception("Failed to invoke restart: " + Marshal.GetLastWin32Error());
+ Throw.Command.Win32Exception("Failed to invoke restart.");
}
+
+ _ = HandleApis.CloseHandle(processInfo.ProcessHandle);
+ _ = HandleApis.CloseHandle(processInfo.ThreadHandle);
}
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.Name);
+ try
+ {
+ Console.WriteLine(svc.Status != ServiceControllerStatus.Stopped ? "Started" : "Stopped");
+ }
+ catch (InvalidOperationException e)
+ when (e.InnerException is Win32Exception inner && inner.NativeErrorCode == Errors.ERROR_SERVICE_DOES_NOT_EXIST)
+ {
+ Console.WriteLine("NonExistent");
+ }
}
void Test()
@@ -538,9 +595,6 @@ namespace WinSW
}
}
- [DoesNotReturn]
- private static void ThrowNoSuchService() => throw new WmiException(ReturnValue.NoSuchService);
-
private static void InitLoggers(IWinSWConfiguration descriptor, bool enableConsoleLogging)
{
// TODO: Make logging levels configurable
diff --git a/src/WinSW/ServiceControllerExtension.cs b/src/WinSW/ServiceControllerExtension.cs
new file mode 100644
index 0000000..dd76b06
--- /dev/null
+++ b/src/WinSW/ServiceControllerExtension.cs
@@ -0,0 +1,32 @@
+using System;
+using System.ServiceProcess;
+using TimeoutException = System.ServiceProcess.TimeoutException;
+
+namespace WinSW
+{
+ internal static class ServiceControllerExtension
+ {
+ ///
+ internal static void WaitForStatus(ServiceController serviceController, ServiceControllerStatus desiredStatus, ServiceControllerStatus pendingStatus)
+ {
+ var timeout = new TimeSpan(TimeSpan.TicksPerSecond);
+ for (; ; )
+ {
+ try
+ {
+ serviceController.WaitForStatus(desiredStatus, timeout);
+ break;
+ }
+ catch (TimeoutException)
+ when (serviceController.Status == desiredStatus || serviceController.Status == pendingStatus)
+ {
+ }
+ }
+ }
+
+ internal static bool HasAnyStartedDependentService(ServiceController serviceController)
+ {
+ return Array.Exists(serviceController.DependentServices, service => service.Status != ServiceControllerStatus.Stopped);
+ }
+ }
+}
diff --git a/src/WinSW/WinSW.csproj b/src/WinSW/WinSW.csproj
index 3688b2c..7d6e909 100644
--- a/src/WinSW/WinSW.csproj
+++ b/src/WinSW/WinSW.csproj
@@ -25,13 +25,8 @@
3.0.41
-
-
-
-
-
diff --git a/src/WinSW/WrapperService.cs b/src/WinSW/WrapperService.cs
index afe9938..fa51f20 100644
--- a/src/WinSW/WrapperService.cs
+++ b/src/WinSW/WrapperService.cs
@@ -20,8 +20,6 @@ namespace WinSW
{
public class WrapperService : ServiceBase, IEventLogger
{
- private ServiceApis.SERVICE_STATUS wrapperServiceStatus;
-
private readonly Process process = new();
private readonly IWinSWConfiguration descriptor;
@@ -61,7 +59,7 @@ namespace WinSW
public WrapperService(IWinSWConfiguration descriptor)
{
this.descriptor = descriptor;
- this.ServiceName = this.descriptor.Id;
+ this.ServiceName = this.descriptor.Name;
this.ExtensionManager = new WinSWExtensionManager(this.descriptor);
this.CanShutdown = true;
this.CanStop = true;
@@ -340,8 +338,8 @@ namespace WinSW
private void DoStop()
{
string? stopArguments = this.descriptor.StopArguments;
- this.LogEvent("Stopping " + this.descriptor.Id);
- Log.Info("Stopping " + this.descriptor.Id);
+ this.LogEvent("Stopping " + this.descriptor.Name);
+ Log.Info("Stopping " + this.descriptor.Name);
this.orderlyShutdown = true;
if (stopArguments is null)
@@ -359,7 +357,7 @@ namespace WinSW
}
else
{
- this.SignalShutdownPending();
+ this.SignalPending();
stopArguments += " " + this.descriptor.Arguments;
@@ -384,12 +382,12 @@ namespace WinSW
Console.Beep();
}
- Log.Info("Finished " + this.descriptor.Id);
+ Log.Info("Finished " + this.descriptor.Name);
}
private void WaitForProcessToExit(Process processoWait)
{
- this.SignalShutdownPending();
+ this.SignalPending();
int effectiveProcessWaitSleepTime;
if (this.descriptor.SleepTime.TotalMilliseconds > int.MaxValue)
@@ -409,7 +407,7 @@ namespace WinSW
while (!processoWait.WaitForExit(effectiveProcessWaitSleepTime))
{
- this.SignalShutdownPending();
+ this.SignalPending();
// WriteEvent("WaitForProcessToExit [repeat]");
}
}
@@ -421,7 +419,7 @@ namespace WinSW
// WriteEvent("WaitForProcessToExit [finished]");
}
- private void SignalShutdownPending()
+ private void SignalPending()
{
int effectiveWaitHint;
if (this.descriptor.WaitHint.TotalMilliseconds > int.MaxValue)
@@ -438,13 +436,12 @@ namespace WinSW
this.RequestAdditionalTime(effectiveWaitHint);
}
- private void SignalShutdownComplete()
+ private void SignalStopped()
{
- var handle = this.ServiceHandle;
- this.wrapperServiceStatus.CheckPoint++;
- // WriteEvent("SignalShutdownComplete " + wrapperServiceStatus.checkPoint + ":" + wrapperServiceStatus.waitHint);
- this.wrapperServiceStatus.CurrentState = ServiceApis.ServiceState.STOPPED;
- ServiceApis.SetServiceStatus(handle, this.wrapperServiceStatus);
+ using var scm = ServiceManager.Open();
+ using var sc = scm.OpenService(this.ServiceName, ServiceApis.ServiceAccess.QueryStatus);
+
+ sc.SetStatus(this.ServiceHandle, ServiceControllerStatus.Stopped);
}
private void StartProcess(Process processToStart, string arguments, string executable, LogHandler? logHandler)
@@ -468,7 +465,7 @@ namespace WinSW
// restart the service automatically
if (proc.ExitCode == 0)
{
- this.SignalShutdownComplete();
+ this.SignalStopped();
}
Environment.Exit(proc.ExitCode);