using System;
using System.Security.AccessControl;
using System.ServiceProcess;
using System.Text;
using static WinSW.Native.ServiceApis;
namespace WinSW.Native
{
public enum SC_ACTION_TYPE
{
///
/// No action.
///
SC_ACTION_NONE = 0,
///
/// Restart the service.
///
SC_ACTION_RESTART = 1,
///
/// Reboot the computer.
///
SC_ACTION_REBOOT = 2,
///
/// Run a command.
///
SC_ACTION_RUN_COMMAND = 3,
}
public struct SC_ACTION
{
///
/// The action to be performed.
///
public SC_ACTION_TYPE Type;
///
/// The time to wait before performing the specified action, in milliseconds.
///
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()
{
IntPtr handle = OpenSCManager(null, null, ServiceManagerAccess.ALL_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,
ServiceStartMode startMode,
string executablePath,
string[] dependencies,
string? username,
string? password)
{
IntPtr handle = ServiceApis.CreateService(
this.handle,
serviceName,
displayName,
ServiceAccess.ALL_ACCESS,
ServiceType.Win32OwnProcess,
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_ACCESS)
{
IntPtr 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)
{
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)
{
_ = 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 SERVICE_STATUS 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 SERVICE_STATUS 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 ChangeConfig(
string displayName,
ServiceStartMode startMode,
string[] dependencies)
{
if (!ChangeServiceConfig(
this.handle,
default,
startMode,
default,
null,
null,
IntPtr.Zero,
GetNativeDependencies(dependencies),
null,
null,
displayName))
{
Throw.Command.Win32Exception("Failed to change service config.");
}
}
///
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 SetPreshutdownTimeout(TimeSpan timeout)
{
if (!ChangeServiceConfig2(
this.handle,
ServiceConfigInfoLevels.PRESHUTDOWN_INFO,
new SERVICE_PRESHUTDOWN_INFO { PreshutdownTimeout = (int)timeout.TotalMilliseconds }))
{
Throw.Command.Win32Exception("Failed to configure the preshutdown timeout.");
}
}
///
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;
}
}
}