mirror of https://github.com/winsw/winsw
Auto refresh
parent
144cff7f19
commit
2fc786a26a
|
@ -677,6 +677,8 @@ namespace WinSW
|
|||
|
||||
public string? SecurityDescriptor => this.SingleElement("securityDescriptor", true);
|
||||
|
||||
public bool AutoRefresh => this.SingleBoolElement("autoRefresh", true);
|
||||
|
||||
private Dictionary<string, string> LoadEnvironmentVariables()
|
||||
{
|
||||
XmlNodeList nodeList = this.dom.SelectNodes("//env");
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace WinSW.Native
|
||||
{
|
||||
internal static class RegistryApis
|
||||
{
|
||||
[DllImport(Libraries.Advapi32, CharSet = CharSet.Unicode)]
|
||||
internal static extern unsafe int RegQueryInfoKeyW(
|
||||
SafeRegistryHandle keyHandle,
|
||||
char* @class,
|
||||
int* classLength,
|
||||
int* reserved,
|
||||
int* subKeysCount,
|
||||
int* maxSubKeyLength,
|
||||
int* maxClassLength,
|
||||
int* valuesCount,
|
||||
int* maxValueNameLength,
|
||||
int* maxValueLength,
|
||||
int* securityDescriptorLength,
|
||||
out FILETIME lastWriteTime);
|
||||
|
||||
internal struct FILETIME
|
||||
{
|
||||
internal int LowDateTime;
|
||||
internal int HighDateTime;
|
||||
|
||||
public long ToTicks() => ((long)this.HighDateTime << 32) + this.LowDateTime;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
using Microsoft.Win32;
|
||||
using WinSW.Native;
|
||||
using static WinSW.Native.RegistryApis;
|
||||
|
||||
namespace WinSW.Util
|
||||
{
|
||||
internal static class RegistryKeyExtensions
|
||||
{
|
||||
/// <exception cref="CommandException" />
|
||||
internal static unsafe DateTime GetLastWriteTime(this RegistryKey registryKey)
|
||||
{
|
||||
int error = RegQueryInfoKeyW(registryKey.Handle, null, null, null, null, null, null, null, null, null, null, out FILETIME lastWriteTime);
|
||||
if (error != Errors.ERROR_SUCCESS)
|
||||
{
|
||||
Throw.Command.Win32Exception(error, "Failed to query registry key.");
|
||||
}
|
||||
|
||||
return DateTime.FromFileTime(lastWriteTime.ToTicks());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net5.0'">
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.7.0" />
|
||||
<PackageReference Include="System.Diagnostics.EventLog" Version="4.7.0" />
|
||||
<PackageReference Include="System.Security.AccessControl" Version="4.7.0" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -20,6 +20,7 @@ using log4net.Appender;
|
|||
using log4net.Config;
|
||||
using log4net.Core;
|
||||
using log4net.Layout;
|
||||
using Microsoft.Win32;
|
||||
using WinSW.Logging;
|
||||
using WinSW.Native;
|
||||
using WinSW.Util;
|
||||
|
@ -88,7 +89,7 @@ namespace WinSW
|
|||
XmlServiceConfig config = null!;
|
||||
try
|
||||
{
|
||||
config = CreateConfig(pathToConfig);
|
||||
config = LoadConfig(pathToConfig);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
|
@ -98,6 +99,9 @@ namespace WinSW
|
|||
InitLoggers(config, enableConsoleLogging: false);
|
||||
|
||||
Log.Debug("Starting WinSW in service mode.");
|
||||
|
||||
AutoRefresh(config);
|
||||
|
||||
ServiceBase.Run(new WrapperService(config));
|
||||
}),
|
||||
};
|
||||
|
@ -379,7 +383,7 @@ namespace WinSW
|
|||
|
||||
void Install(string? pathToConfig, bool noElevate, string? username, string? password)
|
||||
{
|
||||
XmlServiceConfig config = CreateConfig(pathToConfig);
|
||||
XmlServiceConfig config = LoadConfig(pathToConfig);
|
||||
InitLoggers(config, enableConsoleLogging: true);
|
||||
|
||||
if (!elevated)
|
||||
|
@ -504,7 +508,7 @@ namespace WinSW
|
|||
|
||||
void Uninstall(string? pathToConfig, bool noElevate)
|
||||
{
|
||||
XmlServiceConfig config = CreateConfig(pathToConfig);
|
||||
XmlServiceConfig config = LoadConfig(pathToConfig);
|
||||
InitLoggers(config, enableConsoleLogging: true);
|
||||
|
||||
if (!elevated)
|
||||
|
@ -554,7 +558,7 @@ namespace WinSW
|
|||
|
||||
void Start(string? pathToConfig, bool noElevate, bool noWait, CancellationToken ct)
|
||||
{
|
||||
XmlServiceConfig config = CreateConfig(pathToConfig);
|
||||
XmlServiceConfig config = LoadConfig(pathToConfig);
|
||||
InitLoggers(config, enableConsoleLogging: true);
|
||||
|
||||
if (!elevated)
|
||||
|
@ -563,6 +567,8 @@ namespace WinSW
|
|||
return;
|
||||
}
|
||||
|
||||
AutoRefresh(config);
|
||||
|
||||
using var svc = new ServiceController(config.Name);
|
||||
|
||||
try
|
||||
|
@ -598,7 +604,7 @@ namespace WinSW
|
|||
|
||||
void Stop(string? pathToConfig, bool noElevate, bool noWait, bool force, CancellationToken ct)
|
||||
{
|
||||
XmlServiceConfig config = CreateConfig(pathToConfig);
|
||||
XmlServiceConfig config = LoadConfig(pathToConfig);
|
||||
InitLoggers(config, enableConsoleLogging: true);
|
||||
|
||||
if (!elevated)
|
||||
|
@ -607,6 +613,8 @@ namespace WinSW
|
|||
return;
|
||||
}
|
||||
|
||||
AutoRefresh(config);
|
||||
|
||||
using var svc = new ServiceController(config.Name);
|
||||
|
||||
try
|
||||
|
@ -650,7 +658,7 @@ namespace WinSW
|
|||
|
||||
void Restart(string? pathToConfig, bool noElevate, bool force, CancellationToken ct)
|
||||
{
|
||||
XmlServiceConfig config = CreateConfig(pathToConfig);
|
||||
XmlServiceConfig config = LoadConfig(pathToConfig);
|
||||
InitLoggers(config, enableConsoleLogging: true);
|
||||
|
||||
if (!elevated)
|
||||
|
@ -659,6 +667,8 @@ namespace WinSW
|
|||
return;
|
||||
}
|
||||
|
||||
AutoRefresh(config);
|
||||
|
||||
using var svc = new ServiceController(config.Name);
|
||||
|
||||
List<ServiceController>? startedDependentServices = null;
|
||||
|
@ -726,7 +736,7 @@ namespace WinSW
|
|||
|
||||
void RestartSelf(string? pathToConfig)
|
||||
{
|
||||
XmlServiceConfig config = CreateConfig(pathToConfig);
|
||||
XmlServiceConfig config = LoadConfig(pathToConfig);
|
||||
InitLoggers(config, enableConsoleLogging: true);
|
||||
|
||||
if (!elevated)
|
||||
|
@ -734,6 +744,8 @@ namespace WinSW
|
|||
Throw.Command.Win32Exception(Errors.ERROR_ACCESS_DENIED);
|
||||
}
|
||||
|
||||
AutoRefresh(config);
|
||||
|
||||
// run restart from another process group. see README.md for why this is useful.
|
||||
if (!ProcessApis.CreateProcess(
|
||||
null,
|
||||
|
@ -753,7 +765,7 @@ namespace WinSW
|
|||
|
||||
static int Status(string? pathToConfig)
|
||||
{
|
||||
XmlServiceConfig config = CreateConfig(pathToConfig);
|
||||
XmlServiceConfig config = LoadConfig(pathToConfig);
|
||||
InitLoggers(config, enableConsoleLogging: true);
|
||||
|
||||
using var svc = new ServiceController(config.Name);
|
||||
|
@ -786,7 +798,7 @@ namespace WinSW
|
|||
|
||||
void Test(string? pathToConfig, bool noElevate, bool noBreak)
|
||||
{
|
||||
XmlServiceConfig config = CreateConfig(pathToConfig);
|
||||
XmlServiceConfig config = LoadConfig(pathToConfig);
|
||||
InitLoggers(config, enableConsoleLogging: true);
|
||||
|
||||
if (!elevated)
|
||||
|
@ -795,6 +807,8 @@ namespace WinSW
|
|||
return;
|
||||
}
|
||||
|
||||
AutoRefresh(config);
|
||||
|
||||
using WrapperService wsvc = new WrapperService(config);
|
||||
wsvc.RaiseOnStart(args);
|
||||
try
|
||||
|
@ -829,7 +843,7 @@ namespace WinSW
|
|||
|
||||
void Refresh(string? pathToConfig, bool noElevate)
|
||||
{
|
||||
XmlServiceConfig config = CreateConfig(pathToConfig);
|
||||
XmlServiceConfig config = LoadConfig(pathToConfig);
|
||||
InitLoggers(config, enableConsoleLogging: true);
|
||||
|
||||
if (!elevated)
|
||||
|
@ -838,51 +852,12 @@ namespace WinSW
|
|||
return;
|
||||
}
|
||||
|
||||
using ServiceManager scm = ServiceManager.Open(ServiceManagerAccess.Connect);
|
||||
try
|
||||
{
|
||||
using Service sc = scm.OpenService(config.Name);
|
||||
|
||||
sc.ChangeConfig(config.DisplayName, config.StartMode, config.ServiceDependencies);
|
||||
|
||||
sc.SetDescription(config.Description);
|
||||
|
||||
SC_ACTION[] actions = config.FailureActions;
|
||||
if (actions.Length > 0)
|
||||
{
|
||||
sc.SetFailureActions(config.ResetFailureAfter, actions);
|
||||
}
|
||||
|
||||
bool isDelayedAutoStart = config.StartMode == ServiceStartMode.Automatic && config.DelayedAutoStart;
|
||||
if (isDelayedAutoStart)
|
||||
{
|
||||
sc.SetDelayedAutoStart(true);
|
||||
}
|
||||
|
||||
if (config.PreshutdownTimeout is TimeSpan preshutdownTimeout)
|
||||
{
|
||||
sc.SetPreshutdownTimeout(preshutdownTimeout);
|
||||
}
|
||||
|
||||
string? securityDescriptor = config.SecurityDescriptor;
|
||||
if (securityDescriptor != null)
|
||||
{
|
||||
// throws ArgumentException
|
||||
sc.SetSecurityDescriptor(new RawSecurityDescriptor(securityDescriptor));
|
||||
}
|
||||
}
|
||||
catch (CommandException e)
|
||||
when (e.InnerException is Win32Exception inner && inner.NativeErrorCode == Errors.ERROR_SERVICE_DOES_NOT_EXIST)
|
||||
{
|
||||
Throw.Command.Exception(inner);
|
||||
}
|
||||
|
||||
Log.Info($"Service '{config.Format()}' was refreshed successfully.");
|
||||
DoRefresh(config);
|
||||
}
|
||||
|
||||
static void DevPs(string? pathToConfig)
|
||||
{
|
||||
XmlServiceConfig config = CreateConfig(pathToConfig);
|
||||
XmlServiceConfig config = LoadConfig(pathToConfig);
|
||||
|
||||
using ServiceManager scm = ServiceManager.Open(ServiceManagerAccess.Connect);
|
||||
using Service sc = scm.OpenService(config.Name, ServiceAccess.QueryStatus);
|
||||
|
@ -928,7 +903,7 @@ namespace WinSW
|
|||
|
||||
void DevKill(string? pathToConfig, bool noElevate)
|
||||
{
|
||||
XmlServiceConfig config = CreateConfig(pathToConfig);
|
||||
XmlServiceConfig config = LoadConfig(pathToConfig);
|
||||
|
||||
if (!elevated)
|
||||
{
|
||||
|
@ -1018,10 +993,82 @@ namespace WinSW
|
|||
Environment.Exit(e.ErrorCode);
|
||||
}
|
||||
}
|
||||
|
||||
static void AutoRefresh(XmlServiceConfig config)
|
||||
{
|
||||
if (!config.AutoRefresh)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DateTime fileLastWriteTime = File.GetLastWriteTime(config.FullPath);
|
||||
|
||||
using RegistryKey? registryKey = Registry.LocalMachine
|
||||
.OpenSubKey("SYSTEM")
|
||||
.OpenSubKey("CurrentControlSet")
|
||||
.OpenSubKey("Services")
|
||||
.OpenSubKey(config.Name);
|
||||
|
||||
if (registryKey is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DateTime registryLastWriteTime = registryKey.GetLastWriteTime();
|
||||
|
||||
if (fileLastWriteTime > registryLastWriteTime)
|
||||
{
|
||||
DoRefresh(config);
|
||||
}
|
||||
}
|
||||
|
||||
static void DoRefresh(XmlServiceConfig config)
|
||||
{
|
||||
using ServiceManager scm = ServiceManager.Open(ServiceManagerAccess.Connect);
|
||||
try
|
||||
{
|
||||
using Service sc = scm.OpenService(config.Name);
|
||||
|
||||
sc.ChangeConfig(config.DisplayName, config.StartMode, config.ServiceDependencies);
|
||||
|
||||
sc.SetDescription(config.Description);
|
||||
|
||||
SC_ACTION[] actions = config.FailureActions;
|
||||
if (actions.Length > 0)
|
||||
{
|
||||
sc.SetFailureActions(config.ResetFailureAfter, actions);
|
||||
}
|
||||
|
||||
bool isDelayedAutoStart = config.StartMode == ServiceStartMode.Automatic && config.DelayedAutoStart;
|
||||
if (isDelayedAutoStart)
|
||||
{
|
||||
sc.SetDelayedAutoStart(true);
|
||||
}
|
||||
|
||||
if (config.PreshutdownTimeout is TimeSpan preshutdownTimeout)
|
||||
{
|
||||
sc.SetPreshutdownTimeout(preshutdownTimeout);
|
||||
}
|
||||
|
||||
string? securityDescriptor = config.SecurityDescriptor;
|
||||
if (securityDescriptor != null)
|
||||
{
|
||||
// throws ArgumentException
|
||||
sc.SetSecurityDescriptor(new RawSecurityDescriptor(securityDescriptor));
|
||||
}
|
||||
}
|
||||
catch (CommandException e)
|
||||
when (e.InnerException is Win32Exception inner && inner.NativeErrorCode == Errors.ERROR_SERVICE_DOES_NOT_EXIST)
|
||||
{
|
||||
Throw.Command.Exception(inner);
|
||||
}
|
||||
|
||||
Log.Info($"Service '{config.Format()}' was refreshed successfully.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <exception cref="FileNotFoundException" />
|
||||
private static XmlServiceConfig CreateConfig(string? path)
|
||||
private static XmlServiceConfig LoadConfig(string? path)
|
||||
{
|
||||
if (TestConfig != null)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue