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