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
|
public class WrapperService : ServiceBase, EventLogger
|
||||||
{
|
{
|
||||||
private SERVICE_STATUS _wrapperServiceStatus;
|
private ServiceApis.SERVICE_STATUS _wrapperServiceStatus;
|
||||||
|
|
||||||
private readonly Process _process = new Process();
|
private readonly Process _process = new Process();
|
||||||
private readonly ServiceDescriptor _descriptor;
|
private readonly ServiceDescriptor _descriptor;
|
||||||
|
@ -434,10 +434,10 @@ namespace winsw
|
||||||
private void SignalShutdownComplete()
|
private void SignalShutdownComplete()
|
||||||
{
|
{
|
||||||
IntPtr handle = ServiceHandle;
|
IntPtr handle = ServiceHandle;
|
||||||
_wrapperServiceStatus.checkPoint++;
|
_wrapperServiceStatus.CheckPoint++;
|
||||||
// WriteEvent("SignalShutdownComplete " + wrapperServiceStatus.checkPoint + ":" + wrapperServiceStatus.waitHint);
|
// WriteEvent("SignalShutdownComplete " + wrapperServiceStatus.checkPoint + ":" + wrapperServiceStatus.waitHint);
|
||||||
_wrapperServiceStatus.currentState = (int)State.SERVICE_STOPPED;
|
_wrapperServiceStatus.CurrentState = ServiceApis.ServiceState.STOPPED;
|
||||||
Advapi32.SetServiceStatus(handle, _wrapperServiceStatus);
|
ServiceApis.SetServiceStatus(handle, _wrapperServiceStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StartProcess(Process processToStart, string arguments, string executable, LogHandler? logHandler, bool redirectStdin)
|
private void StartProcess(Process processToStart, string arguments, string executable, LogHandler? logHandler, bool redirectStdin)
|
||||||
|
@ -590,8 +590,8 @@ namespace winsw
|
||||||
{
|
{
|
||||||
elevated = true;
|
elevated = true;
|
||||||
|
|
||||||
_ = SigIntHelper.FreeConsole();
|
_ = ConsoleApis.FreeConsole();
|
||||||
_ = SigIntHelper.AttachConsole(SigIntHelper.ATTACH_PARENT_PROCESS);
|
_ = ConsoleApis.AttachConsole(ConsoleApis.ATTACH_PARENT_PROCESS);
|
||||||
|
|
||||||
args = args.GetRange(1, args.Count - 1);
|
args = args.GetRange(1, args.Count - 1);
|
||||||
}
|
}
|
||||||
|
@ -714,7 +714,7 @@ namespace winsw
|
||||||
|
|
||||||
if (setallowlogonasaserviceright)
|
if (setallowlogonasaserviceright)
|
||||||
{
|
{
|
||||||
LogonAsAService.AddLogonAsAServiceRight(username!);
|
Security.AddServiceLogonRight(descriptor.ServiceAccountDomain!, descriptor.ServiceAccountName!);
|
||||||
}
|
}
|
||||||
|
|
||||||
svc.Create(
|
svc.Create(
|
||||||
|
@ -729,13 +729,13 @@ namespace winsw
|
||||||
password,
|
password,
|
||||||
descriptor.ServiceDependencies);
|
descriptor.ServiceDependencies);
|
||||||
|
|
||||||
using ServiceManager scm = new ServiceManager();
|
using ServiceManager scm = ServiceManager.Open();
|
||||||
using Service sc = scm.Open(descriptor.Id);
|
using Service sc = scm.OpenService(descriptor.Id);
|
||||||
|
|
||||||
sc.SetDescription(descriptor.Description);
|
sc.SetDescription(descriptor.Description);
|
||||||
|
|
||||||
var actions = descriptor.FailureActions;
|
var actions = descriptor.FailureActions;
|
||||||
if (actions.Count > 0)
|
if (actions.Length > 0)
|
||||||
{
|
{
|
||||||
sc.SetFailureActions(descriptor.ResetFailureAfter, actions);
|
sc.SetFailureActions(descriptor.ResetFailureAfter, actions);
|
||||||
}
|
}
|
||||||
|
@ -746,13 +746,11 @@ namespace winsw
|
||||||
sc.SetDelayedAutoStart(true);
|
sc.SetDelayedAutoStart(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (descriptor.SecurityDescriptor != null)
|
var securityDescriptor = descriptor.SecurityDescriptor;
|
||||||
|
if (securityDescriptor != null)
|
||||||
{
|
{
|
||||||
// throws ArgumentException
|
// throws ArgumentException
|
||||||
RawSecurityDescriptor rawSecurityDescriptor = new RawSecurityDescriptor(descriptor.SecurityDescriptor);
|
sc.SetSecurityDescriptor(new RawSecurityDescriptor(securityDescriptor));
|
||||||
byte[] securityDescriptorBytes = new byte[rawSecurityDescriptor.BinaryLength];
|
|
||||||
rawSecurityDescriptor.GetBinaryForm(securityDescriptorBytes, 0);
|
|
||||||
_ = Advapi32.SetServiceObjectSecurity(sc.Handle, SecurityInfos.DiscretionaryAcl, securityDescriptorBytes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string eventLogSource = descriptor.Id;
|
string eventLogSource = descriptor.Id;
|
||||||
|
@ -919,7 +917,7 @@ namespace winsw
|
||||||
|
|
||||||
// run restart from another process group. see README.md for why this is useful.
|
// 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)
|
if (!result)
|
||||||
{
|
{
|
||||||
throw new Exception("Failed to invoke restart: " + Marshal.GetLastWin32Error());
|
throw new Exception("Failed to invoke restart: " + Marshal.GetLastWin32Error());
|
||||||
|
@ -1063,19 +1061,19 @@ namespace winsw
|
||||||
|
|
||||||
internal static unsafe bool IsProcessElevated()
|
internal static unsafe bool IsProcessElevated()
|
||||||
{
|
{
|
||||||
IntPtr process = Kernel32.GetCurrentProcess();
|
IntPtr process = ProcessApis.GetCurrentProcess();
|
||||||
if (!Advapi32.OpenProcessToken(process, TokenAccessLevels.Read, out IntPtr token))
|
if (!ProcessApis.OpenProcessToken(process, TokenAccessLevels.Read, out IntPtr token))
|
||||||
{
|
{
|
||||||
ThrowWin32Exception("Failed to open process token.");
|
ThrowWin32Exception("Failed to open process token.");
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!Advapi32.GetTokenInformation(
|
if (!SecurityApis.GetTokenInformation(
|
||||||
token,
|
token,
|
||||||
TOKEN_INFORMATION_CLASS.TokenElevation,
|
SecurityApis.TOKEN_INFORMATION_CLASS.TokenElevation,
|
||||||
out TOKEN_ELEVATION elevation,
|
out SecurityApis.TOKEN_ELEVATION elevation,
|
||||||
sizeof(TOKEN_ELEVATION),
|
sizeof(SecurityApis.TOKEN_ELEVATION),
|
||||||
out _))
|
out _))
|
||||||
{
|
{
|
||||||
ThrowWin32Exception("Failed to get token information");
|
ThrowWin32Exception("Failed to get token information");
|
||||||
|
@ -1085,7 +1083,7 @@ namespace winsw
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_ = Kernel32.CloseHandle(token);
|
_ = HandleApis.CloseHandle(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ThrowWin32Exception(string message)
|
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 bool AllowServiceAcountLogonRight => false;
|
||||||
public string? ServiceAccountPassword => null;
|
public string? ServiceAccountPassword => null;
|
||||||
public string? ServiceAccountUser => 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);
|
public TimeSpan ResetFailureAfter => TimeSpan.FromDays(1);
|
||||||
|
|
||||||
// Executable management
|
// Executable management
|
||||||
|
|
|
@ -21,7 +21,7 @@ namespace winsw.Configuration
|
||||||
bool AllowServiceAcountLogonRight { get; }
|
bool AllowServiceAcountLogonRight { get; }
|
||||||
string? ServiceAccountPassword { get; }
|
string? ServiceAccountPassword { get; }
|
||||||
string? ServiceAccountUser { get; }
|
string? ServiceAccountUser { get; }
|
||||||
List<Native.SC_ACTION> FailureActions { get; }
|
Native.SC_ACTION[] FailureActions { get; }
|
||||||
TimeSpan ResetFailureAfter { get; }
|
TimeSpan ResetFailureAfter { get; }
|
||||||
|
|
||||||
// Executable management
|
// 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
|
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;
|
using Microsoft.Win32.SafeHandles;
|
||||||
|
|
||||||
namespace winsw.Native
|
namespace winsw.Native
|
||||||
{
|
{
|
||||||
/// <summary>
|
internal static class Kernel32
|
||||||
/// kernel32.dll P/Invoke wrappers
|
|
||||||
/// </summary>
|
|
||||||
public class Kernel32
|
|
||||||
{
|
{
|
||||||
public const uint CREATE_NEW_PROCESS_GROUP = 0x00000200;
|
[DllImport(Libraries.Kernel32)]
|
||||||
|
internal static extern bool SetStdHandle(int stdHandle, SafeFileHandle handle);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
get
|
||||||
{
|
{
|
||||||
XmlNodeList? childNodes = dom.SelectNodes("//onfailure");
|
XmlNodeList? childNodes = dom.SelectNodes("//onfailure");
|
||||||
if (childNodes is null)
|
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++)
|
for (int i = 0; i < childNodes.Count; i++)
|
||||||
{
|
{
|
||||||
XmlNode node = childNodes[i];
|
XmlNode node = childNodes[i];
|
||||||
|
@ -618,7 +618,7 @@ namespace winsw
|
||||||
_ => throw new Exception("Invalid failure action: " + action)
|
_ => throw new Exception("Invalid failure action: " + action)
|
||||||
};
|
};
|
||||||
XmlAttribute? delay = node.Attributes["delay"];
|
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;
|
return result;
|
||||||
|
@ -645,9 +645,9 @@ namespace winsw
|
||||||
|
|
||||||
protected string? AllowServiceLogon => GetServiceAccountPart("allowservicelogon");
|
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");
|
public string? ServiceAccountPassword => GetServiceAccountPart("password");
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using log4net;
|
using log4net;
|
||||||
|
using winsw.Native;
|
||||||
|
|
||||||
namespace winsw.Util
|
namespace winsw.Util
|
||||||
{
|
{
|
||||||
|
@ -9,35 +10,6 @@ namespace winsw.Util
|
||||||
{
|
{
|
||||||
private static readonly ILog Logger = LogManager.GetLogger(typeof(SigIntHelper));
|
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>
|
/// <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
|
/// 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.
|
/// 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>
|
/// <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)
|
public static bool SendSIGINTToProcess(Process process, TimeSpan shutdownTimeout)
|
||||||
{
|
{
|
||||||
if (AttachConsole(process.Id))
|
if (!ConsoleApis.AttachConsole(process.Id))
|
||||||
{
|
{
|
||||||
|
Logger.Warn("Failed to attach to console. Error code: " + Marshal.GetLastWin32Error());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Disable Ctrl-C handling for our program
|
// Disable Ctrl-C handling for our program
|
||||||
_ = SetConsoleCtrlHandler(null, true);
|
_ = ConsoleApis.SetConsoleCtrlHandler(null, true);
|
||||||
_ = GenerateConsoleCtrlEvent(CtrlTypes.CTRL_C_EVENT, 0);
|
_ = ConsoleApis.GenerateConsoleCtrlEvent(ConsoleApis.CtrlEvents.CTRL_C_EVENT, 0);
|
||||||
|
|
||||||
process.WaitForExit((int)shutdownTimeout.TotalMilliseconds);
|
process.WaitForExit((int)shutdownTimeout.TotalMilliseconds);
|
||||||
|
|
||||||
// Detach from console. Causes child console process to be automatically closed.
|
// Detach from console. Causes child console process to be automatically closed.
|
||||||
bool success = FreeConsole();
|
bool succeeded = ConsoleApis.FreeConsole();
|
||||||
if (!success)
|
Debug.Assert(succeeded);
|
||||||
{
|
|
||||||
long errorCode = Marshal.GetLastWin32Error();
|
|
||||||
Logger.Warn("Failed to detach from console. Error code: " + errorCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
return process.HasExited;
|
return process.HasExited;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
<TargetFrameworks>net20;net40;net461;netcoreapp3.1</TargetFrameworks>
|
<TargetFrameworks>net20;net40;net461;netcoreapp3.1</TargetFrameworks>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
|
||||||
<RootNamespace>winsw</RootNamespace>
|
<RootNamespace>winsw</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
Loading…
Reference in New Issue