mirror of https://github.com/winsw/winsw
Backport command updates (#751)
parent
16c6aa9c08
commit
03930a23d2
|
@ -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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,8 +2,8 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.ServiceProcess;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using WMI;
|
|
||||||
|
|
||||||
namespace WinSW.Configuration
|
namespace WinSW.Configuration
|
||||||
{
|
{
|
||||||
|
@ -14,9 +14,9 @@ namespace WinSW.Configuration
|
||||||
{
|
{
|
||||||
public static LogDefaults DefaultLogSettings { get; } = new LogDefaults();
|
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.");
|
public string Description => throw new InvalidOperationException(nameof(this.Description) + " must be specified.");
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ namespace WinSW.Configuration
|
||||||
public bool StopParentProcessFirst => true;
|
public bool StopParentProcessFirst => true;
|
||||||
|
|
||||||
// Service management
|
// Service management
|
||||||
public StartMode StartMode => StartMode.Automatic;
|
public ServiceStartMode StartMode => ServiceStartMode.Automatic;
|
||||||
|
|
||||||
public bool DelayedAutoStart => false;
|
public bool DelayedAutoStart => false;
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.ServiceProcess;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using WMI;
|
|
||||||
|
|
||||||
namespace WinSW.Configuration
|
namespace WinSW.Configuration
|
||||||
{
|
{
|
||||||
public interface IWinSWConfiguration
|
public interface IWinSWConfiguration
|
||||||
{
|
{
|
||||||
// TODO: Document the parameters && refactor
|
// TODO: Document the parameters && refactor
|
||||||
string Id { get; }
|
string Name { get; }
|
||||||
|
|
||||||
string Caption { get; }
|
string DisplayName { get; }
|
||||||
|
|
||||||
string Description { get; }
|
string Description { get; }
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ namespace WinSW.Configuration
|
||||||
bool StopParentProcessFirst { get; }
|
bool StopParentProcessFirst { get; }
|
||||||
|
|
||||||
// Service management
|
// Service management
|
||||||
StartMode StartMode { get; }
|
ServiceStartMode StartMode { get; }
|
||||||
|
|
||||||
string[] ServiceDependencies { get; }
|
string[] ServiceDependencies { get; }
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,10 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.ServiceProcess;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using WinSW.Native;
|
using WinSW.Native;
|
||||||
using WinSW.Util;
|
using WinSW.Util;
|
||||||
using WMI;
|
|
||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
using static WinSW.Download;
|
using static WinSW.Download;
|
||||||
|
|
||||||
|
@ -458,7 +458,7 @@ namespace WinSW.Configuration
|
||||||
return Environment.ExpandEnvironmentVariables(str);
|
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);
|
public string Description => this.DescriptionYaml is null ? this.Defaults.Description : ExpandEnv(this.DescriptionYaml);
|
||||||
|
|
||||||
|
@ -468,7 +468,7 @@ namespace WinSW.Configuration
|
||||||
this.Defaults.ExecutablePath :
|
this.Defaults.ExecutablePath :
|
||||||
ExpandEnv(this.ExecutablePathYaml);
|
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;
|
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
|
get
|
||||||
{
|
{
|
||||||
|
@ -495,12 +495,12 @@ namespace WinSW.Configuration
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return (StartMode)Enum.Parse(typeof(StartMode), p, true);
|
return (ServiceStartMode)Enum.Parse(typeof(ServiceStartMode), p, true);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
Console.WriteLine("Start mode in YAML must be one of the following:");
|
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);
|
Console.WriteLine(sm);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,205 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Reflection.Emit;
|
|
||||||
|
|
||||||
namespace DynamicProxy
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Interface that a user defined proxy handler needs to implement. This interface
|
|
||||||
/// defines one method that gets invoked by the generated proxy.
|
|
||||||
/// </summary>
|
|
||||||
public interface IProxyInvocationHandler
|
|
||||||
{
|
|
||||||
/// <param name="proxy">The instance of the proxy</param>
|
|
||||||
/// <param name="method">The method info that can be used to invoke the actual method on the object implementation</param>
|
|
||||||
/// <param name="parameters">Parameters to pass to the method</param>
|
|
||||||
/// <returns>Object</returns>
|
|
||||||
object? Invoke(object proxy, MethodInfo method, object[] parameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// </summary>
|
|
||||||
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<string, Type> 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()!;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// <see cref="IProxyInvocationHandler.Invoke(object, MethodInfo, object[])"/>.
|
|
||||||
/// </summary>
|
|
||||||
private static readonly MethodInfo InvokeMethod = typeof(IProxyInvocationHandler).GetMethod(nameof(IProxyInvocationHandler.Invoke))!;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// <see cref="MethodBase.GetMethodFromHandle(RuntimeMethodHandle)"/>.
|
|
||||||
/// </summary>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using log4net;
|
using log4net;
|
||||||
using WinSW.Configuration;
|
using WinSW.Configuration;
|
||||||
|
|
|
@ -7,6 +7,11 @@ namespace WinSW.Native
|
||||||
internal const int ERROR_ACCESS_DENIED = 5;
|
internal const int ERROR_ACCESS_DENIED = 5;
|
||||||
internal const int ERROR_INVALID_HANDLE = 6;
|
internal const int ERROR_INVALID_HANDLE = 6;
|
||||||
internal const int ERROR_INVALID_PARAMETER = 7;
|
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;
|
internal const int ERROR_CANCELLED = 1223;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ namespace WinSW.Native
|
||||||
{
|
{
|
||||||
if (!LookupAccountName(null, accountName, sid, ref sidSize, domainName, ref domainNameLength, out _))
|
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;
|
return sid;
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Security.AccessControl;
|
using System.Security.AccessControl;
|
||||||
|
using System.ServiceProcess;
|
||||||
|
using System.Text;
|
||||||
using static WinSW.Native.ServiceApis;
|
using static WinSW.Native.ServiceApis;
|
||||||
|
|
||||||
namespace WinSW.Native
|
namespace WinSW.Native
|
||||||
|
@ -52,28 +55,72 @@ namespace WinSW.Native
|
||||||
|
|
||||||
private ServiceManager(IntPtr handle) => this.handle = handle;
|
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)
|
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);
|
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)
|
if (serviceHandle == IntPtr.Zero)
|
||||||
{
|
{
|
||||||
Throw.Win32Exception("Failed to open the service.");
|
Throw.Command.Win32Exception("Failed to open the service.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Service(serviceHandle);
|
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()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (this.handle != IntPtr.Zero)
|
if (this.handle != IntPtr.Zero)
|
||||||
|
@ -91,6 +138,67 @@ namespace WinSW.Native
|
||||||
|
|
||||||
internal Service(IntPtr handle) => this.handle = handle;
|
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)
|
internal void SetDescription(string description)
|
||||||
{
|
{
|
||||||
if (!ChangeServiceConfig2(
|
if (!ChangeServiceConfig2(
|
||||||
|
@ -98,7 +206,7 @@ namespace WinSW.Native
|
||||||
ServiceConfigInfoLevels.DESCRIPTION,
|
ServiceConfigInfoLevels.DESCRIPTION,
|
||||||
new SERVICE_DESCRIPTION { Description = 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,
|
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,
|
ServiceConfigInfoLevels.DELAYED_AUTO_START_INFO,
|
||||||
new SERVICE_DELAYED_AUTO_START_INFO { DelayedAutostart = enabled }))
|
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);
|
securityDescriptor.GetBinaryForm(securityDescriptorBytes, 0);
|
||||||
if (!SetServiceObjectSecurity(this.handle, SecurityInfos.DiscretionaryAcl, securityDescriptorBytes))
|
if (!SetServiceObjectSecurity(this.handle, SecurityInfos.DiscretionaryAcl, securityDescriptorBytes))
|
||||||
{
|
{
|
||||||
Throw.Win32Exception("Failed to configure the security descriptor.");
|
Throw.Command.Win32Exception("Failed to configure the security descriptor.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Security.AccessControl;
|
using System.Security.AccessControl;
|
||||||
|
using System.ServiceProcess;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace WinSW.Native
|
namespace WinSW.Native
|
||||||
{
|
{
|
||||||
|
@ -18,16 +20,38 @@ namespace WinSW.Native
|
||||||
[DllImport(Libraries.Advapi32)]
|
[DllImport(Libraries.Advapi32)]
|
||||||
internal static extern bool CloseServiceHandle(IntPtr objectHandle);
|
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")]
|
[DllImport(Libraries.Advapi32, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "OpenSCManagerW")]
|
||||||
internal static extern IntPtr OpenSCManager(string? machineName, string? databaseName, ServiceManagerAccess desiredAccess);
|
internal static extern IntPtr OpenSCManager(string? machineName, string? databaseName, ServiceManagerAccess desiredAccess);
|
||||||
|
|
||||||
[DllImport(Libraries.Advapi32, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "OpenServiceW")]
|
[DllImport(Libraries.Advapi32, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "OpenServiceW")]
|
||||||
internal static extern IntPtr OpenService(IntPtr databaseHandle, string serviceName, ServiceAccess desiredAccess);
|
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)]
|
[DllImport(Libraries.Advapi32, SetLastError = true)]
|
||||||
internal static extern bool SetServiceObjectSecurity(IntPtr serviceHandle, SecurityInfos securityInformation, byte[] securityDescriptor);
|
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);
|
internal static extern bool SetServiceStatus(IntPtr serviceStatusHandle, in SERVICE_STATUS serviceStatus);
|
||||||
|
|
||||||
// SERVICE_
|
// SERVICE_
|
||||||
|
@ -35,27 +59,27 @@ namespace WinSW.Native
|
||||||
[Flags]
|
[Flags]
|
||||||
internal enum ServiceAccess : uint
|
internal enum ServiceAccess : uint
|
||||||
{
|
{
|
||||||
QUERY_CONFIG = 0x0001,
|
QueryConfig = 0x0001,
|
||||||
CHANGE_CONFIG = 0x0002,
|
ChangeConfig = 0x0002,
|
||||||
QUERY_STATUS = 0x0004,
|
QueryStatus = 0x0004,
|
||||||
ENUMERATE_DEPENDENTS = 0x0008,
|
EnumerateDependents = 0x0008,
|
||||||
START = 0x0010,
|
Start = 0x0010,
|
||||||
STOP = 0x0020,
|
Stop = 0x0020,
|
||||||
PAUSE_CONTINUE = 0x0040,
|
PauseContinue = 0x0040,
|
||||||
INTERROGATE = 0x0080,
|
Interrogate = 0x0080,
|
||||||
USER_DEFINED_CONTROL = 0x0100,
|
UserDefinedControl = 0x0100,
|
||||||
|
|
||||||
ALL_ACCESS =
|
All =
|
||||||
SecurityApis.StandardAccess.REQUIRED |
|
SecurityApis.StandardAccess.REQUIRED |
|
||||||
QUERY_CONFIG |
|
QueryConfig |
|
||||||
CHANGE_CONFIG |
|
ChangeConfig |
|
||||||
QUERY_STATUS |
|
QueryStatus |
|
||||||
ENUMERATE_DEPENDENTS |
|
EnumerateDependents |
|
||||||
START |
|
Start |
|
||||||
STOP |
|
Stop |
|
||||||
PAUSE_CONTINUE |
|
PauseContinue |
|
||||||
INTERROGATE |
|
Interrogate |
|
||||||
USER_DEFINED_CONTROL,
|
UserDefinedControl,
|
||||||
}
|
}
|
||||||
|
|
||||||
// SERVICE_CONFIG_
|
// SERVICE_CONFIG_
|
||||||
|
@ -73,17 +97,13 @@ namespace WinSW.Native
|
||||||
PREFERRED_NODE = 9,
|
PREFERRED_NODE = 9,
|
||||||
}
|
}
|
||||||
|
|
||||||
// SERVICE_
|
// SERVICE_ERROR_
|
||||||
// https://docs.microsoft.com/windows/win32/api/winsvc/ns-winsvc-service_status
|
internal enum ServiceErrorControl : uint
|
||||||
internal enum ServiceState : uint
|
|
||||||
{
|
{
|
||||||
STOPPED = 0x00000001,
|
Ignore = 0x00000000,
|
||||||
START_PENDING = 0x00000002,
|
Normal = 0x00000001,
|
||||||
STOP_PENDING = 0x00000003,
|
Severe = 0x00000002,
|
||||||
RUNNING = 0x00000004,
|
Critical = 0x00000003,
|
||||||
CONTINUE_PENDING = 0x00000005,
|
|
||||||
PAUSE_PENDING = 0x00000006,
|
|
||||||
PAUSED = 0x00000007,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SC_MANAGER_
|
// SC_MANAGER_
|
||||||
|
@ -91,21 +111,42 @@ namespace WinSW.Native
|
||||||
[Flags]
|
[Flags]
|
||||||
internal enum ServiceManagerAccess : uint
|
internal enum ServiceManagerAccess : uint
|
||||||
{
|
{
|
||||||
CONNECT = 0x0001,
|
Connect = 0x0001,
|
||||||
CREATE_SERVICE = 0x0002,
|
CreateService = 0x0002,
|
||||||
ENUMERATE_SERVICE = 0x0004,
|
EnumerateService = 0x0004,
|
||||||
LOCK = 0x0008,
|
Lock = 0x0008,
|
||||||
QUERY_LOCK_STATUS = 0x0010,
|
QueryLockStatus = 0x0010,
|
||||||
MODIFY_BOOT_CONFIG = 0x0020,
|
ModifyBootConfig = 0x0020,
|
||||||
|
|
||||||
ALL_ACCESS =
|
All =
|
||||||
SecurityApis.StandardAccess.REQUIRED |
|
SecurityApis.StandardAccess.REQUIRED |
|
||||||
CONNECT |
|
Connect |
|
||||||
CREATE_SERVICE |
|
CreateService |
|
||||||
ENUMERATE_SERVICE |
|
EnumerateService |
|
||||||
LOCK |
|
Lock |
|
||||||
QUERY_LOCK_STATUS |
|
QueryLockStatus |
|
||||||
MODIFY_BOOT_CONFIG,
|
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
|
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
|
// https://docs.microsoft.com/windows/win32/api/winsvc/ns-winsvc-service_failure_actionsw
|
||||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||||
internal unsafe struct SERVICE_FAILURE_ACTIONS
|
internal struct SERVICE_FAILURE_ACTIONS
|
||||||
{
|
{
|
||||||
public int ResetPeriod;
|
public int ResetPeriod;
|
||||||
public string RebootMessage;
|
public string RebootMessage;
|
||||||
public string Command;
|
public string Command;
|
||||||
public int ActionsCount;
|
public int ActionsCount;
|
||||||
public SC_ACTION* Actions;
|
public unsafe SC_ACTION* Actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal struct SERVICE_STATUS
|
internal struct SERVICE_STATUS
|
||||||
{
|
{
|
||||||
public int ServiceType;
|
public ServiceType ServiceType;
|
||||||
public ServiceState CurrentState;
|
public ServiceControllerStatus CurrentState;
|
||||||
public int ControlsAccepted;
|
public int ControlsAccepted;
|
||||||
public int Win32ExitCode;
|
public int Win32ExitCode;
|
||||||
public int ServiceSpecificExitCode;
|
public int ServiceSpecificExitCode;
|
||||||
|
|
|
@ -1,16 +1,74 @@
|
||||||
using System.ComponentModel;
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace WinSW.Native
|
namespace WinSW.Native
|
||||||
{
|
{
|
||||||
internal static class Throw
|
internal static class Throw
|
||||||
{
|
{
|
||||||
internal static void Win32Exception(string message)
|
internal static class Command
|
||||||
{
|
{
|
||||||
var inner = new Win32Exception();
|
[DoesNotReturn]
|
||||||
Debug.Assert(inner.NativeErrorCode != 0);
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
Debug.Assert(message.EndsWith("."));
|
internal static void Exception(Exception inner)
|
||||||
throw new Win32Exception(inner.NativeErrorCode, message + ' ' + inner.Message);
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,25 @@
|
||||||
#if !NETCOREAPP
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
#pragma warning disable SA1502 // Element should not be on a single line
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
#if !NET
|
||||||
namespace System.Diagnostics.CodeAnalysis
|
namespace System.Diagnostics.CodeAnalysis
|
||||||
{
|
{
|
||||||
/// <summary>Specifies that null is allowed as an input even if the corresponding type disallows it.</summary>
|
/// <summary>Specifies that null is allowed as an input even if the corresponding type disallows it.</summary>
|
||||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
|
||||||
internal sealed class AllowNullAttribute : Attribute { }
|
internal sealed class AllowNullAttribute : Attribute
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Specifies that an output may be null even if the corresponding type disallows it.</summary>
|
/// <summary>Specifies that an output may be null even if the corresponding type disallows it.</summary>
|
||||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
|
||||||
internal sealed class MaybeNullAttribute : Attribute { }
|
internal sealed class MaybeNullAttribute : Attribute
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Applied to a method that will never return under any circumstance.</summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
|
||||||
|
internal sealed class DoesNotReturnAttribute : Attribute
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.ServiceProcess;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using WinSW.Configuration;
|
using WinSW.Configuration;
|
||||||
using WinSW.Native;
|
using WinSW.Native;
|
||||||
using WinSW.Util;
|
using WinSW.Util;
|
||||||
using WMI;
|
|
||||||
|
|
||||||
namespace WinSW
|
namespace WinSW
|
||||||
{
|
{
|
||||||
|
@ -56,13 +56,13 @@ namespace WinSW
|
||||||
Environment.SetEnvironmentVariable("BASE", d.FullName);
|
Environment.SetEnvironmentVariable("BASE", d.FullName);
|
||||||
|
|
||||||
// ditto for ID
|
// ditto for ID
|
||||||
Environment.SetEnvironmentVariable("SERVICE_ID", this.Id);
|
Environment.SetEnvironmentVariable("SERVICE_ID", this.Name);
|
||||||
|
|
||||||
// New name
|
// New name
|
||||||
Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameExecutablePath, this.ExecutablePath);
|
Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameExecutablePath, this.ExecutablePath);
|
||||||
|
|
||||||
// Also inject system environment variables
|
// Also inject system environment variables
|
||||||
Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameServiceId, this.Id);
|
Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameServiceId, this.Name);
|
||||||
|
|
||||||
this.environmentVariables = this.LoadEnvironmentVariables();
|
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");
|
public string Description => this.SingleElement("description");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Start mode of the Service
|
/// Start mode of the Service
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public StartMode StartMode
|
public ServiceStartMode StartMode
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
@ -476,12 +476,12 @@ namespace WinSW
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return (StartMode)Enum.Parse(typeof(StartMode), p, true);
|
return (ServiceStartMode)Enum.Parse(typeof(ServiceStartMode), p, true);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
Console.WriteLine("Start mode in XML must be one of the following:");
|
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);
|
Console.WriteLine(sm);
|
||||||
}
|
}
|
||||||
|
@ -607,7 +607,7 @@ namespace WinSW
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServiceAccount ServiceAccount
|
public Configuration.ServiceAccount ServiceAccount
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
|
|
@ -26,13 +26,13 @@ namespace WinSW
|
||||||
Environment.SetEnvironmentVariable("BASE", d.FullName);
|
Environment.SetEnvironmentVariable("BASE", d.FullName);
|
||||||
|
|
||||||
// ditto for ID
|
// ditto for ID
|
||||||
Environment.SetEnvironmentVariable("SERVICE_ID", this.Configurations.Id);
|
Environment.SetEnvironmentVariable("SERVICE_ID", this.Configurations.Name);
|
||||||
|
|
||||||
// New name
|
// New name
|
||||||
Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameExecutablePath, Defaults.ExecutablePath);
|
Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameExecutablePath, Defaults.ExecutablePath);
|
||||||
|
|
||||||
// Also inject system environment variables
|
// Also inject system environment variables
|
||||||
Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameServiceId, this.Configurations.Id);
|
Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameServiceId, this.Configurations.Name);
|
||||||
|
|
||||||
this.Configurations.LoadEnvironmentVariables();
|
this.Configurations.LoadEnvironmentVariables();
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +19,8 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'net5.0-windows'">
|
<ItemGroup Condition="'$(TargetFramework)' == 'net5.0-windows'">
|
||||||
<PackageReference Include="System.Diagnostics.EventLog" Version="4.7.0" />
|
<PackageReference Include="System.Diagnostics.EventLog" Version="5.0.0" />
|
||||||
<PackageReference Include="System.Management" Version="4.7.0" />
|
<PackageReference Include="System.Security.AccessControl" Version="5.0.0" />
|
||||||
<PackageReference Include="System.Security.AccessControl" Version="4.7.0" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- error NU1605: Detected package downgrade: log4net 2.0.8 -->
|
<!-- error NU1605: Detected package downgrade: log4net 2.0.8 -->
|
||||||
|
@ -31,12 +30,12 @@
|
||||||
<PackageReference Include="System.Net.NameResolution" Version="4.3.0" />
|
<PackageReference Include="System.Net.NameResolution" Version="4.3.0" />
|
||||||
<PackageReference Include="System.Runtime.Extensions" Version="4.3.1" />
|
<PackageReference Include="System.Runtime.Extensions" Version="4.3.1" />
|
||||||
<PackageReference Include="System.Runtime.InteropServices" Version="4.3.0" />
|
<PackageReference Include="System.Runtime.InteropServices" Version="4.3.0" />
|
||||||
|
<PackageReference Include="System.ServiceProcess.ServiceController" Version="5.0.0" />
|
||||||
<PackageReference Include="System.Threading" Version="4.3.0" />
|
<PackageReference Include="System.Threading" Version="4.3.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' != 'net5.0-windows'">
|
<ItemGroup Condition="'$(TargetFramework)' != 'net5.0-windows'">
|
||||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" />
|
<Reference Include="System.ServiceProcess" />
|
||||||
<Reference Include="System.Management" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'net461'">
|
<ItemGroup Condition="'$(TargetFramework)' == 'net461'">
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Signals a problem in WMI related operations
|
|
||||||
/// </summary>
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Associated a WMI class name to the proxy interface (which should extend from IWmiCollection)
|
|
||||||
/// </summary>
|
|
||||||
public class WmiClassName : Attribute
|
|
||||||
{
|
|
||||||
public readonly string Name;
|
|
||||||
|
|
||||||
public WmiClassName(string name) => this.Name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Marker interface to denote a collection in WMI.
|
|
||||||
/// </summary>
|
|
||||||
public interface IWmiCollection
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Marker interface to denote an individual managed object
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Obtains an object that corresponds to a table in WMI, which is a collection of a managed object.
|
|
||||||
/// </summary>
|
|
||||||
public T GetCollection<T>()
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Device driver started by the operating system loader. This value is valid only for driver services.
|
|
||||||
/// </summary>
|
|
||||||
Boot,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Device driver started by the operating system initialization process. This value is valid only for driver services.
|
|
||||||
/// </summary>
|
|
||||||
System,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Service to be started automatically by the Service Control Manager during system startup.
|
|
||||||
/// </summary>
|
|
||||||
Automatic,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Service to be started by the Service Control Manager when a process calls the StartService method.
|
|
||||||
/// </summary>
|
|
||||||
Manual,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Service that can no longer be started.
|
|
||||||
/// </summary>
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Specialized;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
@ -188,7 +186,7 @@ namespace WinSW.Plugins
|
||||||
this.Pidfile = XmlHelper.SingleElement(node, "pidfile", false)!;
|
this.Pidfile = XmlHelper.SingleElement(node, "pidfile", false)!;
|
||||||
this.StopTimeout = TimeSpan.FromMilliseconds(int.Parse(XmlHelper.SingleElement(node, "stopTimeout", false)!));
|
this.StopTimeout = TimeSpan.FromMilliseconds(int.Parse(XmlHelper.SingleElement(node, "stopTimeout", false)!));
|
||||||
this.StopParentProcessFirst = bool.Parse(XmlHelper.SingleElement(node, "stopParentFirst", false)!);
|
this.StopParentProcessFirst = bool.Parse(XmlHelper.SingleElement(node, "stopParentFirst", false)!);
|
||||||
this.ServiceId = descriptor.Id;
|
this.ServiceId = descriptor.Name;
|
||||||
// TODO: Consider making it documented
|
// TODO: Consider making it documented
|
||||||
string? checkWinSWEnvironmentVariable = XmlHelper.SingleElement(node, "checkWinSWEnvironmentVariable", true);
|
string? checkWinSWEnvironmentVariable = XmlHelper.SingleElement(node, "checkWinSWEnvironmentVariable", true);
|
||||||
this.CheckWinSWEnvironmentVariable = checkWinSWEnvironmentVariable is null ? true : bool.Parse(checkWinSWEnvironmentVariable);
|
this.CheckWinSWEnvironmentVariable = checkWinSWEnvironmentVariable is null ? true : bool.Parse(checkWinSWEnvironmentVariable);
|
||||||
|
|
|
@ -19,8 +19,8 @@ namespace winswTests.Configuration
|
||||||
{
|
{
|
||||||
var desc = Load("allOptions");
|
var desc = Load("allOptions");
|
||||||
|
|
||||||
Assert.That(desc.Id, Is.EqualTo("myapp"));
|
Assert.That(desc.Name, Is.EqualTo("myapp"));
|
||||||
Assert.That(desc.Caption, Is.EqualTo("MyApp Service (powered by WinSW)"));
|
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.Description, Is.EqualTo("This service is a service created from a sample configuration"));
|
||||||
Assert.That(desc.Executable, Is.EqualTo("%BASE%\\myExecutable.exe"));
|
Assert.That(desc.Executable, Is.EqualTo("%BASE%\\myExecutable.exe"));
|
||||||
|
|
||||||
|
@ -32,8 +32,8 @@ namespace winswTests.Configuration
|
||||||
{
|
{
|
||||||
var desc = Load("minimal");
|
var desc = Load("minimal");
|
||||||
|
|
||||||
Assert.That(desc.Id, Is.EqualTo("myapp"));
|
Assert.That(desc.Name, Is.EqualTo("myapp"));
|
||||||
Assert.That(desc.Caption, Is.EqualTo("MyApp Service (powered by WinSW)"));
|
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.Description, Is.EqualTo("This service is a service created from a minimal configuration"));
|
||||||
Assert.That(desc.Executable, Is.EqualTo("%BASE%\\myExecutable.exe"));
|
Assert.That(desc.Executable, Is.EqualTo("%BASE%\\myExecutable.exe"));
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.ServiceProcess;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using WinSW;
|
using WinSW;
|
||||||
using winswTests.Util;
|
using winswTests.Util;
|
||||||
using WMI;
|
|
||||||
|
|
||||||
namespace winswTests
|
namespace winswTests
|
||||||
{
|
{
|
||||||
|
@ -44,7 +44,7 @@ $@"<service>
|
||||||
[Test]
|
[Test]
|
||||||
public void DefaultStartMode()
|
public void DefaultStartMode()
|
||||||
{
|
{
|
||||||
Assert.That(this._extendedServiceDescriptor.StartMode, Is.EqualTo(StartMode.Automatic));
|
Assert.That(this._extendedServiceDescriptor.StartMode, Is.EqualTo(ServiceStartMode.Automatic));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -96,7 +96,7 @@ $@"<service>
|
||||||
</service>";
|
</service>";
|
||||||
|
|
||||||
this._extendedServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
|
this._extendedServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
|
||||||
Assert.That(this._extendedServiceDescriptor.StartMode, Is.EqualTo(StartMode.Manual));
|
Assert.That(this._extendedServiceDescriptor.StartMode, Is.EqualTo(ServiceStartMode.Manual));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.ServiceProcess;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using WinSW;
|
using WinSW;
|
||||||
using WinSW.Configuration;
|
using WinSW.Configuration;
|
||||||
using WMI;
|
|
||||||
|
|
||||||
namespace winswTests
|
namespace winswTests
|
||||||
{
|
{
|
||||||
|
@ -43,7 +43,7 @@ workingdirectory: {ExpectedWorkingDirectory}";
|
||||||
[Test]
|
[Test]
|
||||||
public void DefaultStartMode()
|
public void DefaultStartMode()
|
||||||
{
|
{
|
||||||
Assert.That(this._extendedServiceDescriptor.StartMode, Is.EqualTo(StartMode.Automatic));
|
Assert.That(this._extendedServiceDescriptor.StartMode, Is.EqualTo(ServiceStartMode.Automatic));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -73,7 +73,7 @@ arguments: My Arguments
|
||||||
startMode: manual";
|
startMode: manual";
|
||||||
|
|
||||||
this._extendedServiceDescriptor = ServiceDescriptorYaml.FromYaml(yaml).Configurations;
|
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]
|
[Test]
|
||||||
|
@ -470,7 +470,7 @@ description: This is test winsw";
|
||||||
|
|
||||||
Assert.That(() =>
|
Assert.That(() =>
|
||||||
{
|
{
|
||||||
_ = ServiceDescriptorYaml.FromYaml(yml).Configurations.Id;
|
_ = ServiceDescriptorYaml.FromYaml(yml).Configurations.Name;
|
||||||
}, Throws.TypeOf<InvalidOperationException>());
|
}, Throws.TypeOf<InvalidOperationException>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using WinSW;
|
using WinSW;
|
||||||
using WinSW.Configuration;
|
using WinSW.Configuration;
|
||||||
|
@ -55,14 +54,14 @@ namespace winswTests.Util
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var properties = AllProperties;
|
var properties = AllProperties;
|
||||||
properties.Remove("Id");
|
properties.Remove(nameof(IWinSWConfiguration.Name));
|
||||||
properties.Remove("Caption");
|
properties.Remove(nameof(IWinSWConfiguration.DisplayName));
|
||||||
properties.Remove("Description");
|
properties.Remove(nameof(IWinSWConfiguration.Description));
|
||||||
properties.Remove("Executable");
|
properties.Remove(nameof(IWinSWConfiguration.Executable));
|
||||||
properties.Remove("BaseName");
|
properties.Remove(nameof(IWinSWConfiguration.BaseName));
|
||||||
properties.Remove("BasePath");
|
properties.Remove(nameof(IWinSWConfiguration.BasePath));
|
||||||
properties.Remove("Log");
|
properties.Remove(nameof(IWinSWConfiguration.Log));
|
||||||
properties.Remove("ServiceAccount");
|
properties.Remove(nameof(IWinSWConfiguration.ServiceAccount));
|
||||||
return properties;
|
return properties;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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})";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,12 +2,10 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
#if NET
|
#if NET
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
#endif
|
#endif
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Security.AccessControl;
|
using System.Security.AccessControl;
|
||||||
using System.Security.Principal;
|
using System.Security.Principal;
|
||||||
using System.ServiceProcess;
|
using System.ServiceProcess;
|
||||||
|
@ -21,8 +19,10 @@ using log4net.Layout;
|
||||||
using WinSW.Configuration;
|
using WinSW.Configuration;
|
||||||
using WinSW.Logging;
|
using WinSW.Logging;
|
||||||
using WinSW.Native;
|
using WinSW.Native;
|
||||||
using WMI;
|
using static WinSW.FormatExtensions;
|
||||||
using ServiceType = WMI.ServiceType;
|
using static WinSW.ServiceControllerExtension;
|
||||||
|
using static WinSW.Native.ServiceApis;
|
||||||
|
using TimeoutException = System.ServiceProcess.TimeoutException;
|
||||||
|
|
||||||
namespace WinSW
|
namespace WinSW
|
||||||
{
|
{
|
||||||
|
@ -35,7 +35,6 @@ namespace WinSW
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Run(args);
|
Run(args);
|
||||||
Log.Debug("Completed. Exit code is 0");
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
catch (InvalidDataException e)
|
catch (InvalidDataException e)
|
||||||
|
@ -45,16 +44,27 @@ namespace WinSW
|
||||||
Console.Error.WriteLine(message);
|
Console.Error.WriteLine(message);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
catch (WmiException e)
|
catch (CommandException e)
|
||||||
{
|
{
|
||||||
Log.Fatal("WMI Operation failure: " + e.ErrorCode, e);
|
string message = e.Message;
|
||||||
Console.Error.WriteLine(e);
|
Log.Fatal(message);
|
||||||
return (int)e.ErrorCode;
|
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)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Log.Fatal("Unhandled exception", e);
|
Log.Fatal("Unhandled exception", e);
|
||||||
Console.Error.WriteLine(e);
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,10 +104,6 @@ namespace WinSW
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get service info for the future use
|
|
||||||
var svcs = new WmiRoot().GetCollection<IWin32Services>();
|
|
||||||
var svc = svcs.Select(descriptor.Id);
|
|
||||||
|
|
||||||
var args = new List<string>(Array.AsReadOnly(argsArray));
|
var args = new List<string>(Array.AsReadOnly(argsArray));
|
||||||
if (args[0] == "/redirect")
|
if (args[0] == "/redirect")
|
||||||
{
|
{
|
||||||
|
@ -148,11 +154,11 @@ namespace WinSW
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case "stop":
|
case "stop":
|
||||||
Stop();
|
Stop(true);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case "stopwait":
|
case "stopwait":
|
||||||
StopWait();
|
Stop(false);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case "restart":
|
case "restart":
|
||||||
|
@ -201,14 +207,15 @@ namespace WinSW
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Info("Installing the service with id '" + descriptor.Id + "'");
|
Log.Info($"Installing service '{Format(descriptor)}'...");
|
||||||
|
|
||||||
// Check if the service exists
|
using var scm = ServiceManager.Open(ServiceManagerAccess.CreateService);
|
||||||
if (svc != null)
|
|
||||||
|
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");
|
Log.Error($"A service with ID '{descriptor.Name}' already exists.");
|
||||||
throw new Exception("Installation failure: Service with id '" + descriptor.Id + "' already exists");
|
Throw.Command.Win32Exception(Errors.ERROR_SERVICE_EXISTS, "Failed to install the service.");
|
||||||
}
|
}
|
||||||
|
|
||||||
string? username = null;
|
string? username = null;
|
||||||
|
@ -244,22 +251,21 @@ namespace WinSW
|
||||||
Security.AddServiceLogonRight(descriptor.ServiceAccount.ServiceAccountDomain!, descriptor.ServiceAccount.ServiceAccountName!);
|
Security.AddServiceLogonRight(descriptor.ServiceAccount.ServiceAccountDomain!, descriptor.ServiceAccount.ServiceAccountName!);
|
||||||
}
|
}
|
||||||
|
|
||||||
svcs.Create(
|
using var sc = scm.CreateService(
|
||||||
descriptor.Id,
|
descriptor.Name,
|
||||||
descriptor.Caption,
|
descriptor.DisplayName,
|
||||||
"\"" + descriptor.ExecutablePath + "\"",
|
|
||||||
ServiceType.OwnProcess,
|
|
||||||
ErrorControl.UserNotified,
|
|
||||||
descriptor.StartMode.ToString(),
|
|
||||||
descriptor.Interactive,
|
descriptor.Interactive,
|
||||||
|
descriptor.StartMode,
|
||||||
|
$"\"{descriptor.ExecutablePath}\"",
|
||||||
|
descriptor.ServiceDependencies,
|
||||||
username,
|
username,
|
||||||
password,
|
password);
|
||||||
descriptor.ServiceDependencies);
|
|
||||||
|
|
||||||
using var scm = ServiceManager.Open();
|
string description = descriptor.Description;
|
||||||
using var sc = scm.OpenService(descriptor.Id);
|
if (description.Length != 0)
|
||||||
|
{
|
||||||
sc.SetDescription(descriptor.Description);
|
sc.SetDescription(description);
|
||||||
|
}
|
||||||
|
|
||||||
var actions = descriptor.FailureActions;
|
var actions = descriptor.FailureActions;
|
||||||
if (actions.Length > 0)
|
if (actions.Length > 0)
|
||||||
|
@ -267,7 +273,7 @@ namespace WinSW
|
||||||
sc.SetFailureActions(descriptor.ResetFailureAfter, actions);
|
sc.SetFailureActions(descriptor.ResetFailureAfter, actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isDelayedAutoStart = descriptor.StartMode == StartMode.Automatic && descriptor.DelayedAutoStart;
|
bool isDelayedAutoStart = descriptor.StartMode == ServiceStartMode.Automatic && descriptor.DelayedAutoStart;
|
||||||
if (isDelayedAutoStart)
|
if (isDelayedAutoStart)
|
||||||
{
|
{
|
||||||
sc.SetDelayedAutoStart(true);
|
sc.SetDelayedAutoStart(true);
|
||||||
|
@ -280,11 +286,13 @@ namespace WinSW
|
||||||
sc.SetSecurityDescriptor(new RawSecurityDescriptor(securityDescriptor));
|
sc.SetSecurityDescriptor(new RawSecurityDescriptor(securityDescriptor));
|
||||||
}
|
}
|
||||||
|
|
||||||
string eventLogSource = descriptor.Id;
|
string eventLogSource = descriptor.Name;
|
||||||
if (!EventLog.SourceExists(eventLogSource))
|
if (!EventLog.SourceExists(eventLogSource))
|
||||||
{
|
{
|
||||||
EventLog.CreateEventSource(eventLogSource, "Application");
|
EventLog.CreateEventSource(eventLogSource, "Application");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.Info($"Service '{Format(descriptor)}' was installed successfully.");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Uninstall()
|
void Uninstall()
|
||||||
|
@ -295,40 +303,42 @@ namespace WinSW
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Info("Uninstalling the service with id '" + descriptor.Id + "'");
|
Log.Info($"Uninstalling service '{Format(descriptor)}'...");
|
||||||
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 var scm = ServiceManager.Open(ServiceManagerAccess.Connect);
|
||||||
try
|
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 + "'"
|
case Errors.ERROR_SERVICE_DOES_NOT_EXIST:
|
||||||
+ ". It has been marked for deletion.");
|
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?
|
case Errors.ERROR_SERVICE_MARKED_FOR_DELETE:
|
||||||
return; // it's already uninstalled, so consider it a success
|
Log.Error(e.Message);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Log.Fatal("Failed to uninstall the service with id '" + descriptor.Id + "'. WMI Error code is '" + e.ErrorCode + "'");
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Info("Starting the service with id '" + descriptor.Id + "'");
|
using var svc = new ServiceController(descriptor.Name);
|
||||||
if (svc is null)
|
|
||||||
{
|
|
||||||
ThrowNoSuchService();
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
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)
|
Throw.Command.Exception(inner);
|
||||||
{
|
}
|
||||||
Log.Info($"The service with ID '{descriptor.Id}' has already been started");
|
catch (InvalidOperationException e)
|
||||||
}
|
when (e.InnerException is Win32Exception inner && inner.NativeErrorCode == Errors.ERROR_SERVICE_ALREADY_RUNNING)
|
||||||
else
|
{
|
||||||
{
|
Log.Info($"Service '{Format(svc)}' has already started.");
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stop()
|
void Stop(bool noWait)
|
||||||
{
|
{
|
||||||
if (!elevated)
|
if (!elevated)
|
||||||
{
|
{
|
||||||
|
@ -371,56 +379,37 @@ namespace WinSW
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Info("Stopping the service with id '" + descriptor.Id + "'");
|
using var svc = new ServiceController(descriptor.Name);
|
||||||
if (svc is null)
|
|
||||||
{
|
|
||||||
ThrowNoSuchService();
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
svc.StopService();
|
Log.Info($"Stopping service '{Format(svc)}'...");
|
||||||
}
|
svc.Stop();
|
||||||
catch (WmiException e)
|
|
||||||
{
|
if (!noWait)
|
||||||
if (e.ErrorCode == ReturnValue.ServiceCannotAcceptControl)
|
|
||||||
{
|
{
|
||||||
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()
|
Log.Info($"Service '{Format(svc)}' stopped successfully.");
|
||||||
{
|
}
|
||||||
if (!elevated)
|
catch (InvalidOperationException e)
|
||||||
|
when (e.InnerException is Win32Exception inner && inner.NativeErrorCode == Errors.ERROR_SERVICE_DOES_NOT_EXIST)
|
||||||
{
|
{
|
||||||
Elevate();
|
Throw.Command.Exception(inner);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
catch (InvalidOperationException e)
|
||||||
Log.Info("Stopping the service with id '" + descriptor.Id + "'");
|
when (e.InnerException is Win32Exception inner && inner.NativeErrorCode == Errors.ERROR_SERVICE_NOT_ACTIVE)
|
||||||
if (svc is null)
|
|
||||||
{
|
{
|
||||||
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()
|
void Restart()
|
||||||
|
@ -431,47 +420,115 @@ namespace WinSW
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Info("Restarting the service with id '" + descriptor.Id + "'");
|
|
||||||
if (svc is null)
|
using var svc = new ServiceController(descriptor.Name);
|
||||||
|
|
||||||
|
List<ServiceController>? 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);
|
foreach (var service in startedDependentServices)
|
||||||
svc = svcs.Select(descriptor.Id)!;
|
{
|
||||||
|
if (service.Status == ServiceControllerStatus.Stopped)
|
||||||
|
{
|
||||||
|
Log.Info($"Starting service '{Format(service)}'...");
|
||||||
|
service.Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
svc.StartService();
|
Log.Info($"Service '{Format(svc)}' restarted successfully.");
|
||||||
}
|
}
|
||||||
|
|
||||||
void RestartSelf()
|
void RestartSelf()
|
||||||
{
|
{
|
||||||
if (!elevated)
|
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.
|
// 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 (!ProcessApis.CreateProcess(
|
||||||
if (!result)
|
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()
|
void Status()
|
||||||
{
|
{
|
||||||
Log.Debug("User requested the status of the process with id '" + descriptor.Id + "'");
|
using var svc = new ServiceController(descriptor.Name);
|
||||||
Console.WriteLine(svc is null ? "NonExistent" : svc.Started ? "Started" : "Stopped");
|
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()
|
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)
|
private static void InitLoggers(IWinSWConfiguration descriptor, bool enableConsoleLogging)
|
||||||
{
|
{
|
||||||
// TODO: Make logging levels configurable
|
// TODO: Make logging levels configurable
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
using System;
|
||||||
|
using System.ServiceProcess;
|
||||||
|
using TimeoutException = System.ServiceProcess.TimeoutException;
|
||||||
|
|
||||||
|
namespace WinSW
|
||||||
|
{
|
||||||
|
internal static class ServiceControllerExtension
|
||||||
|
{
|
||||||
|
/// <exception cref="TimeoutException" />
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,13 +25,8 @@
|
||||||
<ILMergeVersion>3.0.41</ILMergeVersion>
|
<ILMergeVersion>3.0.41</ILMergeVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'net5.0-windows'">
|
|
||||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="4.7.0" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' != 'net5.0-windows'">
|
<ItemGroup Condition="'$(TargetFramework)' != 'net5.0-windows'">
|
||||||
<PackageReference Include="ilmerge" Version="$(ILMergeVersion)" />
|
<PackageReference Include="ilmerge" Version="$(ILMergeVersion)" />
|
||||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" />
|
|
||||||
<Reference Include="System.ServiceProcess" />
|
<Reference Include="System.ServiceProcess" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,6 @@ namespace WinSW
|
||||||
{
|
{
|
||||||
public class WrapperService : ServiceBase, IEventLogger
|
public class WrapperService : ServiceBase, IEventLogger
|
||||||
{
|
{
|
||||||
private ServiceApis.SERVICE_STATUS wrapperServiceStatus;
|
|
||||||
|
|
||||||
private readonly Process process = new();
|
private readonly Process process = new();
|
||||||
|
|
||||||
private readonly IWinSWConfiguration descriptor;
|
private readonly IWinSWConfiguration descriptor;
|
||||||
|
@ -61,7 +59,7 @@ namespace WinSW
|
||||||
public WrapperService(IWinSWConfiguration descriptor)
|
public WrapperService(IWinSWConfiguration descriptor)
|
||||||
{
|
{
|
||||||
this.descriptor = descriptor;
|
this.descriptor = descriptor;
|
||||||
this.ServiceName = this.descriptor.Id;
|
this.ServiceName = this.descriptor.Name;
|
||||||
this.ExtensionManager = new WinSWExtensionManager(this.descriptor);
|
this.ExtensionManager = new WinSWExtensionManager(this.descriptor);
|
||||||
this.CanShutdown = true;
|
this.CanShutdown = true;
|
||||||
this.CanStop = true;
|
this.CanStop = true;
|
||||||
|
@ -340,8 +338,8 @@ namespace WinSW
|
||||||
private void DoStop()
|
private void DoStop()
|
||||||
{
|
{
|
||||||
string? stopArguments = this.descriptor.StopArguments;
|
string? stopArguments = this.descriptor.StopArguments;
|
||||||
this.LogEvent("Stopping " + this.descriptor.Id);
|
this.LogEvent("Stopping " + this.descriptor.Name);
|
||||||
Log.Info("Stopping " + this.descriptor.Id);
|
Log.Info("Stopping " + this.descriptor.Name);
|
||||||
this.orderlyShutdown = true;
|
this.orderlyShutdown = true;
|
||||||
|
|
||||||
if (stopArguments is null)
|
if (stopArguments is null)
|
||||||
|
@ -359,7 +357,7 @@ namespace WinSW
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.SignalShutdownPending();
|
this.SignalPending();
|
||||||
|
|
||||||
stopArguments += " " + this.descriptor.Arguments;
|
stopArguments += " " + this.descriptor.Arguments;
|
||||||
|
|
||||||
|
@ -384,12 +382,12 @@ namespace WinSW
|
||||||
Console.Beep();
|
Console.Beep();
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Info("Finished " + this.descriptor.Id);
|
Log.Info("Finished " + this.descriptor.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WaitForProcessToExit(Process processoWait)
|
private void WaitForProcessToExit(Process processoWait)
|
||||||
{
|
{
|
||||||
this.SignalShutdownPending();
|
this.SignalPending();
|
||||||
|
|
||||||
int effectiveProcessWaitSleepTime;
|
int effectiveProcessWaitSleepTime;
|
||||||
if (this.descriptor.SleepTime.TotalMilliseconds > int.MaxValue)
|
if (this.descriptor.SleepTime.TotalMilliseconds > int.MaxValue)
|
||||||
|
@ -409,7 +407,7 @@ namespace WinSW
|
||||||
|
|
||||||
while (!processoWait.WaitForExit(effectiveProcessWaitSleepTime))
|
while (!processoWait.WaitForExit(effectiveProcessWaitSleepTime))
|
||||||
{
|
{
|
||||||
this.SignalShutdownPending();
|
this.SignalPending();
|
||||||
// WriteEvent("WaitForProcessToExit [repeat]");
|
// WriteEvent("WaitForProcessToExit [repeat]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -421,7 +419,7 @@ namespace WinSW
|
||||||
// WriteEvent("WaitForProcessToExit [finished]");
|
// WriteEvent("WaitForProcessToExit [finished]");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SignalShutdownPending()
|
private void SignalPending()
|
||||||
{
|
{
|
||||||
int effectiveWaitHint;
|
int effectiveWaitHint;
|
||||||
if (this.descriptor.WaitHint.TotalMilliseconds > int.MaxValue)
|
if (this.descriptor.WaitHint.TotalMilliseconds > int.MaxValue)
|
||||||
|
@ -438,13 +436,12 @@ namespace WinSW
|
||||||
this.RequestAdditionalTime(effectiveWaitHint);
|
this.RequestAdditionalTime(effectiveWaitHint);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SignalShutdownComplete()
|
private void SignalStopped()
|
||||||
{
|
{
|
||||||
var handle = this.ServiceHandle;
|
using var scm = ServiceManager.Open();
|
||||||
this.wrapperServiceStatus.CheckPoint++;
|
using var sc = scm.OpenService(this.ServiceName, ServiceApis.ServiceAccess.QueryStatus);
|
||||||
// WriteEvent("SignalShutdownComplete " + wrapperServiceStatus.checkPoint + ":" + wrapperServiceStatus.waitHint);
|
|
||||||
this.wrapperServiceStatus.CurrentState = ServiceApis.ServiceState.STOPPED;
|
sc.SetStatus(this.ServiceHandle, ServiceControllerStatus.Stopped);
|
||||||
ServiceApis.SetServiceStatus(handle, this.wrapperServiceStatus);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StartProcess(Process processToStart, string arguments, string executable, LogHandler? logHandler)
|
private void StartProcess(Process processToStart, string arguments, string executable, LogHandler? logHandler)
|
||||||
|
@ -468,7 +465,7 @@ namespace WinSW
|
||||||
// restart the service automatically
|
// restart the service automatically
|
||||||
if (proc.ExitCode == 0)
|
if (proc.ExitCode == 0)
|
||||||
{
|
{
|
||||||
this.SignalShutdownComplete();
|
this.SignalStopped();
|
||||||
}
|
}
|
||||||
|
|
||||||
Environment.Exit(proc.ExitCode);
|
Environment.Exit(proc.ExitCode);
|
||||||
|
|
Loading…
Reference in New Issue