From e96fc2b4f33a36eb9a7ca0c211a02f11032e6ffa Mon Sep 17 00:00:00 2001 From: NextTurn <45985406+NextTurn@users.noreply.github.com> Date: Mon, 20 Jul 2020 00:00:00 +0800 Subject: [PATCH] Add `prestart`/`poststart`/`prestop`/`poststop` settings --- src/WinSW.Core/Configuration/SettingNames.cs | 13 ++ src/WinSW.Core/ServiceDescriptor.cs | 29 +++++ src/WinSW.Tests/ServiceDescriptorTests.cs | 44 +++++++ src/WinSW/WinSW.csproj | 2 +- src/WinSW/WrapperService.cs | 129 ++++++++++++++----- 5 files changed, 181 insertions(+), 36 deletions(-) create mode 100644 src/WinSW.Core/Configuration/SettingNames.cs diff --git a/src/WinSW.Core/Configuration/SettingNames.cs b/src/WinSW.Core/Configuration/SettingNames.cs new file mode 100644 index 0000000..21db14f --- /dev/null +++ b/src/WinSW.Core/Configuration/SettingNames.cs @@ -0,0 +1,13 @@ +namespace WinSW.Configuration +{ + internal static class SettingNames + { + internal const string Arguments = "arguments"; + internal const string Executable = "executable"; + internal const string Poststart = "poststart"; + internal const string Poststop = "poststop"; + internal const string Prestart = "prestart"; + internal const string Prestop = "prestop"; + internal const string Service = "service"; + } +} diff --git a/src/WinSW.Core/ServiceDescriptor.cs b/src/WinSW.Core/ServiceDescriptor.cs index 2e484ef..a6a2ca6 100644 --- a/src/WinSW.Core/ServiceDescriptor.cs +++ b/src/WinSW.Core/ServiceDescriptor.cs @@ -8,6 +8,7 @@ using System.Xml; using WinSW.Configuration; using WinSW.Native; using WinSW.Util; +using Names = WinSW.Configuration.SettingNames; namespace WinSW { @@ -250,6 +251,22 @@ namespace WinSW } } + public string? PrestartExecutable => this.GetExecutable(Names.Prestart); + + public string? PrestartArguments => this.GetArguments(Names.Prestart); + + public string? PoststartExecutable => this.GetExecutable(Names.Poststart); + + public string? PoststartArguments => this.GetArguments(Names.Poststart); + + public string? PrestopExecutable => this.GetExecutable(Names.Prestop); + + public string? PrestopArguments => this.GetArguments(Names.Prestop); + + public string? PoststopExecutable => this.GetExecutable(Names.Poststop); + + public string? PoststopArguments => this.GetArguments(Names.Poststop); + public string WorkingDirectory { get @@ -727,5 +744,17 @@ namespace WinSW return environment; } + + private string? GetExecutable(string name) + { + string? text = this.dom.SelectSingleNode(Names.Service)?.SelectSingleNode(name)?.SelectSingleNode(Names.Executable)?.InnerText; + return text is null ? null : Environment.ExpandEnvironmentVariables(text); + } + + private string? GetArguments(string name) + { + string? text = this.dom.SelectSingleNode(Names.Service)?.SelectSingleNode(name)?.SelectSingleNode(Names.Arguments)?.InnerText; + return text is null ? null : Environment.ExpandEnvironmentVariables(text); + } } } diff --git a/src/WinSW.Tests/ServiceDescriptorTests.cs b/src/WinSW.Tests/ServiceDescriptorTests.cs index 503bc7a..bd55051 100644 --- a/src/WinSW.Tests/ServiceDescriptorTests.cs +++ b/src/WinSW.Tests/ServiceDescriptorTests.cs @@ -425,5 +425,49 @@ $@" var sd = bldr.ToServiceDescriptor(); Assert.Equal(enabled, sd.DelayedAutoStart); } + + [Fact] + public void Additional_Executable_And_Arguments() + { + const string prestartExecutable = "1"; + const string prestartArguments = "2"; + const string poststartExecutable = "3"; + const string poststartArguments = "4"; + const string prestopExecutable = "5"; + const string prestopArguments = "6"; + const string poststopExecutable = "7"; + const string poststopArguments = "8"; + + string seedXml = +$@" + + {prestartExecutable} + {prestartArguments} + + + {poststartExecutable} + {poststartArguments} + + + {prestopExecutable} + {prestopArguments} + + + {poststopExecutable} + {poststopArguments} + +"; + + ServiceDescriptor descriptor = ServiceDescriptor.FromXml(seedXml); + + Assert.Equal(prestartExecutable, descriptor.PrestartExecutable); + Assert.Equal(prestartArguments, descriptor.PrestartArguments); + Assert.Equal(poststartExecutable, descriptor.PoststartExecutable); + Assert.Equal(poststartArguments, descriptor.PoststartArguments); + Assert.Equal(prestopExecutable, descriptor.PrestopExecutable); + Assert.Equal(prestopArguments, descriptor.PrestopArguments); + Assert.Equal(poststopExecutable, descriptor.PoststopExecutable); + Assert.Equal(poststopArguments, descriptor.PoststopArguments); + } } } diff --git a/src/WinSW/WinSW.csproj b/src/WinSW/WinSW.csproj index 1ca11ed..96391ad 100644 --- a/src/WinSW/WinSW.csproj +++ b/src/WinSW/WinSW.csproj @@ -3,7 +3,7 @@ Exe net461;netcoreapp3.1 - latest + preview enable true true diff --git a/src/WinSW/WrapperService.cs b/src/WinSW/WrapperService.cs index 6a3bc9d..dfdb138 100644 --- a/src/WinSW/WrapperService.cs +++ b/src/WinSW/WrapperService.cs @@ -225,6 +225,21 @@ namespace WinSW throw new AggregateException(exceptions); } + try + { + string? prestartExecutable = this.descriptor.PrestartExecutable; + if (prestartExecutable != null) + { + using Process process = this.StartProcess(prestartExecutable, this.descriptor.PrestartArguments); + this.WaitForProcessToExit(process); + Log.Info($"Pre-start process '{GetDisplayName(process)}' exited with code {process.ExitCode}."); + } + } + catch (Exception e) + { + Log.Error(e); + } + string? startArguments = this.descriptor.StartArguments; if (startArguments is null) @@ -253,6 +268,26 @@ namespace WinSW this.ExtensionManager.FireOnProcessStarted(this.process); this.process.StandardInput.Close(); // nothing for you to read! + + try + { + string? poststartExecutable = this.descriptor.PoststartExecutable; + if (poststartExecutable != null) + { + using Process process = this.StartProcess(poststartExecutable, this.descriptor.PoststartArguments); + process.Exited += (sender, _) => + { + Process process = (Process)sender!; + Log.Info($"Post-start process '{GetDisplayName(process)}' exited with code {process.ExitCode}."); + }; + + process.EnableRaisingEvents = true; + } + } + catch (Exception e) + { + Log.Error(e); + } } protected override void OnShutdown() @@ -293,6 +328,21 @@ namespace WinSW /// private void StopIt() { + try + { + string? prestopExecutable = this.descriptor.PrestopExecutable; + if (prestopExecutable != null) + { + using Process process = this.StartProcess(prestopExecutable, this.descriptor.PrestopArguments); + this.WaitForProcessToExit(process); + Log.Info($"Pre-stop process '{GetDisplayName(process)}' exited with code {process.ExitCode}."); + } + } + catch (Exception e) + { + Log.Error(e); + } + string? stopArguments = this.descriptor.StopArguments; this.LogEvent("Stopping " + this.descriptor.Id); Log.Info("Stopping " + this.descriptor.Id); @@ -307,7 +357,7 @@ namespace WinSW } else { - this.SignalShutdownPending(); + this.SignalPending(); stopArguments += " " + this.descriptor.Arguments; @@ -324,6 +374,21 @@ namespace WinSW this.WaitForProcessToExit(stopProcess); } + try + { + string? poststopExecutable = this.descriptor.PoststopExecutable; + if (poststopExecutable != null) + { + using Process process = this.StartProcess(poststopExecutable, this.descriptor.PoststopArguments); + this.WaitForProcessToExit(process); + Log.Info($"Post-stop process '{GetDisplayName(process)}' exited with code {process.ExitCode}."); + } + } + catch (Exception e) + { + Log.Error(e); + } + // Stop extensions this.ExtensionManager.FireBeforeWrapperStopped(); @@ -335,48 +400,23 @@ namespace WinSW Log.Info("Finished " + this.descriptor.Id); } - private void WaitForProcessToExit(Process processoWait) + private void WaitForProcessToExit(Process process) { - this.SignalShutdownPending(); + this.SignalPending(); - int effectiveProcessWaitSleepTime; - if (this.descriptor.SleepTime.TotalMilliseconds > int.MaxValue) + int processWaitHint = (int)Math.Min(this.descriptor.SleepTime.TotalMilliseconds, int.MaxValue); + + while (!process.WaitForExit(processWaitHint)) { - Log.Warn("The requested sleep time " + this.descriptor.SleepTime.TotalMilliseconds + "is greater that the max value " + - int.MaxValue + ". The value will be truncated"); - effectiveProcessWaitSleepTime = int.MaxValue; + this.SignalPending(); } - else - { - effectiveProcessWaitSleepTime = (int)this.descriptor.SleepTime.TotalMilliseconds; - } - - // WriteEvent("WaitForProcessToExit [start]"); - - while (!processoWait.WaitForExit(effectiveProcessWaitSleepTime)) - { - this.SignalShutdownPending(); - // WriteEvent("WaitForProcessToExit [repeat]"); - } - - // WriteEvent("WaitForProcessToExit [finished]"); } - private void SignalShutdownPending() + private void SignalPending() { - int effectiveWaitHint; - if (this.descriptor.WaitHint.TotalMilliseconds > int.MaxValue) - { - Log.Warn("The requested WaitHint value (" + this.descriptor.WaitHint.TotalMilliseconds + " ms) is greater that the max value " + - int.MaxValue + ". The value will be truncated"); - effectiveWaitHint = int.MaxValue; - } - else - { - effectiveWaitHint = (int)this.descriptor.WaitHint.TotalMilliseconds; - } + int serviceWaitHint = (int)Math.Min(this.descriptor.WaitHint.TotalMilliseconds, int.MaxValue); - this.RequestAdditionalTime(effectiveWaitHint); + this.RequestAdditionalTime(serviceWaitHint); } private void SignalShutdownComplete() @@ -428,5 +468,24 @@ namespace WinSW redirectStdin: redirectStdin, hideWindow: this.descriptor.HideWindow); } + + private Process StartProcess(string executable, string? arguments) + { + var info = new ProcessStartInfo(executable, arguments) + { + UseShellExecute = false, + RedirectStandardInput = true, + WorkingDirectory = this.descriptor.WorkingDirectory, + }; + + Process process = Process.Start(info); + process.StandardInput.Close(); + return process; + } + + private static string GetDisplayName(Process process) + { + return $"{process.ProcessName} ({process.Id})"; + } } }