mirror of https://github.com/winsw/winsw
266 lines
7.9 KiB
C#
266 lines
7.9 KiB
C#
using System;
|
|
using System.Runtime.InteropServices;
|
|
using System.Security.AccessControl;
|
|
using System.ServiceProcess;
|
|
using System.Text;
|
|
using static WinSW.Native.ServiceApis;
|
|
|
|
namespace WinSW.Native
|
|
{
|
|
public enum SC_ACTION_TYPE
|
|
{
|
|
/// <summary>
|
|
/// No action.
|
|
/// </summary>
|
|
SC_ACTION_NONE = 0,
|
|
|
|
/// <summary>
|
|
/// Restart the service.
|
|
/// </summary>
|
|
SC_ACTION_RESTART = 1,
|
|
|
|
/// <summary>
|
|
/// Reboot the computer.
|
|
/// </summary>
|
|
SC_ACTION_REBOOT = 2,
|
|
|
|
/// <summary>
|
|
/// Run a command.
|
|
/// </summary>
|
|
SC_ACTION_RUN_COMMAND = 3,
|
|
}
|
|
|
|
public struct SC_ACTION
|
|
{
|
|
/// <summary>
|
|
/// The action to be performed.
|
|
/// </summary>
|
|
public SC_ACTION_TYPE Type;
|
|
|
|
/// <summary>
|
|
/// The time to wait before performing the specified action, in milliseconds.
|
|
/// </summary>
|
|
public int Delay;
|
|
|
|
public SC_ACTION(SC_ACTION_TYPE type, TimeSpan delay)
|
|
{
|
|
this.Type = type;
|
|
this.Delay = (int)delay.TotalMilliseconds;
|
|
}
|
|
}
|
|
|
|
internal ref struct ServiceManager
|
|
{
|
|
private IntPtr handle;
|
|
|
|
private ServiceManager(IntPtr handle) => this.handle = handle;
|
|
|
|
internal static ServiceManager Open(ServiceManagerAccess access = ServiceManagerAccess.All)
|
|
{
|
|
var handle = OpenSCManager(null, null, access);
|
|
if (handle == IntPtr.Zero)
|
|
{
|
|
Throw.Command.Win32Exception("Failed to open the service control manager database.");
|
|
}
|
|
|
|
return new ServiceManager(handle);
|
|
}
|
|
|
|
internal Service CreateService(
|
|
string serviceName,
|
|
string displayName,
|
|
bool interactive,
|
|
ServiceStartMode startMode,
|
|
string executablePath,
|
|
string[] dependencies,
|
|
string? username,
|
|
string? password)
|
|
{
|
|
var handle = ServiceApis.CreateService(
|
|
this.handle,
|
|
serviceName,
|
|
displayName,
|
|
ServiceAccess.All,
|
|
ServiceType.Win32OwnProcess | (interactive ? ServiceType.InteractiveProcess : default),
|
|
startMode,
|
|
ServiceErrorControl.Normal,
|
|
executablePath,
|
|
default,
|
|
default,
|
|
Service.GetNativeDependencies(dependencies),
|
|
username,
|
|
password);
|
|
if (handle == IntPtr.Zero)
|
|
{
|
|
Throw.Command.Win32Exception("Failed to create service.");
|
|
}
|
|
|
|
return new Service(handle);
|
|
}
|
|
|
|
internal Service OpenService(string serviceName, ServiceAccess access = ServiceAccess.All)
|
|
{
|
|
var serviceHandle = ServiceApis.OpenService(this.handle, serviceName, access);
|
|
if (serviceHandle == IntPtr.Zero)
|
|
{
|
|
Throw.Command.Win32Exception("Failed to open the service.");
|
|
}
|
|
|
|
return new Service(serviceHandle);
|
|
}
|
|
|
|
internal bool ServiceExists(string serviceName)
|
|
{
|
|
var serviceHandle = ServiceApis.OpenService(this.handle, serviceName, ServiceAccess.All);
|
|
if (serviceHandle == IntPtr.Zero)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
_ = CloseServiceHandle(this.handle);
|
|
return true;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (this.handle != IntPtr.Zero)
|
|
{
|
|
_ = CloseServiceHandle(this.handle);
|
|
}
|
|
|
|
this.handle = IntPtr.Zero;
|
|
}
|
|
}
|
|
|
|
internal ref struct Service
|
|
{
|
|
private IntPtr 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)
|
|
{
|
|
if (!ChangeServiceConfig2(
|
|
this.handle,
|
|
ServiceConfigInfoLevels.DESCRIPTION,
|
|
new SERVICE_DESCRIPTION { Description = description }))
|
|
{
|
|
Throw.Command.Win32Exception("Failed to configure the description.");
|
|
}
|
|
}
|
|
|
|
internal unsafe void SetFailureActions(TimeSpan failureResetPeriod, SC_ACTION[] actions)
|
|
{
|
|
fixed (SC_ACTION* actionsPtr = actions)
|
|
{
|
|
if (!ChangeServiceConfig2(
|
|
this.handle,
|
|
ServiceConfigInfoLevels.FAILURE_ACTIONS,
|
|
new SERVICE_FAILURE_ACTIONS
|
|
{
|
|
ResetPeriod = (int)failureResetPeriod.TotalSeconds,
|
|
RebootMessage = string.Empty, // TODO
|
|
Command = string.Empty, // TODO
|
|
ActionsCount = actions.Length,
|
|
Actions = actionsPtr,
|
|
}))
|
|
{
|
|
Throw.Command.Win32Exception("Failed to configure the failure actions.");
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void SetDelayedAutoStart(bool enabled)
|
|
{
|
|
if (!ChangeServiceConfig2(
|
|
this.handle,
|
|
ServiceConfigInfoLevels.DELAYED_AUTO_START_INFO,
|
|
new SERVICE_DELAYED_AUTO_START_INFO { DelayedAutostart = enabled }))
|
|
{
|
|
Throw.Command.Win32Exception("Failed to configure the delayed auto-start setting.");
|
|
}
|
|
}
|
|
|
|
internal void SetSecurityDescriptor(RawSecurityDescriptor securityDescriptor)
|
|
{
|
|
byte[] securityDescriptorBytes = new byte[securityDescriptor.BinaryLength];
|
|
securityDescriptor.GetBinaryForm(securityDescriptorBytes, 0);
|
|
if (!SetServiceObjectSecurity(this.handle, SecurityInfos.DiscretionaryAcl, securityDescriptorBytes))
|
|
{
|
|
Throw.Command.Win32Exception("Failed to configure the security descriptor.");
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (this.handle != IntPtr.Zero)
|
|
{
|
|
_ = CloseServiceHandle(this.handle);
|
|
}
|
|
|
|
this.handle = IntPtr.Zero;
|
|
}
|
|
}
|
|
}
|