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 bool AutoRefresh => this.SingleBoolElement("autoRefresh", true);
private Dictionary<string, string> LoadEnvironmentVariables()
{
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 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>

View File

@ -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)
{