Auto refresh

pull/673/head
NextTurn 2020-08-15 00:00:00 +08:00 committed by Next Turn
parent 144cff7f19
commit 2fc786a26a
5 changed files with 156 additions and 53 deletions

View File

@ -677,6 +677,8 @@ namespace WinSW
public string? SecurityDescriptor => this.SingleElement("securityDescriptor", true); public string? SecurityDescriptor => this.SingleElement("securityDescriptor", true);
public bool AutoRefresh => this.SingleBoolElement("autoRefresh", true);
private Dictionary<string, string> LoadEnvironmentVariables() private Dictionary<string, string> LoadEnvironmentVariables()
{ {
XmlNodeList nodeList = this.dom.SelectNodes("//env"); XmlNodeList nodeList = this.dom.SelectNodes("//env");

View File

@ -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;
}
}
}

View File

@ -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());
}
}
}

View File

@ -16,6 +16,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net5.0'"> <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.Diagnostics.EventLog" Version="4.7.0" />
<PackageReference Include="System.Security.AccessControl" Version="4.7.0" /> <PackageReference Include="System.Security.AccessControl" Version="4.7.0" />
</ItemGroup> </ItemGroup>

View File

@ -20,6 +20,7 @@ using log4net.Appender;
using log4net.Config; using log4net.Config;
using log4net.Core; using log4net.Core;
using log4net.Layout; using log4net.Layout;
using Microsoft.Win32;
using WinSW.Logging; using WinSW.Logging;
using WinSW.Native; using WinSW.Native;
using WinSW.Util; using WinSW.Util;
@ -88,7 +89,7 @@ namespace WinSW
XmlServiceConfig config = null!; XmlServiceConfig config = null!;
try try
{ {
config = CreateConfig(pathToConfig); config = LoadConfig(pathToConfig);
} }
catch (FileNotFoundException) catch (FileNotFoundException)
{ {
@ -98,6 +99,9 @@ namespace WinSW
InitLoggers(config, enableConsoleLogging: false); InitLoggers(config, enableConsoleLogging: false);
Log.Debug("Starting WinSW in service mode."); Log.Debug("Starting WinSW in service mode.");
AutoRefresh(config);
ServiceBase.Run(new WrapperService(config)); ServiceBase.Run(new WrapperService(config));
}), }),
}; };
@ -379,7 +383,7 @@ namespace WinSW
void Install(string? pathToConfig, bool noElevate, string? username, string? password) void Install(string? pathToConfig, bool noElevate, string? username, string? password)
{ {
XmlServiceConfig config = CreateConfig(pathToConfig); XmlServiceConfig config = LoadConfig(pathToConfig);
InitLoggers(config, enableConsoleLogging: true); InitLoggers(config, enableConsoleLogging: true);
if (!elevated) if (!elevated)
@ -504,7 +508,7 @@ namespace WinSW
void Uninstall(string? pathToConfig, bool noElevate) void Uninstall(string? pathToConfig, bool noElevate)
{ {
XmlServiceConfig config = CreateConfig(pathToConfig); XmlServiceConfig config = LoadConfig(pathToConfig);
InitLoggers(config, enableConsoleLogging: true); InitLoggers(config, enableConsoleLogging: true);
if (!elevated) if (!elevated)
@ -554,7 +558,7 @@ namespace WinSW
void Start(string? pathToConfig, bool noElevate, bool noWait, CancellationToken ct) void Start(string? pathToConfig, bool noElevate, bool noWait, CancellationToken ct)
{ {
XmlServiceConfig config = CreateConfig(pathToConfig); XmlServiceConfig config = LoadConfig(pathToConfig);
InitLoggers(config, enableConsoleLogging: true); InitLoggers(config, enableConsoleLogging: true);
if (!elevated) if (!elevated)
@ -563,6 +567,8 @@ namespace WinSW
return; return;
} }
AutoRefresh(config);
using var svc = new ServiceController(config.Name); using var svc = new ServiceController(config.Name);
try try
@ -598,7 +604,7 @@ namespace WinSW
void Stop(string? pathToConfig, bool noElevate, bool noWait, bool force, CancellationToken ct) void Stop(string? pathToConfig, bool noElevate, bool noWait, bool force, CancellationToken ct)
{ {
XmlServiceConfig config = CreateConfig(pathToConfig); XmlServiceConfig config = LoadConfig(pathToConfig);
InitLoggers(config, enableConsoleLogging: true); InitLoggers(config, enableConsoleLogging: true);
if (!elevated) if (!elevated)
@ -607,6 +613,8 @@ namespace WinSW
return; return;
} }
AutoRefresh(config);
using var svc = new ServiceController(config.Name); using var svc = new ServiceController(config.Name);
try try
@ -650,7 +658,7 @@ namespace WinSW
void Restart(string? pathToConfig, bool noElevate, bool force, CancellationToken ct) void Restart(string? pathToConfig, bool noElevate, bool force, CancellationToken ct)
{ {
XmlServiceConfig config = CreateConfig(pathToConfig); XmlServiceConfig config = LoadConfig(pathToConfig);
InitLoggers(config, enableConsoleLogging: true); InitLoggers(config, enableConsoleLogging: true);
if (!elevated) if (!elevated)
@ -659,6 +667,8 @@ namespace WinSW
return; return;
} }
AutoRefresh(config);
using var svc = new ServiceController(config.Name); using var svc = new ServiceController(config.Name);
List<ServiceController>? startedDependentServices = null; List<ServiceController>? startedDependentServices = null;
@ -726,7 +736,7 @@ namespace WinSW
void RestartSelf(string? pathToConfig) void RestartSelf(string? pathToConfig)
{ {
XmlServiceConfig config = CreateConfig(pathToConfig); XmlServiceConfig config = LoadConfig(pathToConfig);
InitLoggers(config, enableConsoleLogging: true); InitLoggers(config, enableConsoleLogging: true);
if (!elevated) if (!elevated)
@ -734,6 +744,8 @@ namespace WinSW
Throw.Command.Win32Exception(Errors.ERROR_ACCESS_DENIED); Throw.Command.Win32Exception(Errors.ERROR_ACCESS_DENIED);
} }
AutoRefresh(config);
// 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.
if (!ProcessApis.CreateProcess( if (!ProcessApis.CreateProcess(
null, null,
@ -753,7 +765,7 @@ namespace WinSW
static int Status(string? pathToConfig) static int Status(string? pathToConfig)
{ {
XmlServiceConfig config = CreateConfig(pathToConfig); XmlServiceConfig config = LoadConfig(pathToConfig);
InitLoggers(config, enableConsoleLogging: true); InitLoggers(config, enableConsoleLogging: true);
using var svc = new ServiceController(config.Name); using var svc = new ServiceController(config.Name);
@ -786,7 +798,7 @@ namespace WinSW
void Test(string? pathToConfig, bool noElevate, bool noBreak) void Test(string? pathToConfig, bool noElevate, bool noBreak)
{ {
XmlServiceConfig config = CreateConfig(pathToConfig); XmlServiceConfig config = LoadConfig(pathToConfig);
InitLoggers(config, enableConsoleLogging: true); InitLoggers(config, enableConsoleLogging: true);
if (!elevated) if (!elevated)
@ -795,6 +807,8 @@ namespace WinSW
return; return;
} }
AutoRefresh(config);
using WrapperService wsvc = new WrapperService(config); using WrapperService wsvc = new WrapperService(config);
wsvc.RaiseOnStart(args); wsvc.RaiseOnStart(args);
try try
@ -829,7 +843,7 @@ namespace WinSW
void Refresh(string? pathToConfig, bool noElevate) void Refresh(string? pathToConfig, bool noElevate)
{ {
XmlServiceConfig config = CreateConfig(pathToConfig); XmlServiceConfig config = LoadConfig(pathToConfig);
InitLoggers(config, enableConsoleLogging: true); InitLoggers(config, enableConsoleLogging: true);
if (!elevated) if (!elevated)
@ -838,51 +852,12 @@ namespace WinSW
return; return;
} }
using ServiceManager scm = ServiceManager.Open(ServiceManagerAccess.Connect); DoRefresh(config);
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.");
} }
static void DevPs(string? pathToConfig) static void DevPs(string? pathToConfig)
{ {
XmlServiceConfig config = CreateConfig(pathToConfig); XmlServiceConfig config = LoadConfig(pathToConfig);
using ServiceManager scm = ServiceManager.Open(ServiceManagerAccess.Connect); using ServiceManager scm = ServiceManager.Open(ServiceManagerAccess.Connect);
using Service sc = scm.OpenService(config.Name, ServiceAccess.QueryStatus); using Service sc = scm.OpenService(config.Name, ServiceAccess.QueryStatus);
@ -928,7 +903,7 @@ namespace WinSW
void DevKill(string? pathToConfig, bool noElevate) void DevKill(string? pathToConfig, bool noElevate)
{ {
XmlServiceConfig config = CreateConfig(pathToConfig); XmlServiceConfig config = LoadConfig(pathToConfig);
if (!elevated) if (!elevated)
{ {
@ -1018,10 +993,82 @@ namespace WinSW
Environment.Exit(e.ErrorCode); 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" /> /// <exception cref="FileNotFoundException" />
private static XmlServiceConfig CreateConfig(string? path) private static XmlServiceConfig LoadConfig(string? path)
{ {
if (TestConfig != null) if (TestConfig != null)
{ {