NextTurn 5 år sedan
förälder
incheckning
2fc786a26a

+ 2 - 0
src/WinSW.Core/Configuration/XmlServiceConfig.cs

@@ -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");

+ 31 - 0
src/WinSW.Core/Native/RegistryApis.cs

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

+ 22 - 0
src/WinSW.Core/Util/RegistryKeyExtensions.cs

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

+ 1 - 0
src/WinSW.Core/WinSW.Core.csproj

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

+ 100 - 53
src/WinSW/Program.cs

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