mirror of https://github.com/winsw/winsw
Decouple CLI and service
parent
80f109a6a5
commit
1b8cbccd8a
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,694 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.IO;
|
||||||
|
#if NETCOREAPP
|
||||||
|
using System.Reflection;
|
||||||
|
#endif
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Security.AccessControl;
|
||||||
|
using System.Security.Principal;
|
||||||
|
using System.ServiceProcess;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using log4net;
|
||||||
|
using log4net.Appender;
|
||||||
|
using log4net.Config;
|
||||||
|
using log4net.Core;
|
||||||
|
using log4net.Layout;
|
||||||
|
using winsw.Logging;
|
||||||
|
using winsw.Native;
|
||||||
|
using winsw.Util;
|
||||||
|
using WMI;
|
||||||
|
using ServiceType = WMI.ServiceType;
|
||||||
|
|
||||||
|
namespace winsw
|
||||||
|
{
|
||||||
|
public static class Program
|
||||||
|
{
|
||||||
|
private static readonly ILog Log = LogManager.GetLogger(typeof(Program));
|
||||||
|
|
||||||
|
public static int Main(string[] args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Run(args);
|
||||||
|
Log.Debug("Completed. Exit code is 0");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
catch (InvalidDataException e)
|
||||||
|
{
|
||||||
|
string message = "The configuration file cound not be loaded. " + e.Message;
|
||||||
|
Log.Fatal(message, e);
|
||||||
|
Console.Error.WriteLine(message);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
catch (WmiException e)
|
||||||
|
{
|
||||||
|
Log.Fatal("WMI Operation failure: " + e.ErrorCode, e);
|
||||||
|
Console.Error.WriteLine(e);
|
||||||
|
return (int)e.ErrorCode;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Fatal("Unhandled exception", e);
|
||||||
|
Console.Error.WriteLine(e);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Run(string[] argsArray, ServiceDescriptor? descriptor = null)
|
||||||
|
{
|
||||||
|
bool inConsoleMode = argsArray.Length > 0;
|
||||||
|
|
||||||
|
// If descriptor is not specified, initialize the new one (and load configs from there)
|
||||||
|
descriptor ??= new ServiceDescriptor();
|
||||||
|
|
||||||
|
// Configure the wrapper-internal logging.
|
||||||
|
// STDOUT and STDERR of the child process will be handled independently.
|
||||||
|
InitLoggers(descriptor, inConsoleMode);
|
||||||
|
|
||||||
|
if (!inConsoleMode)
|
||||||
|
{
|
||||||
|
Log.Debug("Starting WinSW in service mode");
|
||||||
|
ServiceBase.Run(new WrapperService(descriptor));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Debug("Starting WinSW in console mode");
|
||||||
|
|
||||||
|
if (argsArray.Length == 0)
|
||||||
|
{
|
||||||
|
PrintHelp();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get service info for the future use
|
||||||
|
Win32Services svcs = new WmiRoot().GetCollection<Win32Services>();
|
||||||
|
Win32Service? svc = svcs.Select(descriptor.Id);
|
||||||
|
|
||||||
|
var args = new List<string>(Array.AsReadOnly(argsArray));
|
||||||
|
if (args[0] == "/redirect")
|
||||||
|
{
|
||||||
|
var f = new FileStream(args[1], FileMode.Create);
|
||||||
|
var w = new StreamWriter(f) { AutoFlush = true };
|
||||||
|
Console.SetOut(w);
|
||||||
|
Console.SetError(w);
|
||||||
|
|
||||||
|
var handle = f.SafeFileHandle;
|
||||||
|
_ = Kernel32.SetStdHandle(-11, handle); // set stdout
|
||||||
|
_ = Kernel32.SetStdHandle(-12, handle); // set stder
|
||||||
|
|
||||||
|
args = args.GetRange(2, args.Count - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool elevated;
|
||||||
|
if (args[0] == "/elevated")
|
||||||
|
{
|
||||||
|
elevated = true;
|
||||||
|
|
||||||
|
_ = ConsoleApis.FreeConsole();
|
||||||
|
_ = ConsoleApis.AttachConsole(ConsoleApis.ATTACH_PARENT_PROCESS);
|
||||||
|
|
||||||
|
args = args.GetRange(1, args.Count - 1);
|
||||||
|
}
|
||||||
|
else if (Environment.OSVersion.Version.Major == 5)
|
||||||
|
{
|
||||||
|
// Windows XP
|
||||||
|
elevated = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
elevated = IsProcessElevated();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (args[0].ToLower())
|
||||||
|
{
|
||||||
|
case "install":
|
||||||
|
Install();
|
||||||
|
return;
|
||||||
|
|
||||||
|
case "uninstall":
|
||||||
|
Uninstall();
|
||||||
|
return;
|
||||||
|
|
||||||
|
case "start":
|
||||||
|
Start();
|
||||||
|
return;
|
||||||
|
|
||||||
|
case "stop":
|
||||||
|
Stop();
|
||||||
|
return;
|
||||||
|
|
||||||
|
case "stopwait":
|
||||||
|
StopWait();
|
||||||
|
return;
|
||||||
|
|
||||||
|
case "restart":
|
||||||
|
Restart();
|
||||||
|
return;
|
||||||
|
|
||||||
|
case "restart!":
|
||||||
|
RestartSelf();
|
||||||
|
return;
|
||||||
|
|
||||||
|
case "status":
|
||||||
|
Status();
|
||||||
|
return;
|
||||||
|
|
||||||
|
case "test":
|
||||||
|
Test();
|
||||||
|
return;
|
||||||
|
|
||||||
|
case "testwait":
|
||||||
|
TestWait();
|
||||||
|
return;
|
||||||
|
|
||||||
|
case "help":
|
||||||
|
case "--help":
|
||||||
|
case "-h":
|
||||||
|
case "-?":
|
||||||
|
case "/?":
|
||||||
|
PrintHelp();
|
||||||
|
return;
|
||||||
|
|
||||||
|
case "version":
|
||||||
|
PrintVersion();
|
||||||
|
return;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Console.WriteLine("Unknown command: " + args[0]);
|
||||||
|
PrintAvailableCommands();
|
||||||
|
throw new Exception("Unknown command: " + args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Install()
|
||||||
|
{
|
||||||
|
if (!elevated)
|
||||||
|
{
|
||||||
|
Elevate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Info("Installing the service with id '" + descriptor.Id + "'");
|
||||||
|
|
||||||
|
// Check if the service exists
|
||||||
|
if (svc != null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Service with id '" + descriptor.Id + "' already exists");
|
||||||
|
Console.WriteLine("To install the service, delete the existing one or change service Id in the configuration file");
|
||||||
|
throw new Exception("Installation failure: Service with id '" + descriptor.Id + "' already exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
string? username = null;
|
||||||
|
string? password = null;
|
||||||
|
bool allowServiceLogonRight = false;
|
||||||
|
if (args.Count > 1 && args[1] == "/p")
|
||||||
|
{
|
||||||
|
Console.Write("Username: ");
|
||||||
|
username = Console.ReadLine();
|
||||||
|
Console.Write("Password: ");
|
||||||
|
password = ReadPassword();
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.Write("Set Account rights to allow log on as a service (y/n)?: ");
|
||||||
|
var keypressed = Console.ReadKey();
|
||||||
|
Console.WriteLine();
|
||||||
|
if (keypressed.Key == ConsoleKey.Y)
|
||||||
|
{
|
||||||
|
allowServiceLogonRight = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (descriptor.HasServiceAccount())
|
||||||
|
{
|
||||||
|
username = descriptor.ServiceAccountUser;
|
||||||
|
password = descriptor.ServiceAccountPassword;
|
||||||
|
allowServiceLogonRight = descriptor.AllowServiceAcountLogonRight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allowServiceLogonRight)
|
||||||
|
{
|
||||||
|
Security.AddServiceLogonRight(descriptor.ServiceAccountDomain!, descriptor.ServiceAccountName!);
|
||||||
|
}
|
||||||
|
|
||||||
|
svcs.Create(
|
||||||
|
descriptor.Id,
|
||||||
|
descriptor.Caption,
|
||||||
|
"\"" + descriptor.ExecutablePath + "\"",
|
||||||
|
ServiceType.OwnProcess,
|
||||||
|
ErrorControl.UserNotified,
|
||||||
|
descriptor.StartMode.ToString(),
|
||||||
|
descriptor.Interactive,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
descriptor.ServiceDependencies);
|
||||||
|
|
||||||
|
using ServiceManager scm = ServiceManager.Open();
|
||||||
|
using Service sc = scm.OpenService(descriptor.Id);
|
||||||
|
|
||||||
|
sc.SetDescription(descriptor.Description);
|
||||||
|
|
||||||
|
SC_ACTION[] actions = descriptor.FailureActions;
|
||||||
|
if (actions.Length > 0)
|
||||||
|
{
|
||||||
|
sc.SetFailureActions(descriptor.ResetFailureAfter, actions);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isDelayedAutoStart = descriptor.StartMode == StartMode.Automatic && descriptor.DelayedAutoStart;
|
||||||
|
if (isDelayedAutoStart)
|
||||||
|
{
|
||||||
|
sc.SetDelayedAutoStart(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
string? securityDescriptor = descriptor.SecurityDescriptor;
|
||||||
|
if (securityDescriptor != null)
|
||||||
|
{
|
||||||
|
// throws ArgumentException
|
||||||
|
sc.SetSecurityDescriptor(new RawSecurityDescriptor(securityDescriptor));
|
||||||
|
}
|
||||||
|
|
||||||
|
string eventLogSource = descriptor.Id;
|
||||||
|
if (!EventLog.SourceExists(eventLogSource))
|
||||||
|
{
|
||||||
|
EventLog.CreateEventSource(eventLogSource, "Application");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Uninstall()
|
||||||
|
{
|
||||||
|
if (!elevated)
|
||||||
|
{
|
||||||
|
Elevate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Info("Uninstalling the service with id '" + descriptor.Id + "'");
|
||||||
|
if (svc is null)
|
||||||
|
{
|
||||||
|
Log.Warn("The service with id '" + descriptor.Id + "' does not exist. Nothing to uninstall");
|
||||||
|
return; // there's no such service, so consider it already uninstalled
|
||||||
|
}
|
||||||
|
|
||||||
|
if (svc.Started)
|
||||||
|
{
|
||||||
|
// We could fail the opeartion here, but it would be an incompatible change.
|
||||||
|
// So it is just a warning
|
||||||
|
Log.Warn("The service with id '" + descriptor.Id + "' is running. It may be impossible to uninstall it");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
svc.Delete();
|
||||||
|
}
|
||||||
|
catch (WmiException e)
|
||||||
|
{
|
||||||
|
if (e.ErrorCode == ReturnValue.ServiceMarkedForDeletion)
|
||||||
|
{
|
||||||
|
Log.Error("Failed to uninstall the service with id '" + descriptor.Id + "'"
|
||||||
|
+ ". It has been marked for deletion.");
|
||||||
|
|
||||||
|
// TODO: change the default behavior to Error?
|
||||||
|
return; // it's already uninstalled, so consider it a success
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Fatal("Failed to uninstall the service with id '" + descriptor.Id + "'. WMI Error code is '" + e.ErrorCode + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Start()
|
||||||
|
{
|
||||||
|
if (!elevated)
|
||||||
|
{
|
||||||
|
Elevate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Info("Starting the service with id '" + descriptor.Id + "'");
|
||||||
|
if (svc is null)
|
||||||
|
{
|
||||||
|
ThrowNoSuchService();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
svc.StartService();
|
||||||
|
}
|
||||||
|
catch (WmiException e)
|
||||||
|
{
|
||||||
|
if (e.ErrorCode == ReturnValue.ServiceAlreadyRunning)
|
||||||
|
{
|
||||||
|
Log.Info($"The service with ID '{descriptor.Id}' has already been started");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stop()
|
||||||
|
{
|
||||||
|
if (!elevated)
|
||||||
|
{
|
||||||
|
Elevate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Info("Stopping the service with id '" + descriptor.Id + "'");
|
||||||
|
if (svc is null)
|
||||||
|
{
|
||||||
|
ThrowNoSuchService();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
svc.StopService();
|
||||||
|
}
|
||||||
|
catch (WmiException e)
|
||||||
|
{
|
||||||
|
if (e.ErrorCode == ReturnValue.ServiceCannotAcceptControl)
|
||||||
|
{
|
||||||
|
Log.Info($"The service with ID '{descriptor.Id}' is not running");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StopWait()
|
||||||
|
{
|
||||||
|
if (!elevated)
|
||||||
|
{
|
||||||
|
Elevate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Info("Stopping the service with id '" + descriptor.Id + "'");
|
||||||
|
if (svc is null)
|
||||||
|
{
|
||||||
|
ThrowNoSuchService();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (svc.Started)
|
||||||
|
{
|
||||||
|
svc.StopService();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (svc != null && svc.Started)
|
||||||
|
{
|
||||||
|
Log.Info("Waiting the service to stop...");
|
||||||
|
Thread.Sleep(1000);
|
||||||
|
svc = svcs.Select(descriptor.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Info("The service stopped.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Restart()
|
||||||
|
{
|
||||||
|
if (!elevated)
|
||||||
|
{
|
||||||
|
Elevate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Info("Restarting the service with id '" + descriptor.Id + "'");
|
||||||
|
if (svc is null)
|
||||||
|
{
|
||||||
|
ThrowNoSuchService();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (svc.Started)
|
||||||
|
{
|
||||||
|
svc.StopService();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (svc.Started)
|
||||||
|
{
|
||||||
|
Thread.Sleep(1000);
|
||||||
|
svc = svcs.Select(descriptor.Id)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
svc.StartService();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RestartSelf()
|
||||||
|
{
|
||||||
|
if (!elevated)
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException("Access is denied.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Info("Restarting the service with id '" + descriptor.Id + "'");
|
||||||
|
|
||||||
|
// run restart from another process group. see README.md for why this is useful.
|
||||||
|
|
||||||
|
bool result = ProcessApis.CreateProcess(null, descriptor.ExecutablePath + " restart", IntPtr.Zero, IntPtr.Zero, false, ProcessApis.CREATE_NEW_PROCESS_GROUP, IntPtr.Zero, null, default, out _);
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
throw new Exception("Failed to invoke restart: " + Marshal.GetLastWin32Error());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Status()
|
||||||
|
{
|
||||||
|
Log.Debug("User requested the status of the process with id '" + descriptor.Id + "'");
|
||||||
|
Console.WriteLine(svc is null ? "NonExistent" : svc.Started ? "Started" : "Stopped");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Test()
|
||||||
|
{
|
||||||
|
if (!elevated)
|
||||||
|
{
|
||||||
|
Elevate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WrapperService wsvc = new WrapperService(descriptor);
|
||||||
|
wsvc.RaiseOnStart(args.ToArray());
|
||||||
|
Thread.Sleep(1000);
|
||||||
|
wsvc.RaiseOnStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestWait()
|
||||||
|
{
|
||||||
|
if (!elevated)
|
||||||
|
{
|
||||||
|
Elevate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WrapperService wsvc = new WrapperService(descriptor);
|
||||||
|
wsvc.RaiseOnStart(args.ToArray());
|
||||||
|
Console.WriteLine("Press any key to stop the service...");
|
||||||
|
_ = Console.Read();
|
||||||
|
wsvc.RaiseOnStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// [DoesNotReturn]
|
||||||
|
void Elevate()
|
||||||
|
{
|
||||||
|
using Process current = Process.GetCurrentProcess();
|
||||||
|
|
||||||
|
ProcessStartInfo startInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
UseShellExecute = true,
|
||||||
|
Verb = "runas",
|
||||||
|
FileName = current.MainModule.FileName,
|
||||||
|
#if NETCOREAPP
|
||||||
|
Arguments = "/elevated " + string.Join(' ', args),
|
||||||
|
#elif !NET20
|
||||||
|
Arguments = "/elevated " + string.Join(" ", args),
|
||||||
|
#else
|
||||||
|
Arguments = "/elevated " + string.Join(" ", args.ToArray()),
|
||||||
|
#endif
|
||||||
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using Process elevated = Process.Start(startInfo);
|
||||||
|
|
||||||
|
elevated.WaitForExit();
|
||||||
|
Environment.Exit(elevated.ExitCode);
|
||||||
|
}
|
||||||
|
catch (Win32Exception e) when (e.NativeErrorCode == Errors.ERROR_CANCELLED)
|
||||||
|
{
|
||||||
|
Log.Fatal(e.Message);
|
||||||
|
Environment.Exit(e.ErrorCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[DoesNotReturn]
|
||||||
|
private static void ThrowNoSuchService() => throw new WmiException(ReturnValue.NoSuchService);
|
||||||
|
|
||||||
|
private static void InitLoggers(ServiceDescriptor descriptor, bool enableConsoleLogging)
|
||||||
|
{
|
||||||
|
// TODO: Make logging levels configurable
|
||||||
|
Level fileLogLevel = Level.Debug;
|
||||||
|
// TODO: Debug should not be printed to console by default. Otherwise commands like 'status' will be pollutted
|
||||||
|
// This is a workaround till there is a better command line parsing, which will allow determining
|
||||||
|
Level consoleLogLevel = Level.Info;
|
||||||
|
Level eventLogLevel = Level.Warn;
|
||||||
|
|
||||||
|
// Legacy format from winsw-1.x: (DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " - " + message);
|
||||||
|
PatternLayout layout = new PatternLayout { ConversionPattern = "%d %-5p - %m%n" };
|
||||||
|
layout.ActivateOptions();
|
||||||
|
|
||||||
|
List<IAppender> appenders = new List<IAppender>();
|
||||||
|
|
||||||
|
// .wrapper.log
|
||||||
|
string wrapperLogPath = Path.Combine(descriptor.LogDirectory, descriptor.BaseName + ".wrapper.log");
|
||||||
|
var wrapperLog = new FileAppender
|
||||||
|
{
|
||||||
|
AppendToFile = true,
|
||||||
|
File = wrapperLogPath,
|
||||||
|
ImmediateFlush = true,
|
||||||
|
Name = "Wrapper file log",
|
||||||
|
Threshold = fileLogLevel,
|
||||||
|
LockingModel = new FileAppender.MinimalLock(),
|
||||||
|
Layout = layout,
|
||||||
|
};
|
||||||
|
wrapperLog.ActivateOptions();
|
||||||
|
appenders.Add(wrapperLog);
|
||||||
|
|
||||||
|
// console log
|
||||||
|
if (enableConsoleLogging)
|
||||||
|
{
|
||||||
|
var consoleAppender = new ConsoleAppender
|
||||||
|
{
|
||||||
|
Name = "Wrapper console log",
|
||||||
|
Threshold = consoleLogLevel,
|
||||||
|
Layout = layout,
|
||||||
|
};
|
||||||
|
consoleAppender.ActivateOptions();
|
||||||
|
appenders.Add(consoleAppender);
|
||||||
|
}
|
||||||
|
|
||||||
|
// event log
|
||||||
|
var systemEventLogger = new ServiceEventLogAppender
|
||||||
|
{
|
||||||
|
Name = "Wrapper event log",
|
||||||
|
Threshold = eventLogLevel,
|
||||||
|
provider = WrapperService.eventLogProvider,
|
||||||
|
};
|
||||||
|
systemEventLogger.ActivateOptions();
|
||||||
|
appenders.Add(systemEventLogger);
|
||||||
|
|
||||||
|
BasicConfigurator.Configure(
|
||||||
|
#if NETCOREAPP
|
||||||
|
LogManager.GetRepository(Assembly.GetExecutingAssembly()),
|
||||||
|
#endif
|
||||||
|
appenders.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static unsafe bool IsProcessElevated()
|
||||||
|
{
|
||||||
|
IntPtr process = ProcessApis.GetCurrentProcess();
|
||||||
|
if (!ProcessApis.OpenProcessToken(process, TokenAccessLevels.Read, out IntPtr token))
|
||||||
|
{
|
||||||
|
ThrowWin32Exception("Failed to open process token.");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!SecurityApis.GetTokenInformation(
|
||||||
|
token,
|
||||||
|
SecurityApis.TOKEN_INFORMATION_CLASS.TokenElevation,
|
||||||
|
out SecurityApis.TOKEN_ELEVATION elevation,
|
||||||
|
sizeof(SecurityApis.TOKEN_ELEVATION),
|
||||||
|
out _))
|
||||||
|
{
|
||||||
|
ThrowWin32Exception("Failed to get token information");
|
||||||
|
}
|
||||||
|
|
||||||
|
return elevation.TokenIsElevated != 0;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_ = HandleApis.CloseHandle(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ThrowWin32Exception(string message)
|
||||||
|
{
|
||||||
|
Win32Exception inner = new Win32Exception();
|
||||||
|
throw new Win32Exception(inner.NativeErrorCode, message + ' ' + inner.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ReadPassword()
|
||||||
|
{
|
||||||
|
StringBuilder buf = new StringBuilder();
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
ConsoleKeyInfo key = Console.ReadKey(true);
|
||||||
|
if (key.Key == ConsoleKey.Enter)
|
||||||
|
{
|
||||||
|
return buf.ToString();
|
||||||
|
}
|
||||||
|
else if (key.Key == ConsoleKey.Backspace)
|
||||||
|
{
|
||||||
|
_ = buf.Remove(buf.Length - 1, 1);
|
||||||
|
Console.Write("\b \b");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.Write('*');
|
||||||
|
_ = buf.Append(key.KeyChar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PrintHelp()
|
||||||
|
{
|
||||||
|
Console.WriteLine("A wrapper binary that can be used to host executables as Windows services");
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.WriteLine("Usage: winsw <command> [<args>]");
|
||||||
|
Console.WriteLine(" Missing arguments triggers the service mode");
|
||||||
|
Console.WriteLine();
|
||||||
|
PrintAvailableCommands();
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.WriteLine("Extra options:");
|
||||||
|
Console.WriteLine(" /redirect redirect the wrapper's STDOUT and STDERR to the specified file");
|
||||||
|
Console.WriteLine();
|
||||||
|
PrintVersion();
|
||||||
|
Console.WriteLine("More info: https://github.com/winsw/winsw");
|
||||||
|
Console.WriteLine("Bug tracker: https://github.com/winsw/winsw/issues");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Rework to enum in winsw-2.0
|
||||||
|
private static void PrintAvailableCommands()
|
||||||
|
{
|
||||||
|
Console.WriteLine(
|
||||||
|
@"Available commands:
|
||||||
|
install install the service to Windows Service Controller
|
||||||
|
uninstall uninstall the service
|
||||||
|
start start the service (must be installed before)
|
||||||
|
stop stop the service
|
||||||
|
stopwait stop the service and wait until it's actually stopped
|
||||||
|
restart restart the service
|
||||||
|
restart! self-restart (can be called from child processes)
|
||||||
|
status check the current status of the service
|
||||||
|
test check if the service can be started and then stopped
|
||||||
|
testwait starts the service and waits until a key is pressed then stops the service
|
||||||
|
version print the version info
|
||||||
|
help print the help info (aliases: -h,--help,-?,/?)");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PrintVersion()
|
||||||
|
{
|
||||||
|
Console.WriteLine("WinSW " + WrapperService.Version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,483 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.ServiceProcess;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
#if VNEXT
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
#endif
|
||||||
|
using log4net;
|
||||||
|
using winsw.Extensions;
|
||||||
|
using winsw.Logging;
|
||||||
|
using winsw.Native;
|
||||||
|
using winsw.Util;
|
||||||
|
|
||||||
|
namespace winsw
|
||||||
|
{
|
||||||
|
public class WrapperService : ServiceBase, EventLogger
|
||||||
|
{
|
||||||
|
private ServiceApis.SERVICE_STATUS _wrapperServiceStatus;
|
||||||
|
|
||||||
|
private readonly Process _process = new Process();
|
||||||
|
private readonly ServiceDescriptor _descriptor;
|
||||||
|
private Dictionary<string, string>? _envs;
|
||||||
|
|
||||||
|
internal WinSWExtensionManager ExtensionManager { get; private set; }
|
||||||
|
|
||||||
|
private static readonly ILog Log = LogManager.GetLogger(
|
||||||
|
#if NETCOREAPP
|
||||||
|
Assembly.GetExecutingAssembly(),
|
||||||
|
#endif
|
||||||
|
"WinSW");
|
||||||
|
internal static readonly WrapperServiceEventLogProvider eventLogProvider = new WrapperServiceEventLogProvider();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates to the watch dog thread that we are going to terminate the process,
|
||||||
|
/// so don't try to kill us when the child exits.
|
||||||
|
/// </summary>
|
||||||
|
private bool _orderlyShutdown;
|
||||||
|
private bool _systemShuttingdown;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Version of Windows service wrapper
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The version will be taken from <see cref="AssemblyInfo"/>
|
||||||
|
/// </remarks>
|
||||||
|
public static Version Version => Assembly.GetExecutingAssembly().GetName().Version!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the system is shutting down.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsShuttingDown => _systemShuttingdown;
|
||||||
|
|
||||||
|
public WrapperService(ServiceDescriptor descriptor)
|
||||||
|
{
|
||||||
|
_descriptor = descriptor;
|
||||||
|
ServiceName = _descriptor.Id;
|
||||||
|
ExtensionManager = new WinSWExtensionManager(_descriptor);
|
||||||
|
CanShutdown = true;
|
||||||
|
CanStop = true;
|
||||||
|
CanPauseAndContinue = false;
|
||||||
|
AutoLog = true;
|
||||||
|
_systemShuttingdown = false;
|
||||||
|
|
||||||
|
// Register the event log provider
|
||||||
|
eventLogProvider.service = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WrapperService() : this(new ServiceDescriptor())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Process the file copy instructions, so that we can replace files that are always in use while
|
||||||
|
/// the service runs.
|
||||||
|
/// </summary>
|
||||||
|
private void HandleFileCopies()
|
||||||
|
{
|
||||||
|
var file = _descriptor.BasePath + ".copies";
|
||||||
|
if (!File.Exists(file))
|
||||||
|
return; // nothing to handle
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var tr = new StreamReader(file, Encoding.UTF8);
|
||||||
|
string? line;
|
||||||
|
while ((line = tr.ReadLine()) != null)
|
||||||
|
{
|
||||||
|
LogEvent("Handling copy: " + line);
|
||||||
|
string[] tokens = line.Split('>');
|
||||||
|
if (tokens.Length > 2)
|
||||||
|
{
|
||||||
|
LogEvent("Too many delimiters in " + line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
MoveFile(tokens[0], tokens[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
File.Delete(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// File replacement.
|
||||||
|
/// </summary>
|
||||||
|
private void MoveFile(string sourceFileName, string destFileName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
FileHelper.MoveOrReplaceFile(sourceFileName, destFileName);
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
LogEvent("Failed to move :" + sourceFileName + " to " + destFileName + " because " + e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle the creation of the logfiles based on the optional logmode setting.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Log Handler, which should be used for the spawned process</returns>
|
||||||
|
private LogHandler CreateExecutableLogHandler()
|
||||||
|
{
|
||||||
|
string logDirectory = _descriptor.LogDirectory;
|
||||||
|
|
||||||
|
if (!Directory.Exists(logDirectory))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(logDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
LogHandler logAppender = _descriptor.LogHandler;
|
||||||
|
logAppender.EventLogger = this;
|
||||||
|
return logAppender;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LogEvent(string message)
|
||||||
|
{
|
||||||
|
if (_systemShuttingdown)
|
||||||
|
{
|
||||||
|
/* NOP - cannot call EventLog because of shutdown. */
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
EventLog.WriteEntry(message);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error("Failed to log event in Windows Event Log: " + message + "; Reason: ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LogEvent(string message, EventLogEntryType type)
|
||||||
|
{
|
||||||
|
if (_systemShuttingdown)
|
||||||
|
{
|
||||||
|
/* NOP - cannot call EventLog because of shutdown. */
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
EventLog.WriteEntry(message, type);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error("Failed to log event in Windows Event Log. Reason: ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnStart(string[] args)
|
||||||
|
{
|
||||||
|
_envs = _descriptor.EnvironmentVariables;
|
||||||
|
// TODO: Disabled according to security concerns in https://github.com/kohsuke/winsw/issues/54
|
||||||
|
// Could be restored, but unlikely it's required in event logs at all
|
||||||
|
/**
|
||||||
|
foreach (string key in _envs.Keys)
|
||||||
|
{
|
||||||
|
LogEvent("envar " + key + '=' + _envs[key]);
|
||||||
|
}*/
|
||||||
|
HandleFileCopies();
|
||||||
|
|
||||||
|
// handle downloads
|
||||||
|
#if VNEXT
|
||||||
|
List<Download> downloads = _descriptor.Downloads;
|
||||||
|
Task[] tasks = new Task[downloads.Count];
|
||||||
|
for (int i = 0; i < downloads.Count; i++)
|
||||||
|
{
|
||||||
|
Download download = downloads[i];
|
||||||
|
string downloadMessage = $"Downloading: {download.From} to {download.To}. failOnError={download.FailOnError.ToString()}";
|
||||||
|
LogEvent(downloadMessage);
|
||||||
|
Log.Info(downloadMessage);
|
||||||
|
tasks[i] = download.PerformAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Task.WaitAll(tasks);
|
||||||
|
}
|
||||||
|
catch (AggregateException e)
|
||||||
|
{
|
||||||
|
List<Exception> exceptions = new List<Exception>(e.InnerExceptions.Count);
|
||||||
|
for (int i = 0; i < tasks.Length; i++)
|
||||||
|
{
|
||||||
|
if (tasks[i].IsFaulted)
|
||||||
|
{
|
||||||
|
Download download = downloads[i];
|
||||||
|
string errorMessage = $"Failed to download {download.From} to {download.To}";
|
||||||
|
AggregateException exception = tasks[i].Exception!;
|
||||||
|
LogEvent($"{errorMessage}. {exception.Message}");
|
||||||
|
Log.Error(errorMessage, exception);
|
||||||
|
|
||||||
|
// TODO: move this code into the download logic
|
||||||
|
if (download.FailOnError)
|
||||||
|
{
|
||||||
|
exceptions.Add(new IOException(errorMessage, exception));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AggregateException(exceptions);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
foreach (Download download in _descriptor.Downloads)
|
||||||
|
{
|
||||||
|
string downloadMessage = $"Downloading: {download.From} to {download.To}. failOnError={download.FailOnError.ToString()}";
|
||||||
|
LogEvent(downloadMessage);
|
||||||
|
Log.Info(downloadMessage);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
download.Perform();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
string errorMessage = $"Failed to download {download.From} to {download.To}";
|
||||||
|
LogEvent($"{errorMessage}. {e.Message}");
|
||||||
|
Log.Error(errorMessage, e);
|
||||||
|
|
||||||
|
// TODO: move this code into the download logic
|
||||||
|
if (download.FailOnError)
|
||||||
|
{
|
||||||
|
throw new IOException(errorMessage, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Else just keep going
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
string? startArguments = _descriptor.StartArguments;
|
||||||
|
|
||||||
|
if (startArguments is null)
|
||||||
|
{
|
||||||
|
startArguments = _descriptor.Arguments;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
startArguments += " " + _descriptor.Arguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converting newlines, line returns, tabs into a single
|
||||||
|
// space. This allows users to provide multi-line arguments
|
||||||
|
// in the xml for readability.
|
||||||
|
startArguments = Regex.Replace(startArguments, @"\s*[\n\r]+\s*", " ");
|
||||||
|
|
||||||
|
LogEvent("Starting " + _descriptor.Executable + ' ' + startArguments);
|
||||||
|
Log.Info("Starting " + _descriptor.Executable + ' ' + startArguments);
|
||||||
|
|
||||||
|
// Load and start extensions
|
||||||
|
ExtensionManager.LoadExtensions();
|
||||||
|
ExtensionManager.FireOnWrapperStarted();
|
||||||
|
|
||||||
|
LogHandler executableLogHandler = CreateExecutableLogHandler();
|
||||||
|
StartProcess(_process, startArguments, _descriptor.Executable, executableLogHandler, true);
|
||||||
|
ExtensionManager.FireOnProcessStarted(_process);
|
||||||
|
|
||||||
|
_process.StandardInput.Close(); // nothing for you to read!
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnShutdown()
|
||||||
|
{
|
||||||
|
// WriteEvent("OnShutdown");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_systemShuttingdown = true;
|
||||||
|
StopIt();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error("Shutdown exception", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnStop()
|
||||||
|
{
|
||||||
|
// WriteEvent("OnStop");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
StopIt();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error("Cannot stop exception", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RaiseOnStart(string[] args) => this.OnStart(args);
|
||||||
|
|
||||||
|
internal void RaiseOnStop() => this.OnStop();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when we are told by Windows SCM to exit.
|
||||||
|
/// </summary>
|
||||||
|
private void StopIt()
|
||||||
|
{
|
||||||
|
string? stopArguments = _descriptor.StopArguments;
|
||||||
|
LogEvent("Stopping " + _descriptor.Id);
|
||||||
|
Log.Info("Stopping " + _descriptor.Id);
|
||||||
|
_orderlyShutdown = true;
|
||||||
|
|
||||||
|
if (stopArguments is null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Log.Debug("ProcessKill " + _process.Id);
|
||||||
|
ProcessHelper.StopProcessAndChildren(_process.Id, _descriptor.StopTimeout, _descriptor.StopParentProcessFirst);
|
||||||
|
ExtensionManager.FireOnProcessTerminated(_process);
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
// already terminated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SignalShutdownPending();
|
||||||
|
|
||||||
|
stopArguments += " " + _descriptor.Arguments;
|
||||||
|
|
||||||
|
Process stopProcess = new Process();
|
||||||
|
string? executable = _descriptor.StopExecutable;
|
||||||
|
|
||||||
|
executable ??= _descriptor.Executable;
|
||||||
|
|
||||||
|
// TODO: Redirect logging to Log4Net once https://github.com/kohsuke/winsw/pull/213 is integrated
|
||||||
|
StartProcess(stopProcess, stopArguments, executable, null, false);
|
||||||
|
|
||||||
|
Log.Debug("WaitForProcessToExit " + _process.Id + "+" + stopProcess.Id);
|
||||||
|
WaitForProcessToExit(_process);
|
||||||
|
WaitForProcessToExit(stopProcess);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop extensions
|
||||||
|
ExtensionManager.FireBeforeWrapperStopped();
|
||||||
|
|
||||||
|
if (_systemShuttingdown && _descriptor.BeepOnShutdown)
|
||||||
|
{
|
||||||
|
Console.Beep();
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Info("Finished " + _descriptor.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WaitForProcessToExit(Process processoWait)
|
||||||
|
{
|
||||||
|
SignalShutdownPending();
|
||||||
|
|
||||||
|
int effectiveProcessWaitSleepTime;
|
||||||
|
if (_descriptor.SleepTime.TotalMilliseconds > int.MaxValue)
|
||||||
|
{
|
||||||
|
Log.Warn("The requested sleep time " + _descriptor.SleepTime.TotalMilliseconds + "is greater that the max value " +
|
||||||
|
int.MaxValue + ". The value will be truncated");
|
||||||
|
effectiveProcessWaitSleepTime = int.MaxValue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
effectiveProcessWaitSleepTime = (int)_descriptor.SleepTime.TotalMilliseconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// WriteEvent("WaitForProcessToExit [start]");
|
||||||
|
|
||||||
|
while (!processoWait.WaitForExit(effectiveProcessWaitSleepTime))
|
||||||
|
{
|
||||||
|
SignalShutdownPending();
|
||||||
|
// WriteEvent("WaitForProcessToExit [repeat]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
// already terminated
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteEvent("WaitForProcessToExit [finished]");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SignalShutdownPending()
|
||||||
|
{
|
||||||
|
int effectiveWaitHint;
|
||||||
|
if (_descriptor.WaitHint.TotalMilliseconds > int.MaxValue)
|
||||||
|
{
|
||||||
|
Log.Warn("The requested WaitHint value (" + _descriptor.WaitHint.TotalMilliseconds + " ms) is greater that the max value " +
|
||||||
|
int.MaxValue + ". The value will be truncated");
|
||||||
|
effectiveWaitHint = int.MaxValue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
effectiveWaitHint = (int)_descriptor.WaitHint.TotalMilliseconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
RequestAdditionalTime(effectiveWaitHint);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SignalShutdownComplete()
|
||||||
|
{
|
||||||
|
IntPtr handle = ServiceHandle;
|
||||||
|
_wrapperServiceStatus.CheckPoint++;
|
||||||
|
// WriteEvent("SignalShutdownComplete " + wrapperServiceStatus.checkPoint + ":" + wrapperServiceStatus.waitHint);
|
||||||
|
_wrapperServiceStatus.CurrentState = ServiceApis.ServiceState.STOPPED;
|
||||||
|
ServiceApis.SetServiceStatus(handle, _wrapperServiceStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartProcess(Process processToStart, string arguments, string executable, LogHandler? logHandler, bool redirectStdin)
|
||||||
|
{
|
||||||
|
// Define handler of the completed process
|
||||||
|
void OnProcessCompleted(Process proc)
|
||||||
|
{
|
||||||
|
string msg = processToStart.Id + " - " + processToStart.StartInfo.FileName + " " + processToStart.StartInfo.Arguments;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_orderlyShutdown)
|
||||||
|
{
|
||||||
|
LogEvent("Child process [" + msg + "] terminated with " + proc.ExitCode, EventLogEntryType.Information);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogEvent("Child process [" + msg + "] finished with " + proc.ExitCode, EventLogEntryType.Warning);
|
||||||
|
// if we finished orderly, report that to SCM.
|
||||||
|
// by not reporting unclean shutdown, we let Windows SCM to decide if it wants to
|
||||||
|
// restart the service automatically
|
||||||
|
if (proc.ExitCode == 0)
|
||||||
|
SignalShutdownComplete();
|
||||||
|
|
||||||
|
Environment.Exit(proc.ExitCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ioe)
|
||||||
|
{
|
||||||
|
LogEvent("WaitForExit " + ioe.Message);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
proc.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoke process and exit
|
||||||
|
ProcessHelper.StartProcessAndCallbackForExit(
|
||||||
|
processToStart: processToStart,
|
||||||
|
executable: executable,
|
||||||
|
arguments: arguments,
|
||||||
|
envVars: _envs,
|
||||||
|
workingDirectory: _descriptor.WorkingDirectory,
|
||||||
|
priority: _descriptor.Priority,
|
||||||
|
callback: OnProcessCompleted,
|
||||||
|
logHandler: logHandler,
|
||||||
|
redirectStdin: redirectStdin,
|
||||||
|
hideWindow: _descriptor.HideWindow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,7 +46,7 @@ $@"<service>
|
||||||
Console.SetError(swErr);
|
Console.SetError(swErr);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
WrapperService.Run(arguments, descriptor ?? DefaultServiceDescriptor);
|
Program.Run(arguments, descriptor ?? DefaultServiceDescriptor);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
@ -78,7 +78,7 @@ $@"<service>
|
||||||
Console.SetError(swErr);
|
Console.SetError(swErr);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
WrapperService.Run(arguments, descriptor ?? DefaultServiceDescriptor);
|
Program.Run(arguments, descriptor ?? DefaultServiceDescriptor);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
@ -7,7 +7,7 @@ namespace winswTests.Util
|
||||||
{
|
{
|
||||||
internal static void RequireProcessElevated()
|
internal static void RequireProcessElevated()
|
||||||
{
|
{
|
||||||
if (!WrapperService.IsProcessElevated())
|
if (!Program.IsProcessElevated())
|
||||||
{
|
{
|
||||||
Assert.Ignore();
|
Assert.Ignore();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue