Reorganize interop codes

pull/535/head
NextTurn 2019-05-06 00:00:00 +08:00 committed by Next Turn
parent ee29eee8cf
commit f260d7e5d9
19 changed files with 698 additions and 773 deletions

View File

@ -31,7 +31,7 @@ namespace winsw
{
public class WrapperService : ServiceBase, EventLogger
{
private SERVICE_STATUS _wrapperServiceStatus;
private ServiceApis.SERVICE_STATUS _wrapperServiceStatus;
private readonly Process _process = new Process();
private readonly ServiceDescriptor _descriptor;
@ -434,10 +434,10 @@ namespace winsw
private void SignalShutdownComplete()
{
IntPtr handle = ServiceHandle;
_wrapperServiceStatus.checkPoint++;
_wrapperServiceStatus.CheckPoint++;
// WriteEvent("SignalShutdownComplete " + wrapperServiceStatus.checkPoint + ":" + wrapperServiceStatus.waitHint);
_wrapperServiceStatus.currentState = (int)State.SERVICE_STOPPED;
Advapi32.SetServiceStatus(handle, _wrapperServiceStatus);
_wrapperServiceStatus.CurrentState = ServiceApis.ServiceState.STOPPED;
ServiceApis.SetServiceStatus(handle, _wrapperServiceStatus);
}
private void StartProcess(Process processToStart, string arguments, string executable, LogHandler? logHandler, bool redirectStdin)
@ -590,8 +590,8 @@ namespace winsw
{
elevated = true;
_ = SigIntHelper.FreeConsole();
_ = SigIntHelper.AttachConsole(SigIntHelper.ATTACH_PARENT_PROCESS);
_ = ConsoleApis.FreeConsole();
_ = ConsoleApis.AttachConsole(ConsoleApis.ATTACH_PARENT_PROCESS);
args = args.GetRange(1, args.Count - 1);
}
@ -714,7 +714,7 @@ namespace winsw
if (setallowlogonasaserviceright)
{
LogonAsAService.AddLogonAsAServiceRight(username!);
Security.AddServiceLogonRight(descriptor.ServiceAccountDomain!, descriptor.ServiceAccountName!);
}
svc.Create(
@ -729,13 +729,13 @@ namespace winsw
password,
descriptor.ServiceDependencies);
using ServiceManager scm = new ServiceManager();
using Service sc = scm.Open(descriptor.Id);
using ServiceManager scm = ServiceManager.Open();
using Service sc = scm.OpenService(descriptor.Id);
sc.SetDescription(descriptor.Description);
var actions = descriptor.FailureActions;
if (actions.Count > 0)
if (actions.Length > 0)
{
sc.SetFailureActions(descriptor.ResetFailureAfter, actions);
}
@ -746,13 +746,11 @@ namespace winsw
sc.SetDelayedAutoStart(true);
}
if (descriptor.SecurityDescriptor != null)
var securityDescriptor = descriptor.SecurityDescriptor;
if (securityDescriptor != null)
{
// throws ArgumentException
RawSecurityDescriptor rawSecurityDescriptor = new RawSecurityDescriptor(descriptor.SecurityDescriptor);
byte[] securityDescriptorBytes = new byte[rawSecurityDescriptor.BinaryLength];
rawSecurityDescriptor.GetBinaryForm(securityDescriptorBytes, 0);
_ = Advapi32.SetServiceObjectSecurity(sc.Handle, SecurityInfos.DiscretionaryAcl, securityDescriptorBytes);
sc.SetSecurityDescriptor(new RawSecurityDescriptor(securityDescriptor));
}
string eventLogSource = descriptor.Id;
@ -919,7 +917,7 @@ namespace winsw
// run restart from another process group. see README.md for why this is useful.
bool result = Kernel32.CreateProcess(null, descriptor.ExecutablePath + " restart", IntPtr.Zero, IntPtr.Zero, false, Kernel32.CREATE_NEW_PROCESS_GROUP, IntPtr.Zero, null, default, out _);
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)
{
throw new Exception("Failed to invoke restart: " + Marshal.GetLastWin32Error());
@ -1063,19 +1061,19 @@ namespace winsw
internal static unsafe bool IsProcessElevated()
{
IntPtr process = Kernel32.GetCurrentProcess();
if (!Advapi32.OpenProcessToken(process, TokenAccessLevels.Read, out IntPtr token))
IntPtr process = ProcessApis.GetCurrentProcess();
if (!ProcessApis.OpenProcessToken(process, TokenAccessLevels.Read, out IntPtr token))
{
ThrowWin32Exception("Failed to open process token.");
}
try
{
if (!Advapi32.GetTokenInformation(
if (!SecurityApis.GetTokenInformation(
token,
TOKEN_INFORMATION_CLASS.TokenElevation,
out TOKEN_ELEVATION elevation,
sizeof(TOKEN_ELEVATION),
SecurityApis.TOKEN_INFORMATION_CLASS.TokenElevation,
out SecurityApis.TOKEN_ELEVATION elevation,
sizeof(SecurityApis.TOKEN_ELEVATION),
out _))
{
ThrowWin32Exception("Failed to get token information");
@ -1085,7 +1083,7 @@ namespace winsw
}
finally
{
_ = Kernel32.CloseHandle(token);
_ = HandleApis.CloseHandle(token);
}
static void ThrowWin32Exception(string message)

View File

@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("WindowsService")]

View File

@ -24,7 +24,7 @@ namespace winsw.Configuration
public bool AllowServiceAcountLogonRight => false;
public string? ServiceAccountPassword => null;
public string? ServiceAccountUser => null;
public List<Native.SC_ACTION> FailureActions => new List<Native.SC_ACTION>(0);
public Native.SC_ACTION[] FailureActions => new Native.SC_ACTION[0];
public TimeSpan ResetFailureAfter => TimeSpan.FromDays(1);
// Executable management

View File

@ -21,7 +21,7 @@ namespace winsw.Configuration
bool AllowServiceAcountLogonRight { get; }
string? ServiceAccountPassword { get; }
string? ServiceAccountUser { get; }
List<Native.SC_ACTION> FailureActions { get; }
Native.SC_ACTION[] FailureActions { get; }
TimeSpan ResetFailureAfter { get; }
// Executable management

View File

@ -1,631 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Text;
// ReSharper disable InconsistentNaming
namespace winsw.Native
{
public class ServiceManager : IDisposable
{
private IntPtr _handle;
public ServiceManager()
{
_handle = Advapi32.OpenSCManager(null, null, (uint)SCM_ACCESS.SC_MANAGER_ALL_ACCESS);
if (_handle == IntPtr.Zero)
{
throw new Exception(string.Format("Error connecting to Service Control Manager. Error provided was: 0x{0:X}", Marshal.GetLastWin32Error()));
}
}
public Service Open(string serviceName)
{
IntPtr svcHandle = Advapi32.OpenService(_handle, serviceName, (int)SERVICE_ACCESS.SERVICE_ALL_ACCESS);
if (svcHandle == IntPtr.Zero)
{
throw new Exception(string.Format("Error opening service for modifying. Error returned was: 0x{0:X}", Marshal.GetLastWin32Error()));
}
return new Service(svcHandle);
}
public void Dispose()
{
if (_handle != IntPtr.Zero)
_ = Advapi32.CloseServiceHandle(_handle);
_handle = IntPtr.Zero;
}
}
public struct Service : IDisposable
{
public IntPtr Handle;
internal Service(IntPtr service)
{
Handle = service;
}
public void SetFailureActions(TimeSpan failureResetPeriod, List<SC_ACTION> actions)
{
SERVICE_FAILURE_ACTIONS sfa = new SERVICE_FAILURE_ACTIONS
{
dwResetPeriod = (int)failureResetPeriod.TotalSeconds,
lpRebootMsg = string.Empty,
lpCommand = string.Empty
};
// delete message
// delete the command to run
int len = Marshal.SizeOf(typeof(SC_ACTION));
sfa.cActions = actions.Count;
sfa.lpsaActions = Marshal.AllocHGlobal(len * actions.Count);
try
{
for (int i = 0; i < actions.Count; i++)
{
Marshal.StructureToPtr(actions[i], new IntPtr(sfa.lpsaActions.ToInt64() + i * len), false);
}
if (!Advapi32.ChangeServiceConfig2(Handle, SERVICE_CONFIG_INFOLEVEL.SERVICE_CONFIG_FAILURE_ACTIONS, sfa))
throw new Exception("Failed to change the failure actions", new Win32Exception());
}
finally
{
Marshal.FreeHGlobal(sfa.lpsaActions);
}
}
/// <summary>
/// Sets the DelayedAutoStart flag.
/// It will be applioed to services with Automatic startup mode only.
/// If the platform does not support this flag, an exception may be thrown.
/// </summary>
/// <param name="enabled">Value to set</param>
/// <exception cref="Exception">Operation failure, e.g. the OS does not support this flag</exception>
public void SetDelayedAutoStart(bool enabled)
{
SERVICE_DELAYED_AUTO_START settings = new SERVICE_DELAYED_AUTO_START
{
fDelayedAutostart = enabled
};
if (!Advapi32.ChangeServiceConfig2(Handle, SERVICE_CONFIG_INFOLEVEL.SERVICE_CONFIG_DELAYED_AUTO_START_INFO, settings))
{
throw new Exception("Failed to change the DelayedAutoStart setting", new Win32Exception());
}
}
public void SetDescription(string description)
{
_ = Advapi32.ChangeServiceConfig2(Handle, SERVICE_CONFIG_INFOLEVEL.SERVICE_CONFIG_DESCRIPTION, new SERVICE_DESCRIPTION { lpDescription = description });
}
public void Dispose()
{
if (Handle != IntPtr.Zero)
_ = Advapi32.CloseServiceHandle(Handle);
Handle = IntPtr.Zero;
}
}
public static class LogonAsAService
{
public static void AddLogonAsAServiceRight(string username)
{
// Needs to be at least XP or 2003 server
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724832%28v=vs.85%29.aspx
OperatingSystem osInfo = Environment.OSVersion;
if (osInfo.Version.Major >= 5 && osInfo.Version.Minor >= 1)
{
var newuser = GetLocalAccountIfLocalAccount(username);
// Trace.WriteLine("Username for Logon as A Service: " + newuser);
long rightexitcode = SetRight(newuser, PrivlegeRights.SeServiceLogonRight.ToString());
if (rightexitcode != 0)
{
Console.WriteLine("Failed to set logon as a service right");
Environment.Exit(1);
}
}
else
{
Console.WriteLine("Cannot set Logon as a Service right. Unsupported operating system detected");
}
}
private static string? GetDomain(string s)
{
int stop = s.IndexOf("\\", StringComparison.Ordinal);
if (stop >= 0)
return s.Substring(0, stop);
else
return null;
}
private static string GetLogin(string s)
{
int stop = s.IndexOf("\\", StringComparison.Ordinal);
return (stop > -1) ? s.Substring(stop + 1, s.Length - stop - 1) : s;
}
private static string GetLocalAccountIfLocalAccount(string username)
{
var machinename = Environment.MachineName;
string? domain = GetDomain(username);
if (domain is null || domain.ToLower() == machinename.ToLower() || domain == ".")
{
return GetLogin(username);
}
return username;
}
/// <summary>Adds a privilege to an account</summary>
/// <param name="accountName">Name of an account - "domain\account" or only "account"</param>
/// <param name="privilegeName">Name ofthe privilege</param>
/// <returns>The windows error code returned by LsaAddAccountRights</returns>
private static long SetRight(string accountName, string privilegeName)
{
long winErrorCode; // contains the last error
// pointer an size for the SID
IntPtr sid = IntPtr.Zero;
int sidSize = 0;
// StringBuilder and size for the domain name
StringBuilder domainName = new StringBuilder();
int nameSize = 0;
// get required buffer size
_ = Advapi32.LookupAccountName(null, accountName, sid, ref sidSize, domainName, ref nameSize, out _);
// allocate buffers
domainName = new StringBuilder(nameSize);
sid = Marshal.AllocHGlobal(sidSize);
// lookup the SID for the account
bool result = Advapi32.LookupAccountName(null, accountName, sid, ref sidSize, domainName, ref nameSize, out _);
// say what you're doing
// Console.WriteLine("LookupAccountName result = " + result);
// Console.WriteLine("IsValidSid: " + Advapi32.IsValidSid(sid));
// Console.WriteLine("LookupAccountName domainName: " + domainName.ToString());
if (!result)
{
winErrorCode = Marshal.GetLastWin32Error();
Console.WriteLine("LookupAccountName failed: " + winErrorCode);
}
else
{
// combine all policies
const int access = (int)(
LSA_AccessPolicy.POLICY_AUDIT_LOG_ADMIN |
LSA_AccessPolicy.POLICY_CREATE_ACCOUNT |
LSA_AccessPolicy.POLICY_CREATE_PRIVILEGE |
LSA_AccessPolicy.POLICY_CREATE_SECRET |
LSA_AccessPolicy.POLICY_GET_PRIVATE_INFORMATION |
LSA_AccessPolicy.POLICY_LOOKUP_NAMES |
LSA_AccessPolicy.POLICY_NOTIFICATION |
LSA_AccessPolicy.POLICY_SERVER_ADMIN |
LSA_AccessPolicy.POLICY_SET_AUDIT_REQUIREMENTS |
LSA_AccessPolicy.POLICY_SET_DEFAULT_QUOTA_LIMITS |
LSA_AccessPolicy.POLICY_TRUST_ADMIN |
LSA_AccessPolicy.POLICY_VIEW_AUDIT_INFORMATION |
LSA_AccessPolicy.POLICY_VIEW_LOCAL_INFORMATION
);
// initialize a pointer for the policy handle
// get a policy handle
uint resultPolicy = Advapi32.LsaOpenPolicy(default, default, access, out IntPtr policyHandle);
winErrorCode = Advapi32.LsaNtStatusToWinError(resultPolicy);
if (winErrorCode != 0)
{
Console.WriteLine("OpenPolicy failed: " + winErrorCode);
}
else
{
// Now that we have the SID an the policy,
// we can add rights to the account.
// initialize an unicode-string for the privilege name
LSA_UNICODE_STRING[] userRights = new LSA_UNICODE_STRING[1];
userRights[0].Buffer = Marshal.StringToHGlobalUni(privilegeName);
userRights[0].Length = (ushort)(privilegeName.Length * UnicodeEncoding.CharSize);
userRights[0].MaximumLength = (ushort)((privilegeName.Length + 1) * UnicodeEncoding.CharSize);
// add the right to the account
uint res = Advapi32.LsaAddAccountRights(policyHandle, sid, userRights, 1);
winErrorCode = Advapi32.LsaNtStatusToWinError(res);
if (winErrorCode != 0)
{
Console.WriteLine("LsaAddAccountRights failed: " + winErrorCode);
}
_ = Advapi32.LsaClose(policyHandle);
}
Advapi32.FreeSid(sid);
}
return winErrorCode;
}
}
/// <summary>
/// Advapi32.dll wrapper for performing additional service related operations that are not
/// available in WMI.
/// </summary>
public class Advapi32
{
private const string Advapi32LibraryName = "advapi32.dll";
[DllImport(Advapi32LibraryName, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "ChangeServiceConfig2W")]
internal static extern bool ChangeServiceConfig2(IntPtr hService, SERVICE_CONFIG_INFOLEVEL dwInfoLevel, in SERVICE_FAILURE_ACTIONS lpInfo);
[DllImport(Advapi32LibraryName, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "ChangeServiceConfig2W")]
internal static extern bool ChangeServiceConfig2(IntPtr hService, SERVICE_CONFIG_INFOLEVEL dwInfoLevel, in SERVICE_DELAYED_AUTO_START lpInfo);
[DllImport(Advapi32LibraryName, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "ChangeServiceConfig2W")]
internal static extern bool ChangeServiceConfig2(IntPtr hService, SERVICE_CONFIG_INFOLEVEL dwInfoLevel, in SERVICE_DESCRIPTION lpInfo);
[DllImport(Advapi32LibraryName, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "OpenSCManagerW")]
internal static extern IntPtr OpenSCManager(string? lpMachineName, string? lpDatabaseName, uint dwDesiredAccess);
[DllImport(Advapi32LibraryName, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "OpenServiceW")]
internal static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess);
[DllImport(Advapi32LibraryName, SetLastError = true)]
internal static extern bool CloseServiceHandle(IntPtr hSCObject);
[DllImport(Advapi32LibraryName)]
public static extern bool SetServiceStatus(IntPtr hServiceStatus, in SERVICE_STATUS lpServiceStatus);
[DllImport(Advapi32LibraryName)]
public static extern bool SetServiceObjectSecurity(IntPtr hService, SecurityInfos dwSecurityInformation, byte[] lpSecurityDescriptor);
[DllImport(Advapi32LibraryName)]
internal static extern uint LsaOpenPolicy(
in LSA_UNICODE_STRING SystemName,
in LSA_OBJECT_ATTRIBUTES ObjectAttributes,
int DesiredAccess,
out IntPtr PolicyHandle);
[DllImport(Advapi32LibraryName, SetLastError = true)]
internal static extern uint LsaAddAccountRights(IntPtr PolicyHandle, IntPtr AccountSid, LSA_UNICODE_STRING[] UserRights, uint CountOfRights);
[DllImport(Advapi32LibraryName)]
internal static extern void FreeSid(IntPtr pSid);
[DllImport(Advapi32LibraryName, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "LookupAccountNameW")]
internal static extern bool LookupAccountName(
string? lpSystemName,
string lpAccountName,
IntPtr psid,
ref int cbsid,
StringBuilder domainName,
ref int cbdomainLength,
out int use);
[DllImport(Advapi32LibraryName)]
internal static extern bool IsValidSid(IntPtr pSid);
[DllImport(Advapi32LibraryName, SetLastError = true)]
internal static extern uint LsaClose(IntPtr ObjectHandle);
[DllImport(Advapi32LibraryName, SetLastError = false)]
internal static extern uint LsaNtStatusToWinError(uint status);
[DllImport(Advapi32LibraryName, SetLastError = true)]
public static extern bool OpenProcessToken(
IntPtr ProcessHandle,
TokenAccessLevels DesiredAccess,
out IntPtr TokenHandle);
[DllImport(Advapi32LibraryName, SetLastError = true)]
public static extern bool GetTokenInformation(
IntPtr TokenHandle,
TOKEN_INFORMATION_CLASS TokenInformationClass,
out TOKEN_ELEVATION TokenInformation,
int TokenInformationLength,
out int ReturnLength);
}
// http://msdn.microsoft.com/en-us/library/windows/desktop/bb545671(v=vs.85).aspx
internal enum PrivlegeRights
{
SeServiceLogonRight, // Required for an account to log on using the service logon type.
SeRemoteInteractiveLogonRight, // Required for an account to log on remotely using the interactive logon type.
SeNetworkLogonRight, // Required for an account to log on using the network logon type.
SeInteractiveLogonRight, // Required for an account to log on using the interactive logon type.
SeDenyServiceLogonRight, // Explicitly denies an account the right to log on using the service logon type.
SeDenyRemoteInteractiveLogonRight, // Explicitly denies an account the right to log on remotely using the interactive logon type.
SeDenyNetworkLogonRight, // Explicitly denies an account the right to log on using the network logon type.
SeDenyInteractiveLogonRight, // Explicitly denies an account the right to log on using the interactive logon type.
SeDenyBatchLogonRight, // Explicitly denies an account the right to log on using the batch logon type.
SeBatchLogonRight // Required for an account to log on using the batch logon type.
}
[StructLayout(LayoutKind.Sequential)]
struct LSA_UNICODE_STRING
{
public ushort Length;
public ushort MaximumLength;
public IntPtr Buffer;
}
[StructLayout(LayoutKind.Sequential)]
struct LSA_OBJECT_ATTRIBUTES
{
public int Length;
public IntPtr RootDirectory;
public LSA_UNICODE_STRING ObjectName;
public uint Attributes;
public IntPtr SecurityDescriptor;
public IntPtr SecurityQualityOfService;
}
// enum all policies
[Flags]
enum LSA_AccessPolicy : long
{
POLICY_VIEW_LOCAL_INFORMATION = 0x00000001L,
POLICY_VIEW_AUDIT_INFORMATION = 0x00000002L,
POLICY_GET_PRIVATE_INFORMATION = 0x00000004L,
POLICY_TRUST_ADMIN = 0x00000008L,
POLICY_CREATE_ACCOUNT = 0x00000010L,
POLICY_CREATE_SECRET = 0x00000020L,
POLICY_CREATE_PRIVILEGE = 0x00000040L,
POLICY_SET_DEFAULT_QUOTA_LIMITS = 0x00000080L,
POLICY_SET_AUDIT_REQUIREMENTS = 0x00000100L,
POLICY_AUDIT_LOG_ADMIN = 0x00000200L,
POLICY_SERVER_ADMIN = 0x00000400L,
POLICY_LOOKUP_NAMES = 0x00000800L,
POLICY_NOTIFICATION = 0x00001000L
}
internal enum SCM_ACCESS : uint
{
/// <summary>
/// Required to connect to the service control manager.
/// </summary>
SC_MANAGER_CONNECT = 0x00001,
/// <summary>
/// Required to call the CreateService function to create a service
/// object and add it to the database.
/// </summary>
SC_MANAGER_CREATE_SERVICE = 0x00002,
/// <summary>
/// Required to call the EnumServicesStatusEx function to list the
/// services that are in the database.
/// </summary>
SC_MANAGER_ENUMERATE_SERVICE = 0x00004,
/// <summary>
/// Required to call the LockServiceDatabase function to acquire a
/// lock on the database.
/// </summary>
SC_MANAGER_LOCK = 0x00008,
/// <summary>
/// Required to call the QueryServiceLockStatus function to retrieve
/// the lock status information for the database.
/// </summary>
SC_MANAGER_QUERY_LOCK_STATUS = 0x00010,
/// <summary>
/// Required to call the NotifyBootConfigStatus function.
/// </summary>
SC_MANAGER_MODIFY_BOOT_CONFIG = 0x00020,
/// <summary>
/// Includes STANDARD_RIGHTS_REQUIRED, in addition to all access
/// rights in this table.
/// </summary>
SC_MANAGER_ALL_ACCESS = ACCESS_MASK.STANDARD_RIGHTS_REQUIRED |
SC_MANAGER_CONNECT |
SC_MANAGER_CREATE_SERVICE |
SC_MANAGER_ENUMERATE_SERVICE |
SC_MANAGER_LOCK |
SC_MANAGER_QUERY_LOCK_STATUS |
SC_MANAGER_MODIFY_BOOT_CONFIG,
GENERIC_READ = ACCESS_MASK.STANDARD_RIGHTS_READ |
SC_MANAGER_ENUMERATE_SERVICE |
SC_MANAGER_QUERY_LOCK_STATUS,
GENERIC_WRITE = ACCESS_MASK.STANDARD_RIGHTS_WRITE |
SC_MANAGER_CREATE_SERVICE |
SC_MANAGER_MODIFY_BOOT_CONFIG,
GENERIC_EXECUTE = ACCESS_MASK.STANDARD_RIGHTS_EXECUTE |
SC_MANAGER_CONNECT | SC_MANAGER_LOCK,
GENERIC_ALL = SC_MANAGER_ALL_ACCESS,
}
[Flags]
internal enum SERVICE_ACCESS : uint
{
STANDARD_RIGHTS_REQUIRED = 0xF0000,
SERVICE_QUERY_CONFIG = 0x00001,
SERVICE_CHANGE_CONFIG = 0x00002,
SERVICE_QUERY_STATUS = 0x00004,
SERVICE_ENUMERATE_DEPENDENTS = 0x00008,
SERVICE_START = 0x00010,
SERVICE_STOP = 0x00020,
SERVICE_PAUSE_CONTINUE = 0x00040,
SERVICE_INTERROGATE = 0x00080,
SERVICE_USER_DEFINED_CONTROL = 0x00100,
SERVICE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED |
SERVICE_QUERY_CONFIG |
SERVICE_CHANGE_CONFIG |
SERVICE_QUERY_STATUS |
SERVICE_ENUMERATE_DEPENDENTS |
SERVICE_START |
SERVICE_STOP |
SERVICE_PAUSE_CONTINUE |
SERVICE_INTERROGATE |
SERVICE_USER_DEFINED_CONTROL
}
[Flags]
internal enum ACCESS_MASK : uint
{
DELETE = 0x00010000,
READ_CONTROL = 0x00020000,
WRITE_DAC = 0x00040000,
WRITE_OWNER = 0x00080000,
SYNCHRONIZE = 0x00100000,
STANDARD_RIGHTS_REQUIRED = 0x000f0000,
STANDARD_RIGHTS_READ = 0x00020000,
STANDARD_RIGHTS_WRITE = 0x00020000,
STANDARD_RIGHTS_EXECUTE = 0x00020000,
STANDARD_RIGHTS_ALL = 0x001f0000,
SPECIFIC_RIGHTS_ALL = 0x0000ffff,
ACCESS_SYSTEM_SECURITY = 0x01000000,
MAXIMUM_ALLOWED = 0x02000000,
GENERIC_READ = 0x80000000,
GENERIC_WRITE = 0x40000000,
GENERIC_EXECUTE = 0x20000000,
GENERIC_ALL = 0x10000000,
DESKTOP_READOBJECTS = 0x00000001,
DESKTOP_CREATEWINDOW = 0x00000002,
DESKTOP_CREATEMENU = 0x00000004,
DESKTOP_HOOKCONTROL = 0x00000008,
DESKTOP_JOURNALRECORD = 0x00000010,
DESKTOP_JOURNALPLAYBACK = 0x00000020,
DESKTOP_ENUMERATE = 0x00000040,
DESKTOP_WRITEOBJECTS = 0x00000080,
DESKTOP_SWITCHDESKTOP = 0x00000100,
WINSTA_ENUMDESKTOPS = 0x00000001,
WINSTA_READATTRIBUTES = 0x00000002,
WINSTA_ACCESSCLIPBOARD = 0x00000004,
WINSTA_CREATEDESKTOP = 0x00000008,
WINSTA_WRITEATTRIBUTES = 0x00000010,
WINSTA_ACCESSGLOBALATOMS = 0x00000020,
WINSTA_EXITWINDOWS = 0x00000040,
WINSTA_ENUMERATE = 0x00000100,
WINSTA_READSCREEN = 0x00000200,
WINSTA_ALL_ACCESS = 0x0000037f
}
public struct SERVICE_STATUS
{
public int serviceType;
public int currentState;
public int controlsAccepted;
public int win32ExitCode;
public int serviceSpecificExitCode;
public int checkPoint;
public int waitHint;
}
public enum State
{
SERVICE_STOPPED = 0x00000001,
SERVICE_START_PENDING = 0x00000002,
SERVICE_STOP_PENDING = 0x00000003,
SERVICE_RUNNING = 0x00000004,
SERVICE_CONTINUE_PENDING = 0x00000005,
SERVICE_PAUSE_PENDING = 0x00000006,
SERVICE_PAUSED = 0x00000007,
}
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms685126(v=vs.85).aspx
[StructLayout(LayoutKind.Sequential)]
public struct SC_ACTION
{
public SC_ACTION_TYPE Type;
/// <summary>
/// The time to wait before performing the specified action, in milliseconds.
/// </summary>
public uint Delay;
public SC_ACTION(SC_ACTION_TYPE type, TimeSpan delay)
{
Type = type;
Delay = (uint)delay.TotalMilliseconds;
}
}
internal enum SERVICE_CONFIG_INFOLEVEL
{
SERVICE_CONFIG_DESCRIPTION = 1,
SERVICE_CONFIG_FAILURE_ACTIONS = 2,
SERVICE_CONFIG_DELAYED_AUTO_START_INFO = 3,
SERVICE_CONFIG_FAILURE_ACTIONS_FLAG = 4,
SERVICE_CONFIG_SERVICE_SID_INFO = 5,
SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO = 6,
SERVICE_CONFIG_PRESHUTDOWN_INFO = 7,
SERVICE_CONFIG_TRIGGER_INFO = 8,
SERVICE_CONFIG_PREFERRED_NODE = 9
}
public enum SC_ACTION_TYPE
{
SC_ACTION_NONE = 0,
SC_ACTION_RESTART = 1,
SC_ACTION_REBOOT = 2,
SC_ACTION_RUN_COMMAND = 3
}
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms685939(v=vs.85).aspx
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct SERVICE_FAILURE_ACTIONS
{
/// <summary>
/// The time after which to reset the failure count to zero if there are no failures, in seconds.
/// Specify INFINITE to indicate that this value should never be reset.
/// </summary>
public int dwResetPeriod;
public string lpRebootMsg;
public string lpCommand;
public int cActions;
public IntPtr/*SC_ACTION[]*/ lpsaActions;
}
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms685155(v=vs.85).aspx
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct SERVICE_DELAYED_AUTO_START
{
public bool fDelayedAutostart;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct SERVICE_DESCRIPTION
{
public string lpDescription;
}
public enum TOKEN_INFORMATION_CLASS
{
TokenElevation = 20,
}
public struct TOKEN_ELEVATION
{
public uint TokenIsElevated;
}
}

View File

@ -0,0 +1,32 @@
using System.Runtime.InteropServices;
namespace winsw.Native
{
internal static class ConsoleApis
{
internal const int ATTACH_PARENT_PROCESS = -1;
[DllImport(Libraries.Kernel32, SetLastError = true)]
internal static extern bool AttachConsole(int processId);
[DllImport(Libraries.Kernel32)]
internal static extern bool FreeConsole();
[DllImport(Libraries.Kernel32)]
internal static extern bool GenerateConsoleCtrlEvent(CtrlEvents ctrlEvent, uint processGroupId);
[DllImport(Libraries.Kernel32)]
internal static extern bool SetConsoleCtrlHandler(ConsoleCtrlHandlerRoutine? handlerRoutine, bool add);
internal delegate bool ConsoleCtrlHandlerRoutine(CtrlEvents ctrlType);
internal enum CtrlEvents : uint
{
CTRL_C_EVENT = 0,
CTRL_BREAK_EVENT = 1,
CTRL_CLOSE_EVENT = 2,
CTRL_LOGOFF_EVENT = 5,
CTRL_SHUTDOWN_EVENT = 6,
}
}
}

View File

@ -1,7 +1,7 @@
namespace winsw.Native
{
public static class Errors
internal static class Errors
{
public const int ERROR_CANCELLED = 1223;
internal const int ERROR_CANCELLED = 1223;
}
}

View File

@ -0,0 +1,11 @@
using System;
using System.Runtime.InteropServices;
namespace winsw.Native
{
internal static class HandleApis
{
[DllImport(Libraries.Kernel32)]
internal static extern bool CloseHandle(IntPtr objectHandle);
}
}

View File

@ -1,70 +1,11 @@
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
namespace winsw.Native
{
/// <summary>
/// kernel32.dll P/Invoke wrappers
/// </summary>
public class Kernel32
internal static class Kernel32
{
public const uint CREATE_NEW_PROCESS_GROUP = 0x00000200;
private const string Kernel32LibraryName = "kernel32.dll";
[DllImport(Kernel32LibraryName, SetLastError = true)]
public static extern bool SetStdHandle(int nStdHandle, SafeFileHandle handle);
[DllImport(Kernel32LibraryName, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "CreateProcessW")]
public static extern bool CreateProcess(
string? lpApplicationName,
string? lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
bool bInheritHandles,
uint dwCreationFlags,
IntPtr lpEnvironment,
string? lpCurrentDirectory,
in STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport(Kernel32LibraryName)]
public static extern IntPtr GetCurrentProcess();
[DllImport(Kernel32LibraryName)]
public static extern bool CloseHandle(IntPtr hObject);
}
[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct STARTUPINFO
{
public int cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public int dwX;
public int dwY;
public int dwXSize;
public int dwYSize;
public int dwXCountChars;
public int dwYCountChars;
public int dwFillAttribute;
public int dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
[DllImport(Libraries.Kernel32)]
internal static extern bool SetStdHandle(int stdHandle, SafeFileHandle handle);
}
}

View File

@ -0,0 +1,8 @@
namespace winsw.Native
{
internal static class Libraries
{
internal const string Advapi32 = "advapi32.dll";
internal const string Kernel32 = "kernel32.dll";
}
}

View File

@ -0,0 +1,64 @@
using System;
using System.Runtime.InteropServices;
using System.Security.Principal;
namespace winsw.Native
{
internal static class ProcessApis
{
internal const uint CREATE_NEW_PROCESS_GROUP = 0x00000200;
[DllImport(Libraries.Kernel32, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "CreateProcessW")]
internal static extern bool CreateProcess(
string? applicationName,
string? commandLine,
IntPtr processAttributes,
IntPtr threadAttributes,
bool inheritHandles,
uint creationFlags,
IntPtr environment,
string? currentDirectory,
in STARTUPINFO startupInfo,
out PROCESS_INFORMATION processInformation);
[DllImport(Libraries.Kernel32)]
internal static extern IntPtr GetCurrentProcess();
[DllImport(Libraries.Advapi32, SetLastError = true)]
internal static extern bool OpenProcessToken(
IntPtr processHandle,
TokenAccessLevels desiredAccess,
out IntPtr tokenHandle);
internal struct PROCESS_INFORMATION
{
public IntPtr ProcessHandle;
public IntPtr ThreadHandle;
public int ProcessId;
public int ThreadId;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct STARTUPINFO
{
public int Size;
public string Reserved;
public string Desktop;
public string Title;
public int X;
public int Y;
public int XSize;
public int YSize;
public int XCountChars;
public int YCountChars;
public int FillAttribute;
public int Flags;
public short ShowWindow;
public short ReservedSize2;
public IntPtr Reserved2;
public IntPtr StdInputHandle;
public IntPtr StdOutputHandle;
public IntPtr StdErrorHandle;
}
}
}

View File

@ -0,0 +1,85 @@
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using static winsw.Native.SecurityApis;
namespace winsw.Native
{
internal static class Security
{
internal static void AddServiceLogonRight(string domain, string user)
{
IntPtr sid = GetAccountSid(domain, user);
try
{
AddAccountRight(sid, AccountRights.SE_SERVICE_LOGON_NAME);
}
finally
{
_ = FreeSid(sid);
Marshal.FreeHGlobal(sid);
}
}
private static IntPtr GetAccountSid(string domain, string user)
{
int sidSize = 0;
int domainNameLength = 0;
if (domain == ".")
{
domain = Environment.MachineName;
}
string accountName = domain + "\\" + user;
_ = LookupAccountName(null, accountName, IntPtr.Zero, ref sidSize, IntPtr.Zero, ref domainNameLength, out _);
IntPtr sid = Marshal.AllocHGlobal(sidSize);
IntPtr domainName = Marshal.AllocHGlobal(domainNameLength * sizeof(char));
try
{
if (!LookupAccountName(null, accountName, sid, ref sidSize, domainName, ref domainNameLength, out _))
{
Throw.Win32Exception("Failed to find the account.");
}
return sid;
}
finally
{
Marshal.FreeHGlobal(domainName);
}
}
private static void AddAccountRight(IntPtr sid, string rightName)
{
uint status = LsaOpenPolicy(IntPtr.Zero, default, PolicyAccess.ALL_ACCESS, out IntPtr policyHandle);
if (status != 0)
{
throw new Win32Exception(LsaNtStatusToWinError(status));
}
try
{
LSA_UNICODE_STRING userRight = new LSA_UNICODE_STRING
{
Buffer = rightName,
Length = (ushort)(rightName.Length * sizeof(char)),
MaximumLength = (ushort)((rightName.Length + 1) * sizeof(char)),
};
status = LsaAddAccountRights(policyHandle, sid, userRight, 1);
if (status != 0)
{
throw new Win32Exception(LsaNtStatusToWinError(status));
}
}
finally
{
_ = LsaClose(policyHandle);
}
}
}
}

View File

@ -0,0 +1,128 @@
using System;
using System.Runtime.InteropServices;
namespace winsw.Native
{
internal static class SecurityApis
{
[DllImport(Libraries.Advapi32, SetLastError = false)]
internal static extern IntPtr FreeSid(IntPtr sid);
[DllImport(Libraries.Advapi32, SetLastError = true)]
internal static extern bool GetTokenInformation(
IntPtr tokenHandle,
TOKEN_INFORMATION_CLASS tokenInformationClass,
out TOKEN_ELEVATION tokenInformation,
int tokenInformationLength,
out int returnLength);
[DllImport(Libraries.Advapi32, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "LookupAccountNameW")]
internal static extern bool LookupAccountName(
string? systemName,
string accountName,
IntPtr sid,
ref int sidSize,
IntPtr referencedDomainName,
ref int referencedDomainNameLength,
out int use);
[DllImport(Libraries.Advapi32, SetLastError = false)]
internal static extern uint LsaAddAccountRights(IntPtr policyHandle, IntPtr accountSid, in LSA_UNICODE_STRING/*[]*/ userRights, uint countOfRights);
[DllImport(Libraries.Advapi32, SetLastError = false)]
internal static extern uint LsaClose(IntPtr objectHandle);
[DllImport(Libraries.Advapi32, SetLastError = false)]
internal static extern int LsaNtStatusToWinError(uint status);
[DllImport(Libraries.Advapi32, SetLastError = false)]
internal static extern uint LsaOpenPolicy(
IntPtr systemName,
in LSA_OBJECT_ATTRIBUTES objectAttributes,
PolicyAccess desiredAccess,
out IntPtr policyHandle);
// POLICY_
// https://docs.microsoft.com/windows/win32/secmgmt/policy-object-access-rights
[Flags]
internal enum PolicyAccess : uint
{
VIEW_LOCAL_INFORMATION = 0x00000001,
VIEW_AUDIT_INFORMATION = 0x00000002,
GET_PRIVATE_INFORMATION = 0x00000004,
TRUST_ADMIN = 0x00000008,
CREATE_ACCOUNT = 0x00000010,
CREATE_SECRET = 0x00000020,
CREATE_PRIVILEGE = 0x00000040,
SET_DEFAULT_QUOTA_LIMITS = 0x00000080,
SET_AUDIT_REQUIREMENTS = 0x00000100,
AUDIT_LOG_ADMIN = 0x00000200,
SERVER_ADMIN = 0x00000400,
LOOKUP_NAMES = 0x00000800,
NOTIFICATION = 0x00001000,
ALL_ACCESS =
StandardAccess.REQUIRED |
VIEW_LOCAL_INFORMATION |
VIEW_AUDIT_INFORMATION |
GET_PRIVATE_INFORMATION |
TRUST_ADMIN |
CREATE_ACCOUNT |
CREATE_SECRET |
CREATE_PRIVILEGE |
SET_DEFAULT_QUOTA_LIMITS |
SET_AUDIT_REQUIREMENTS |
AUDIT_LOG_ADMIN |
SERVER_ADMIN |
LOOKUP_NAMES,
}
// STANDARD_RIGHTS_
// https://docs.microsoft.com/windows/win32/secauthz/standard-access-rights
[Flags]
internal enum StandardAccess : uint
{
REQUIRED = 0x000F0000,
READ = 0x00020000,
WRITE = 0x00020000,
EXECUTE = 0x00020000,
ALL = 0x001F0000,
}
internal enum TOKEN_INFORMATION_CLASS
{
TokenElevation = 20,
}
internal struct LSA_OBJECT_ATTRIBUTES
{
public uint Length;
public IntPtr RootDirectory;
public LSA_UNICODE_STRING ObjectName;
public uint Attributes;
public IntPtr SecurityDescriptor;
public IntPtr SecurityQualityOfService;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct LSA_UNICODE_STRING
{
public ushort Length;
public ushort MaximumLength;
public string Buffer;
}
internal struct TOKEN_ELEVATION
{
public uint TokenIsElevated;
}
// https://docs.microsoft.com/windows/win32/secauthz/account-rights-constants
internal static class AccountRights
{
internal const string SE_SERVICE_LOGON_NAME = "SeServiceLogonRight";
}
}
}

View File

@ -0,0 +1,156 @@
using System;
using System.Security.AccessControl;
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()
{
IntPtr handle = OpenSCManager(null, null, ServiceManagerAccess.ALL_ACCESS);
if (handle == IntPtr.Zero)
{
Throw.Win32Exception("Failed to open the service control manager database.");
}
return new ServiceManager(handle);
}
internal Service OpenService(string serviceName)
{
IntPtr serviceHandle = ServiceApis.OpenService(this.handle, serviceName, ServiceAccess.ALL_ACCESS);
if (serviceHandle == IntPtr.Zero)
{
Throw.Win32Exception("Failed to open the service.");
}
return new Service(serviceHandle);
}
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 void SetDescription(string description)
{
if (!ChangeServiceConfig2(
this.handle,
ServiceConfigInfoLevels.DESCRIPTION,
new SERVICE_DESCRIPTION { Description = description }))
{
Throw.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.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.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.Win32Exception("Failed to configure the security descriptor.");
}
}
public void Dispose()
{
if (this.handle != IntPtr.Zero)
{
_ = CloseServiceHandle(this.handle);
}
this.handle = IntPtr.Zero;
}
}
}

View File

@ -0,0 +1,144 @@
using System;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
namespace winsw.Native
{
internal static class ServiceApis
{
[DllImport(Libraries.Advapi32, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "ChangeServiceConfig2W")]
internal static extern bool ChangeServiceConfig2(IntPtr serviceHandle, ServiceConfigInfoLevels infoLevel, in SERVICE_DESCRIPTION info);
[DllImport(Libraries.Advapi32, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "ChangeServiceConfig2W")]
internal static extern bool ChangeServiceConfig2(IntPtr serviceHandle, ServiceConfigInfoLevels infoLevel, in SERVICE_FAILURE_ACTIONS info);
[DllImport(Libraries.Advapi32, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "ChangeServiceConfig2W")]
internal static extern bool ChangeServiceConfig2(IntPtr serviceHandle, ServiceConfigInfoLevels infoLevel, in SERVICE_DELAYED_AUTO_START_INFO info);
[DllImport(Libraries.Advapi32)]
internal static extern bool CloseServiceHandle(IntPtr objectHandle);
[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 SetServiceObjectSecurity(IntPtr serviceHandle, SecurityInfos securityInformation, byte[] securityDescriptor);
[DllImport(Libraries.Advapi32)]
internal static extern bool SetServiceStatus(IntPtr serviceStatusHandle, in SERVICE_STATUS serviceStatus);
// SERVICE_
// https://docs.microsoft.com/windows/win32/services/service-security-and-access-rights
[Flags]
internal enum ServiceAccess : uint
{
QUERY_CONFIG = 0x0001,
CHANGE_CONFIG = 0x0002,
QUERY_STATUS = 0x0004,
ENUMERATE_DEPENDENTS = 0x0008,
START = 0x0010,
STOP = 0x0020,
PAUSE_CONTINUE = 0x0040,
INTERROGATE = 0x0080,
USER_DEFINED_CONTROL = 0x0100,
ALL_ACCESS =
SecurityApis.StandardAccess.REQUIRED |
QUERY_CONFIG |
CHANGE_CONFIG |
QUERY_STATUS |
ENUMERATE_DEPENDENTS |
START |
STOP |
PAUSE_CONTINUE |
INTERROGATE |
USER_DEFINED_CONTROL,
}
// SERVICE_CONFIG_
// https://docs.microsoft.com/windows/win32/api/winsvc/nf-winsvc-changeserviceconfig2w
internal enum ServiceConfigInfoLevels : uint
{
DESCRIPTION = 1,
FAILURE_ACTIONS = 2,
DELAYED_AUTO_START_INFO = 3,
FAILURE_ACTIONS_FLAG = 4,
SERVICE_SID_INFO = 5,
REQUIRED_PRIVILEGES_INFO = 6,
PRESHUTDOWN_INFO = 7,
TRIGGER_INFO = 8,
PREFERRED_NODE = 9,
}
// SERVICE_
// https://docs.microsoft.com/windows/win32/api/winsvc/ns-winsvc-service_status
internal enum ServiceState : uint
{
STOPPED = 0x00000001,
START_PENDING = 0x00000002,
STOP_PENDING = 0x00000003,
RUNNING = 0x00000004,
CONTINUE_PENDING = 0x00000005,
PAUSE_PENDING = 0x00000006,
PAUSED = 0x00000007,
}
// SC_MANAGER_
// https://docs.microsoft.com/windows/win32/services/service-security-and-access-rights
[Flags]
internal enum ServiceManagerAccess : uint
{
CONNECT = 0x0001,
CREATE_SERVICE = 0x0002,
ENUMERATE_SERVICE = 0x0004,
LOCK = 0x0008,
QUERY_LOCK_STATUS = 0x0010,
MODIFY_BOOT_CONFIG = 0x0020,
ALL_ACCESS =
SecurityApis.StandardAccess.REQUIRED |
CONNECT |
CREATE_SERVICE |
ENUMERATE_SERVICE |
LOCK |
QUERY_LOCK_STATUS |
MODIFY_BOOT_CONFIG,
}
internal struct SERVICE_DELAYED_AUTO_START_INFO
{
public bool DelayedAutostart;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct SERVICE_DESCRIPTION
{
public string Description;
}
// 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
{
public int ResetPeriod;
public string RebootMessage;
public string Command;
public int ActionsCount;
public SC_ACTION* Actions;
}
internal struct SERVICE_STATUS
{
public int ServiceType;
public ServiceState CurrentState;
public int ControlsAccepted;
public int Win32ExitCode;
public int ServiceSpecificExitCode;
public int CheckPoint;
public int WaitHint;
}
}
}

View File

@ -0,0 +1,16 @@
using System.ComponentModel;
using System.Diagnostics;
namespace winsw.Native
{
internal static class Throw
{
internal static void Win32Exception(string message)
{
Win32Exception inner = new Win32Exception();
Debug.Assert(inner.NativeErrorCode != 0);
Debug.Assert(message.EndsWith("."));
throw new Win32Exception(inner.NativeErrorCode, message + ' ' + inner.Message);
}
}
}

View File

@ -595,17 +595,17 @@ namespace winsw
}
}
public List<SC_ACTION> FailureActions
public SC_ACTION[] FailureActions
{
get
{
XmlNodeList? childNodes = dom.SelectNodes("//onfailure");
if (childNodes is null)
{
return new List<SC_ACTION>(0);
return new SC_ACTION[0];
}
List<SC_ACTION> result = new List<SC_ACTION>(childNodes.Count);
SC_ACTION[] result = new SC_ACTION[childNodes.Count];
for (int i = 0; i < childNodes.Count; i++)
{
XmlNode node = childNodes[i];
@ -618,7 +618,7 @@ namespace winsw
_ => throw new Exception("Invalid failure action: " + action)
};
XmlAttribute? delay = node.Attributes["delay"];
result.Add(new SC_ACTION(type, delay != null ? ParseTimeSpan(delay.Value) : TimeSpan.Zero));
result[i] = new SC_ACTION(type, delay != null ? ParseTimeSpan(delay.Value) : TimeSpan.Zero);
}
return result;
@ -645,9 +645,9 @@ namespace winsw
protected string? AllowServiceLogon => GetServiceAccountPart("allowservicelogon");
protected string? ServiceAccountDomain => GetServiceAccountPart("domain");
protected internal string? ServiceAccountDomain => GetServiceAccountPart("domain");
protected string? ServiceAccountName => GetServiceAccountPart("user");
protected internal string? ServiceAccountName => GetServiceAccountPart("user");
public string? ServiceAccountPassword => GetServiceAccountPart("password");

View File

@ -2,6 +2,7 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using log4net;
using winsw.Native;
namespace winsw.Util
{
@ -9,35 +10,6 @@ namespace winsw.Util
{
private static readonly ILog Logger = LogManager.GetLogger(typeof(SigIntHelper));
public const int ATTACH_PARENT_PROCESS = -1;
private const string Kernel32LibraryName = "kernel32.dll";
[DllImport(Kernel32LibraryName, SetLastError = true)]
public static extern bool AttachConsole(int dwProcessId);
[DllImport(Kernel32LibraryName, SetLastError = true)]
public static extern bool FreeConsole();
[DllImport(Kernel32LibraryName)]
private static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate? HandlerRoutine, bool Add);
// Delegate type to be used as the Handler Routine for SCCH
private delegate bool ConsoleCtrlDelegate(CtrlTypes CtrlType);
// Enumerated type for the control messages sent to the handler routine
private enum CtrlTypes : uint
{
CTRL_C_EVENT = 0,
CTRL_BREAK_EVENT,
CTRL_CLOSE_EVENT,
CTRL_LOGOFF_EVENT = 5,
CTRL_SHUTDOWN_EVENT
}
[DllImport(Kernel32LibraryName)]
private static extern bool GenerateConsoleCtrlEvent(CtrlTypes dwCtrlEvent, uint dwProcessGroupId);
/// <summary>
/// Uses the native funciton "AttachConsole" to attach the thread to the executing process to try to trigger a CTRL_C event (SIGINT). If the application
/// doesn't honor the event and shut down gracefully, the. wait period will time out after 15 seconds.
@ -46,26 +18,23 @@ namespace winsw.Util
/// <returns>True if the process shut down successfully to the SIGINT, false if it did not.</returns>
public static bool SendSIGINTToProcess(Process process, TimeSpan shutdownTimeout)
{
if (AttachConsole(process.Id))
if (!ConsoleApis.AttachConsole(process.Id))
{
// Disable Ctrl-C handling for our program
_ = SetConsoleCtrlHandler(null, true);
_ = GenerateConsoleCtrlEvent(CtrlTypes.CTRL_C_EVENT, 0);
process.WaitForExit((int)shutdownTimeout.TotalMilliseconds);
// Detach from console. Causes child console process to be automatically closed.
bool success = FreeConsole();
if (!success)
{
long errorCode = Marshal.GetLastWin32Error();
Logger.Warn("Failed to detach from console. Error code: " + errorCode);
}
return process.HasExited;
Logger.Warn("Failed to attach to console. Error code: " + Marshal.GetLastWin32Error());
return false;
}
return false;
// Disable Ctrl-C handling for our program
_ = ConsoleApis.SetConsoleCtrlHandler(null, true);
_ = ConsoleApis.GenerateConsoleCtrlEvent(ConsoleApis.CtrlEvents.CTRL_C_EVENT, 0);
process.WaitForExit((int)shutdownTimeout.TotalMilliseconds);
// Detach from console. Causes child console process to be automatically closed.
bool succeeded = ConsoleApis.FreeConsole();
Debug.Assert(succeeded);
return process.HasExited;
}
}
}

View File

@ -4,6 +4,7 @@
<TargetFrameworks>net20;net40;net461;netcoreapp3.1</TargetFrameworks>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<RootNamespace>winsw</RootNamespace>
</PropertyGroup>