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 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");
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
<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>
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue