mirror of https://github.com/winsw/winsw
Reorganize interop codes
parent
ee29eee8cf
commit
f260d7e5d9
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("WindowsService")]
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
namespace winsw.Native
|
||||
{
|
||||
internal static class Libraries
|
||||
{
|
||||
internal const string Advapi32 = "advapi32.dll";
|
||||
internal const string Kernel32 = "kernel32.dll";
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
<TargetFrameworks>net20;net40;net461;netcoreapp3.1</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
|
||||
<RootNamespace>winsw</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
|
Loading…
Reference in New Issue