mirror of https://github.com/winsw/winsw
CLI updates
parent
274deddfef
commit
6cefa93c69
|
@ -14,9 +14,9 @@ namespace WinSW.Configuration
|
|||
{
|
||||
public abstract string FullPath { get; }
|
||||
|
||||
public abstract string Id { get; }
|
||||
public abstract string Name { get; }
|
||||
|
||||
public virtual string Caption => string.Empty;
|
||||
public virtual string DisplayName => string.Empty;
|
||||
|
||||
public virtual string Description => string.Empty;
|
||||
|
||||
|
|
|
@ -67,13 +67,13 @@ namespace WinSW
|
|||
Environment.SetEnvironmentVariable("BASE", baseDir);
|
||||
|
||||
// ditto for ID
|
||||
Environment.SetEnvironmentVariable("SERVICE_ID", this.Id);
|
||||
Environment.SetEnvironmentVariable("SERVICE_ID", this.Name);
|
||||
|
||||
// New name
|
||||
Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameExecutablePath, this.ExecutablePath);
|
||||
|
||||
// Also inject system environment variables
|
||||
Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameServiceId, this.Id);
|
||||
Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameServiceId, this.Name);
|
||||
|
||||
this.environmentVariables = this.LoadEnvironmentVariables();
|
||||
}
|
||||
|
@ -105,13 +105,13 @@ namespace WinSW
|
|||
Environment.SetEnvironmentVariable("BASE", baseDir);
|
||||
|
||||
// ditto for ID
|
||||
Environment.SetEnvironmentVariable("SERVICE_ID", this.Id);
|
||||
Environment.SetEnvironmentVariable("SERVICE_ID", this.Name);
|
||||
|
||||
// New name
|
||||
Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameExecutablePath, this.ExecutablePath);
|
||||
|
||||
// Also inject system environment variables
|
||||
Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameServiceId, this.Id);
|
||||
Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameServiceId, this.Name);
|
||||
|
||||
this.environmentVariables = this.LoadEnvironmentVariables();
|
||||
}
|
||||
|
@ -529,9 +529,9 @@ namespace WinSW
|
|||
}
|
||||
}
|
||||
|
||||
public override string Id => this.SingleElement("id");
|
||||
public override string Name => this.SingleElement("id");
|
||||
|
||||
public override string Caption => this.SingleElement("name", true) ?? base.Caption;
|
||||
public override string DisplayName => this.SingleElement("name", true) ?? base.DisplayName;
|
||||
|
||||
public override string Description => this.SingleElement("description", true) ?? base.Description;
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.ServiceProcess;
|
||||
using WinSW.Configuration;
|
||||
|
||||
namespace WinSW
|
||||
{
|
||||
|
@ -16,5 +18,17 @@ namespace WinSW
|
|||
return $"({process.Id})";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string Format(this ServiceConfig config)
|
||||
{
|
||||
string name = config.Name;
|
||||
string displayName = config.DisplayName;
|
||||
return $"{(string.IsNullOrEmpty(displayName) ? name : displayName)} ({name})";
|
||||
}
|
||||
|
||||
internal static string Format(this ServiceController controller)
|
||||
{
|
||||
return $"{controller.DisplayName} ({controller.ServiceName})";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ namespace WinSW.Native
|
|||
internal const int ERROR_SERVICE_DOES_NOT_EXIST = 1060;
|
||||
internal const int ERROR_SERVICE_NOT_ACTIVE = 1062;
|
||||
internal const int ERROR_SERVICE_MARKED_FOR_DELETE = 1072;
|
||||
internal const int ERROR_SERVICE_EXISTS = 1073;
|
||||
internal const int ERROR_CANCELLED = 1223;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Security.AccessControl;
|
||||
using System.ServiceProcess;
|
||||
using System.Text;
|
||||
|
@ -218,20 +219,23 @@ namespace WinSW.Native
|
|||
ServiceStartMode startMode,
|
||||
string[] dependencies)
|
||||
{
|
||||
if (!ChangeServiceConfig(
|
||||
this.handle,
|
||||
default,
|
||||
startMode,
|
||||
default,
|
||||
null,
|
||||
null,
|
||||
IntPtr.Zero,
|
||||
GetNativeDependencies(dependencies),
|
||||
null,
|
||||
null,
|
||||
displayName))
|
||||
unchecked
|
||||
{
|
||||
Throw.Command.Win32Exception("Failed to change service config.");
|
||||
if (!ChangeServiceConfig(
|
||||
this.handle,
|
||||
(ServiceType)SERVICE_NO_CHANGE,
|
||||
startMode,
|
||||
(ServiceErrorControl)SERVICE_NO_CHANGE,
|
||||
null,
|
||||
null,
|
||||
IntPtr.Zero,
|
||||
GetNativeDependencies(dependencies),
|
||||
null,
|
||||
null,
|
||||
displayName))
|
||||
{
|
||||
Throw.Command.Win32Exception("Failed to change service config.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using System;
|
||||
#pragma warning disable SA1310 // Field names should not contain underscore
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.AccessControl;
|
||||
using System.ServiceProcess;
|
||||
|
@ -8,6 +10,8 @@ namespace WinSW.Native
|
|||
{
|
||||
internal static class ServiceApis
|
||||
{
|
||||
internal const uint SERVICE_NO_CHANGE = 0xffffffff;
|
||||
|
||||
[DllImport(Libraries.Advapi32, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "ChangeServiceConfigW")]
|
||||
internal static extern bool ChangeServiceConfig(
|
||||
IntPtr serviceHandle,
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using System.ComponentModel;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace WinSW.Native
|
||||
|
@ -9,6 +11,53 @@ namespace WinSW.Native
|
|||
internal static class Command
|
||||
{
|
||||
/// <exception cref="CommandException" />
|
||||
[DoesNotReturn]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
internal static void Exception(Exception inner)
|
||||
{
|
||||
throw new CommandException(inner);
|
||||
}
|
||||
|
||||
/// <exception cref="CommandException" />
|
||||
[DoesNotReturn]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
internal static void Exception(string message)
|
||||
{
|
||||
Debug.Assert(message.EndsWith("."));
|
||||
throw new CommandException(message);
|
||||
}
|
||||
|
||||
/// <exception cref="CommandException" />
|
||||
[DoesNotReturn]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
internal static void Exception(string message, Exception inner)
|
||||
{
|
||||
Debug.Assert(message.EndsWith("."));
|
||||
throw new CommandException(message, inner);
|
||||
}
|
||||
|
||||
/// <exception cref="CommandException" />
|
||||
[DoesNotReturn]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
internal static void Win32Exception(int error)
|
||||
{
|
||||
Debug.Assert(error != 0);
|
||||
throw new CommandException(new Win32Exception(error));
|
||||
}
|
||||
|
||||
/// <exception cref="CommandException" />
|
||||
[DoesNotReturn]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
internal static void Win32Exception(int error, string message)
|
||||
{
|
||||
Debug.Assert(error != 0);
|
||||
Win32Exception inner = new Win32Exception(error);
|
||||
Debug.Assert(message.EndsWith("."));
|
||||
throw new CommandException(message + ' ' + inner.Message, inner);
|
||||
}
|
||||
|
||||
/// <exception cref="CommandException" />
|
||||
[DoesNotReturn]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
internal static void Win32Exception(string message)
|
||||
{
|
||||
|
|
|
@ -15,5 +15,11 @@ namespace System.Diagnostics.CodeAnalysis
|
|||
internal sealed class MaybeNullAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Applied to a method that will never return under any circumstance.</summary>
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
|
||||
internal sealed class DoesNotReturnAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -17,7 +17,7 @@ namespace WinSW.Tests.Configuration
|
|||
{
|
||||
XmlServiceConfig config = Load("complete");
|
||||
|
||||
Assert.Equal("myapp", config.Id);
|
||||
Assert.Equal("myapp", config.Name);
|
||||
Assert.Equal("%BASE%\\myExecutable.exe", config.Executable);
|
||||
|
||||
ServiceConfigAssert.AssertAllOptionalPropertiesAreDefault(config);
|
||||
|
@ -28,7 +28,7 @@ namespace WinSW.Tests.Configuration
|
|||
{
|
||||
XmlServiceConfig config = Load("minimal");
|
||||
|
||||
Assert.Equal("myapp", config.Id);
|
||||
Assert.Equal("myapp", config.Name);
|
||||
Assert.Equal("%BASE%\\myExecutable.exe", config.Executable);
|
||||
|
||||
ServiceConfigAssert.AssertAllOptionalPropertiesAreDefault(config);
|
||||
|
|
|
@ -46,7 +46,7 @@ $@"<service>
|
|||
XmlServiceConfig.TestConfig = config ?? DefaultServiceConfig;
|
||||
try
|
||||
{
|
||||
_ = Program.Run(arguments);
|
||||
_ = Program.Main(arguments);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@ -81,7 +81,7 @@ $@"<service>
|
|||
Program.TestExceptionHandler = (e, _) => exception = e;
|
||||
try
|
||||
{
|
||||
_ = Program.Run(arguments);
|
||||
_ = Program.Main(arguments);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
|
@ -26,7 +26,7 @@ namespace WinSW.Tests.Util
|
|||
|
||||
public override string FullPath => this.config.FullPath;
|
||||
|
||||
public override string Id => this.config.Id;
|
||||
public override string Name => this.config.Name;
|
||||
|
||||
public override string Executable => this.config.Executable;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using log4net.Appender;
|
||||
using log4net.Core;
|
||||
|
||||
namespace WinSW.Logging
|
||||
{
|
||||
internal sealed class WinSWConsoleAppender : AppenderSkeleton
|
||||
{
|
||||
protected override void Append(LoggingEvent loggingEvent)
|
||||
{
|
||||
Console.ResetColor();
|
||||
|
||||
Level level = loggingEvent.Level;
|
||||
Console.ForegroundColor =
|
||||
level >= Level.Error ? ConsoleColor.Red :
|
||||
level >= Level.Warn ? ConsoleColor.Yellow :
|
||||
level >= Level.Info ? ConsoleColor.Gray :
|
||||
ConsoleColor.DarkGray;
|
||||
try
|
||||
{
|
||||
this.Layout.Format(Console.Out, loggingEvent);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
#if !NETCOREAPP
|
||||
namespace System.Diagnostics.CodeAnalysis
|
||||
{
|
||||
/// <summary>Applied to a method that will never return under any circumstance.</summary>
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
|
||||
internal sealed class DoesNotReturnAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -3,16 +3,12 @@ using System.Collections.Generic;
|
|||
using System.CommandLine;
|
||||
using System.CommandLine.Builder;
|
||||
using System.CommandLine.Invocation;
|
||||
using System.CommandLine.IO;
|
||||
using System.CommandLine.Parsing;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.AccessControl;
|
||||
using System.Security.Principal;
|
||||
using System.ServiceProcess;
|
||||
|
@ -39,24 +35,24 @@ namespace WinSW
|
|||
|
||||
internal static Action<Exception, InvocationContext>? TestExceptionHandler;
|
||||
|
||||
private static int Main(string[] args)
|
||||
{
|
||||
int exitCode = Run(args);
|
||||
Log.Debug("Completed. Exit code is " + exitCode);
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
internal static int Run(string[] args)
|
||||
internal static int Main(string[] args)
|
||||
{
|
||||
bool elevated;
|
||||
if (args[0] == "--elevated")
|
||||
if (args.Length > 0 && args[0] == "--elevated")
|
||||
{
|
||||
elevated = true;
|
||||
|
||||
_ = ConsoleApis.FreeConsole();
|
||||
_ = ConsoleApis.AttachConsole(ConsoleApis.ATTACH_PARENT_PROCESS);
|
||||
|
||||
args = new List<string>(args).GetRange(1, args.Length - 1).ToArray();
|
||||
#if NETCOREAPP
|
||||
args = args[1..];
|
||||
#else
|
||||
string[] oldArgs = args;
|
||||
int newLength = oldArgs.Length - 1;
|
||||
args = new string[newLength];
|
||||
Array.Copy(oldArgs, 1, args, 0, newLength);
|
||||
#endif
|
||||
}
|
||||
else if (Environment.OSVersion.Version.Major == 5)
|
||||
{
|
||||
|
@ -72,19 +68,19 @@ namespace WinSW
|
|||
{
|
||||
Handler = CommandHandler.Create((string? pathToConfig) =>
|
||||
{
|
||||
XmlServiceConfig config;
|
||||
XmlServiceConfig config = null!;
|
||||
try
|
||||
{
|
||||
config = XmlServiceConfig.Create(pathToConfig);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
throw new CommandException("The specified command or file was not found.");
|
||||
Throw.Command.Exception("The specified command or file was not found.");
|
||||
}
|
||||
|
||||
InitLoggers(config, enableConsoleLogging: false);
|
||||
|
||||
Log.Debug("Starting WinSW in service mode");
|
||||
Log.Debug("Starting WinSW in service mode.");
|
||||
ServiceBase.Run(new WrapperService(config));
|
||||
}),
|
||||
};
|
||||
|
@ -141,11 +137,12 @@ namespace WinSW
|
|||
{
|
||||
var start = new Command("start", "Starts the service.")
|
||||
{
|
||||
Handler = CommandHandler.Create<string?, bool>(Start),
|
||||
Handler = CommandHandler.Create<string?, bool, bool, CancellationToken>(Start),
|
||||
};
|
||||
|
||||
start.Add(config);
|
||||
start.Add(noElevate);
|
||||
start.Add(new Option("--no-wait", "Doesn't wait for the service to actually start."));
|
||||
|
||||
root.Add(start);
|
||||
}
|
||||
|
@ -153,7 +150,7 @@ namespace WinSW
|
|||
{
|
||||
var stop = new Command("stop", "Stops the service.")
|
||||
{
|
||||
Handler = CommandHandler.Create<string?, bool, bool, bool>(Stop),
|
||||
Handler = CommandHandler.Create<string?, bool, bool, bool, CancellationToken>(Stop),
|
||||
};
|
||||
|
||||
stop.Add(config);
|
||||
|
@ -167,7 +164,7 @@ namespace WinSW
|
|||
{
|
||||
var restart = new Command("restart", "Stops and then starts the service.")
|
||||
{
|
||||
Handler = CommandHandler.Create<string?, bool, bool>(Restart),
|
||||
Handler = CommandHandler.Create<string?, bool, bool, CancellationToken>(Restart),
|
||||
};
|
||||
|
||||
restart.Add(config);
|
||||
|
@ -240,14 +237,15 @@ namespace WinSW
|
|||
}
|
||||
|
||||
{
|
||||
var dev = new Command("dev");
|
||||
|
||||
dev.Add(config);
|
||||
dev.Add(noElevate);
|
||||
var dev = new Command("dev", "Experimental commands.")
|
||||
{
|
||||
config,
|
||||
noElevate,
|
||||
};
|
||||
|
||||
root.Add(dev);
|
||||
|
||||
var ps = new Command("ps")
|
||||
var ps = new Command("ps", "Draws the process tree associated with the service.")
|
||||
{
|
||||
Handler = CommandHandler.Create<string?, bool>(DevPs),
|
||||
};
|
||||
|
@ -256,14 +254,8 @@ namespace WinSW
|
|||
}
|
||||
|
||||
return new CommandLineBuilder(root)
|
||||
|
||||
// see UseDefaults
|
||||
.UseVersionOption()
|
||||
.UseHelp()
|
||||
/* .UseEnvironmentVariableDirective() */
|
||||
.UseParseDirective()
|
||||
.UseDebugDirective()
|
||||
.UseSuggestDirective()
|
||||
.RegisterWithDotnetSuggest()
|
||||
.UseTypoCorrections()
|
||||
.UseParseErrorReporting()
|
||||
|
@ -274,64 +266,57 @@ namespace WinSW
|
|||
|
||||
static void OnException(Exception exception, InvocationContext context)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
try
|
||||
Debug.Assert(exception is TargetInvocationException);
|
||||
Debug.Assert(exception.InnerException != null);
|
||||
exception = exception.InnerException!;
|
||||
switch (exception)
|
||||
{
|
||||
IStandardStreamWriter error = context.Console.Error;
|
||||
case InvalidDataException e:
|
||||
{
|
||||
string message = "The configuration file could not be loaded. " + e.Message;
|
||||
Log.Fatal(message, e);
|
||||
context.ResultCode = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
Debug.Assert(exception is TargetInvocationException);
|
||||
Debug.Assert(exception.InnerException != null);
|
||||
exception = exception.InnerException!;
|
||||
switch (exception)
|
||||
{
|
||||
case InvalidDataException e:
|
||||
{
|
||||
string message = "The configuration file cound not be loaded. " + e.Message;
|
||||
Log.Fatal(message, e);
|
||||
error.WriteLine(message);
|
||||
context.ResultCode = -1;
|
||||
break;
|
||||
}
|
||||
case OperationCanceledException e:
|
||||
{
|
||||
Debug.Assert(e.CancellationToken == context.GetCancellationToken());
|
||||
Log.Fatal(e.Message);
|
||||
context.ResultCode = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
case CommandException e:
|
||||
{
|
||||
string message = e.Message;
|
||||
Log.Fatal(message);
|
||||
error.WriteLine(message);
|
||||
context.ResultCode = e.InnerException is Win32Exception inner ? inner.NativeErrorCode : -1;
|
||||
break;
|
||||
}
|
||||
case CommandException e:
|
||||
{
|
||||
string message = e.Message;
|
||||
Log.Fatal(message);
|
||||
context.ResultCode = e.InnerException is Win32Exception inner ? inner.NativeErrorCode : -1;
|
||||
break;
|
||||
}
|
||||
|
||||
case InvalidOperationException e when e.InnerException is Win32Exception inner:
|
||||
{
|
||||
string message = e.Message;
|
||||
Log.Fatal(message, e);
|
||||
error.WriteLine(message);
|
||||
context.ResultCode = inner.NativeErrorCode;
|
||||
break;
|
||||
}
|
||||
case InvalidOperationException e when e.InnerException is Win32Exception inner:
|
||||
{
|
||||
string message = e.Message;
|
||||
Log.Fatal(message);
|
||||
context.ResultCode = inner.NativeErrorCode;
|
||||
break;
|
||||
}
|
||||
|
||||
case Win32Exception e:
|
||||
{
|
||||
string message = e.Message;
|
||||
Log.Fatal(message, e);
|
||||
error.WriteLine(message);
|
||||
context.ResultCode = e.NativeErrorCode;
|
||||
break;
|
||||
}
|
||||
case Win32Exception e:
|
||||
{
|
||||
string message = e.Message;
|
||||
Log.Fatal(message, e);
|
||||
context.ResultCode = e.NativeErrorCode;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
Log.Fatal("Unhandled exception", exception);
|
||||
error.WriteLine(exception.ToString());
|
||||
context.ResultCode = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.ResetColor();
|
||||
default:
|
||||
{
|
||||
Log.Fatal("Unhandled exception", exception);
|
||||
context.ResultCode = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -346,15 +331,14 @@ namespace WinSW
|
|||
return;
|
||||
}
|
||||
|
||||
Log.Info("Installing the service with id '" + config.Id + "'");
|
||||
Log.Info($"Installing service '{config.Format()}'...");
|
||||
|
||||
using ServiceManager scm = ServiceManager.Open();
|
||||
|
||||
if (scm.ServiceExists(config.Id))
|
||||
if (scm.ServiceExists(config.Name))
|
||||
{
|
||||
Console.WriteLine("Service with id '" + config.Id + "' already exists");
|
||||
Console.WriteLine("To install the service, delete the existing one or change service Id in the configuration file");
|
||||
throw new CommandException("Installation failure: Service with id '" + config.Id + "' already exists");
|
||||
Log.Error($"A service with ID '{config.Name}' already exists.");
|
||||
Throw.Command.Win32Exception(Errors.ERROR_SERVICE_EXISTS, "Failed to install the service.");
|
||||
}
|
||||
|
||||
if (config.HasServiceAccount())
|
||||
|
@ -371,7 +355,7 @@ namespace WinSW
|
|||
ref username,
|
||||
ref password,
|
||||
"Windows Service Wrapper",
|
||||
"service account credentials"); // TODO
|
||||
"Enter the service account credentials");
|
||||
break;
|
||||
|
||||
case "console":
|
||||
|
@ -387,10 +371,10 @@ namespace WinSW
|
|||
}
|
||||
|
||||
using Service sc = scm.CreateService(
|
||||
config.Id,
|
||||
config.Caption,
|
||||
config.Name,
|
||||
config.DisplayName,
|
||||
config.StartMode,
|
||||
"\"" + config.ExecutablePath + "\"" + (pathToConfig != null ? " \"" + Path.GetFullPath(pathToConfig) + "\"" : null),
|
||||
$"\"{config.ExecutablePath}\"" + (pathToConfig is null ? null : $" \"{Path.GetFullPath(pathToConfig)}\""),
|
||||
config.ServiceDependencies,
|
||||
username,
|
||||
password);
|
||||
|
@ -425,12 +409,14 @@ namespace WinSW
|
|||
sc.SetSecurityDescriptor(new RawSecurityDescriptor(securityDescriptor));
|
||||
}
|
||||
|
||||
string eventLogSource = config.Id;
|
||||
string eventLogSource = config.Name;
|
||||
if (!EventLog.SourceExists(eventLogSource))
|
||||
{
|
||||
EventLog.CreateEventSource(eventLogSource, "Application");
|
||||
}
|
||||
|
||||
Log.Info($"Service '{config.Format()}' was installed successfully.");
|
||||
|
||||
void PromptForCredentialsConsole()
|
||||
{
|
||||
if (username is null)
|
||||
|
@ -470,45 +456,46 @@ namespace WinSW
|
|||
return;
|
||||
}
|
||||
|
||||
Log.Info("Uninstalling the service with id '" + config.Id + "'");
|
||||
Log.Info($"Uninstalling service '{config.Format()}'...");
|
||||
|
||||
using ServiceManager scm = ServiceManager.Open();
|
||||
try
|
||||
{
|
||||
using Service sc = scm.OpenService(config.Id);
|
||||
using Service sc = scm.OpenService(config.Name);
|
||||
|
||||
if (sc.Status == ServiceControllerStatus.Running)
|
||||
if (sc.Status != ServiceControllerStatus.Stopped)
|
||||
{
|
||||
// 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 '" + config.Id + "' is running. It may be impossible to uninstall it");
|
||||
Log.Warn($"Service '{config.Format()}' is started. It may be impossible to uninstall it.");
|
||||
}
|
||||
|
||||
sc.Delete();
|
||||
|
||||
Log.Info($"Service '{config.Format()}' was uninstalled successfully.");
|
||||
}
|
||||
catch (CommandException e) when (e.InnerException is Win32Exception inner)
|
||||
{
|
||||
switch (inner.NativeErrorCode)
|
||||
{
|
||||
case Errors.ERROR_SERVICE_DOES_NOT_EXIST:
|
||||
Log.Warn("The service with id '" + config.Id + "' does not exist. Nothing to uninstall");
|
||||
Log.Warn($"Service '{config.Format()}' does not exist.");
|
||||
break; // there's no such service, so consider it already uninstalled
|
||||
|
||||
case Errors.ERROR_SERVICE_MARKED_FOR_DELETE:
|
||||
Log.Error("Failed to uninstall the service with id '" + config.Id + "'"
|
||||
+ ". It has been marked for deletion.");
|
||||
Log.Error(e.Message);
|
||||
|
||||
// TODO: change the default behavior to Error?
|
||||
break; // it's already uninstalled, so consider it a success
|
||||
|
||||
default:
|
||||
Log.Fatal("Failed to uninstall the service with id '" + config.Id + "'. Error code is '" + inner.NativeErrorCode + "'");
|
||||
throw;
|
||||
Throw.Command.Exception("Failed to uninstall the service.", inner);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Start(string? pathToConfig, bool noElevate)
|
||||
void Start(string? pathToConfig, bool noElevate, bool noWait, CancellationToken ct)
|
||||
{
|
||||
XmlServiceConfig config = XmlServiceConfig.Create(pathToConfig);
|
||||
InitLoggers(config, enableConsoleLogging: true);
|
||||
|
@ -519,27 +506,40 @@ namespace WinSW
|
|||
return;
|
||||
}
|
||||
|
||||
Log.Info("Starting the service with id '" + config.Id + "'");
|
||||
|
||||
using var svc = new ServiceController(config.Id);
|
||||
using var svc = new ServiceController(config.Name);
|
||||
|
||||
try
|
||||
{
|
||||
Log.Info($"Starting service '{svc.Format()}'...");
|
||||
svc.Start();
|
||||
|
||||
if (!noWait)
|
||||
{
|
||||
try
|
||||
{
|
||||
svc.WaitForStatus(ServiceControllerStatus.Running, ServiceControllerStatus.StartPending, ct);
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
Throw.Command.Exception("Failed to start the service.");
|
||||
}
|
||||
}
|
||||
|
||||
Log.Info($"Service '{svc.Format()}' started successfully.");
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
when (e.InnerException is Win32Exception inner && inner.NativeErrorCode == Errors.ERROR_SERVICE_DOES_NOT_EXIST)
|
||||
{
|
||||
ThrowNoSuchService(inner);
|
||||
Throw.Command.Exception(inner);
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
when (e.InnerException is Win32Exception inner && inner.NativeErrorCode == Errors.ERROR_SERVICE_ALREADY_RUNNING)
|
||||
{
|
||||
Log.Info($"The service with ID '{config.Id}' has already been started");
|
||||
Log.Info($"Service '{svc.Format()}' has already started.");
|
||||
}
|
||||
}
|
||||
|
||||
void Stop(string? pathToConfig, bool noElevate, bool noWait, bool force)
|
||||
void Stop(string? pathToConfig, bool noElevate, bool noWait, bool force, CancellationToken ct)
|
||||
{
|
||||
XmlServiceConfig config = XmlServiceConfig.Create(pathToConfig);
|
||||
InitLoggers(config, enableConsoleLogging: true);
|
||||
|
@ -550,9 +550,7 @@ namespace WinSW
|
|||
return;
|
||||
}
|
||||
|
||||
Log.Info("Stopping the service with id '" + config.Id + "'");
|
||||
|
||||
using var svc = new ServiceController(config.Id);
|
||||
using var svc = new ServiceController(config.Name);
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -560,39 +558,40 @@ namespace WinSW
|
|||
{
|
||||
if (svc.HasAnyStartedDependentService())
|
||||
{
|
||||
throw new CommandException("Failed to stop the service because it has started dependent services. Specify '--force' to proceed.");
|
||||
Throw.Command.Exception("Failed to stop the service because it has started dependent services. Specify '--force' to proceed.");
|
||||
}
|
||||
}
|
||||
|
||||
Log.Info($"Stopping service '{svc.Format()}'...");
|
||||
svc.Stop();
|
||||
|
||||
if (!noWait)
|
||||
{
|
||||
Log.Info("Waiting for the service to stop...");
|
||||
try
|
||||
{
|
||||
svc.WaitForStatus(ServiceControllerStatus.Stopped, ServiceControllerStatus.StopPending);
|
||||
svc.WaitForStatus(ServiceControllerStatus.Stopped, ServiceControllerStatus.StopPending, ct);
|
||||
}
|
||||
catch (TimeoutException e)
|
||||
catch (TimeoutException)
|
||||
{
|
||||
throw new CommandException("Failed to stop the service.", e);
|
||||
Throw.Command.Exception("Failed to stop the service.");
|
||||
}
|
||||
}
|
||||
|
||||
Log.Info($"Service '{svc.Format()}' stopped successfully.");
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
when (e.InnerException is Win32Exception inner && inner.NativeErrorCode == Errors.ERROR_SERVICE_DOES_NOT_EXIST)
|
||||
{
|
||||
ThrowNoSuchService(inner);
|
||||
Throw.Command.Exception(inner);
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
when (e.InnerException is Win32Exception inner && inner.NativeErrorCode == Errors.ERROR_SERVICE_NOT_ACTIVE)
|
||||
{
|
||||
Log.Info($"Service '{svc.Format()}' has already stopped.");
|
||||
}
|
||||
|
||||
Log.Info("The service stopped.");
|
||||
}
|
||||
|
||||
void Restart(string? pathToConfig, bool noElevate, bool force)
|
||||
void Restart(string? pathToConfig, bool noElevate, bool force, CancellationToken ct)
|
||||
{
|
||||
XmlServiceConfig config = XmlServiceConfig.Create(pathToConfig);
|
||||
InitLoggers(config, enableConsoleLogging: true);
|
||||
|
@ -603,9 +602,7 @@ namespace WinSW
|
|||
return;
|
||||
}
|
||||
|
||||
Log.Info("Restarting the service with id '" + config.Id + "'");
|
||||
|
||||
using var svc = new ServiceController(config.Id);
|
||||
using var svc = new ServiceController(config.Name);
|
||||
|
||||
List<ServiceController>? startedDependentServices = null;
|
||||
|
||||
|
@ -615,46 +612,59 @@ namespace WinSW
|
|||
{
|
||||
if (!force)
|
||||
{
|
||||
throw new CommandException("Failed to restart the service because it has started dependent services. Specify '--force' to proceed.");
|
||||
Throw.Command.Exception("Failed to restart the service because it has started dependent services. Specify '--force' to proceed.");
|
||||
}
|
||||
|
||||
startedDependentServices = svc.DependentServices.Where(service => service.Status != ServiceControllerStatus.Stopped).ToList();
|
||||
}
|
||||
|
||||
Log.Info($"Stopping service '{svc.Format()}'...");
|
||||
svc.Stop();
|
||||
|
||||
Log.Info("Waiting for the service to stop...");
|
||||
try
|
||||
{
|
||||
svc.WaitForStatus(ServiceControllerStatus.Stopped, ServiceControllerStatus.StopPending);
|
||||
svc.WaitForStatus(ServiceControllerStatus.Stopped, ServiceControllerStatus.StopPending, ct);
|
||||
}
|
||||
catch (TimeoutException e)
|
||||
catch (TimeoutException)
|
||||
{
|
||||
throw new CommandException("Failed to stop the service.", e);
|
||||
Throw.Command.Exception("Failed to stop the service.");
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
when (e.InnerException is Win32Exception inner && inner.NativeErrorCode == Errors.ERROR_SERVICE_DOES_NOT_EXIST)
|
||||
{
|
||||
ThrowNoSuchService(inner);
|
||||
Throw.Command.Exception(inner);
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
when (e.InnerException is Win32Exception inner && inner.NativeErrorCode == Errors.ERROR_SERVICE_NOT_ACTIVE)
|
||||
{
|
||||
}
|
||||
|
||||
Log.Info($"Starting service '{svc.Format()}'...");
|
||||
svc.Start();
|
||||
|
||||
try
|
||||
{
|
||||
svc.WaitForStatus(ServiceControllerStatus.Running, ServiceControllerStatus.StartPending, ct);
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
Throw.Command.Exception("Failed to start the service.");
|
||||
}
|
||||
|
||||
if (startedDependentServices != null)
|
||||
{
|
||||
foreach (ServiceController service in startedDependentServices)
|
||||
{
|
||||
if (service.Status == ServiceControllerStatus.Stopped)
|
||||
{
|
||||
Log.Info($"Starting service '{service.Format()}'...");
|
||||
service.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.Info($"Service '{svc.Format()}' restarted successfully.");
|
||||
}
|
||||
|
||||
void RestartSelf(string? pathToConfig)
|
||||
|
@ -664,34 +674,56 @@ namespace WinSW
|
|||
|
||||
if (!elevated)
|
||||
{
|
||||
throw new CommandException(new Win32Exception(Errors.ERROR_ACCESS_DENIED));
|
||||
Throw.Command.Win32Exception(Errors.ERROR_ACCESS_DENIED);
|
||||
}
|
||||
|
||||
Log.Info("Restarting the service with id '" + config.Id + "'");
|
||||
|
||||
// run restart from another process group. see README.md for why this is useful.
|
||||
|
||||
if (!ProcessApis.CreateProcess(null, config.ExecutablePath + " restart", IntPtr.Zero, IntPtr.Zero, false, ProcessApis.CREATE_NEW_PROCESS_GROUP, IntPtr.Zero, null, default, out _))
|
||||
if (!ProcessApis.CreateProcess(
|
||||
null,
|
||||
$"\"{config.ExecutablePath}\" restart" + (pathToConfig is null ? null : $" \"{pathToConfig}\""),
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero,
|
||||
false,
|
||||
ProcessApis.CREATE_NEW_PROCESS_GROUP,
|
||||
IntPtr.Zero,
|
||||
null,
|
||||
default,
|
||||
out _))
|
||||
{
|
||||
throw new CommandException("Failed to invoke restart: " + Marshal.GetLastWin32Error());
|
||||
Throw.Command.Win32Exception("Failed to invoke restart.");
|
||||
}
|
||||
}
|
||||
|
||||
static void Status(string? pathToConfig)
|
||||
static int Status(string? pathToConfig)
|
||||
{
|
||||
XmlServiceConfig config = XmlServiceConfig.Create(pathToConfig);
|
||||
InitLoggers(config, enableConsoleLogging: true);
|
||||
|
||||
Log.Debug("User requested the status of the process with id '" + config.Id + "'");
|
||||
using var svc = new ServiceController(config.Id);
|
||||
using var svc = new ServiceController(config.Name);
|
||||
try
|
||||
{
|
||||
Console.WriteLine(svc.Status == ServiceControllerStatus.Running ? "Started" : "Stopped");
|
||||
Console.WriteLine(svc.Status switch
|
||||
{
|
||||
ServiceControllerStatus.StartPending => "Starting",
|
||||
ServiceControllerStatus.StopPending => "Stopping",
|
||||
ServiceControllerStatus.Running => "Running",
|
||||
ServiceControllerStatus.ContinuePending => "Continuing",
|
||||
ServiceControllerStatus.PausePending => "Pausing",
|
||||
ServiceControllerStatus.Paused => "Paused",
|
||||
_ => "Stopped"
|
||||
});
|
||||
|
||||
return svc.Status switch
|
||||
{
|
||||
ServiceControllerStatus.Stopped => 0,
|
||||
_ => 1
|
||||
};
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
when (e.InnerException is Win32Exception inner && inner.NativeErrorCode == Errors.ERROR_SERVICE_DOES_NOT_EXIST)
|
||||
{
|
||||
Console.WriteLine("NonExistent");
|
||||
return Errors.ERROR_SERVICE_DOES_NOT_EXIST;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -730,6 +762,7 @@ namespace WinSW
|
|||
|
||||
void CancelKeyPress(object sender, ConsoleCancelEventArgs e)
|
||||
{
|
||||
e.Cancel = true;
|
||||
evt.Set();
|
||||
}
|
||||
}
|
||||
|
@ -754,9 +787,9 @@ namespace WinSW
|
|||
using ServiceManager scm = ServiceManager.Open();
|
||||
try
|
||||
{
|
||||
using Service sc = scm.OpenService(config.Id);
|
||||
using Service sc = scm.OpenService(config.Name);
|
||||
|
||||
sc.ChangeConfig(config.Caption, config.StartMode, config.ServiceDependencies);
|
||||
sc.ChangeConfig(config.DisplayName, config.StartMode, config.ServiceDependencies);
|
||||
|
||||
sc.SetDescription(config.Description);
|
||||
|
||||
|
@ -787,8 +820,10 @@ namespace WinSW
|
|||
catch (CommandException e)
|
||||
when (e.InnerException is Win32Exception inner && inner.NativeErrorCode == Errors.ERROR_SERVICE_DOES_NOT_EXIST)
|
||||
{
|
||||
ThrowNoSuchService(inner);
|
||||
Throw.Command.Exception(inner);
|
||||
}
|
||||
|
||||
Log.Info($"Service '{config.Format()}' was refreshed successfully.");
|
||||
}
|
||||
|
||||
void DevPs(string? pathToConfig, bool noElevate)
|
||||
|
@ -802,7 +837,7 @@ namespace WinSW
|
|||
}
|
||||
|
||||
using ServiceManager scm = ServiceManager.Open();
|
||||
using Service sc = scm.OpenService(config.Id);
|
||||
using Service sc = scm.OpenService(config.Name);
|
||||
|
||||
int processId = sc.ProcessId;
|
||||
if (processId >= 0)
|
||||
|
@ -848,7 +883,7 @@ namespace WinSW
|
|||
{
|
||||
if (noElevate)
|
||||
{
|
||||
throw new CommandException(new Win32Exception(Errors.ERROR_ACCESS_DENIED));
|
||||
Throw.Command.Win32Exception(Errors.ERROR_ACCESS_DENIED);
|
||||
}
|
||||
|
||||
using Process current = Process.GetCurrentProcess();
|
||||
|
@ -881,11 +916,6 @@ namespace WinSW
|
|||
}
|
||||
}
|
||||
|
||||
/// <exception cref="CommandException" />
|
||||
[DoesNotReturn]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowNoSuchService(Win32Exception inner) => throw new CommandException(inner);
|
||||
|
||||
private static void InitLoggers(XmlServiceConfig config, bool enableConsoleLogging)
|
||||
{
|
||||
if (XmlServiceConfig.TestConfig != null)
|
||||
|
@ -900,10 +930,6 @@ namespace WinSW
|
|||
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
|
||||
|
@ -916,7 +942,7 @@ namespace WinSW
|
|||
Name = "Wrapper file log",
|
||||
Threshold = fileLogLevel,
|
||||
LockingModel = new FileAppender.MinimalLock(),
|
||||
Layout = layout,
|
||||
Layout = new PatternLayout("%date %-5level - %message%newline"),
|
||||
};
|
||||
wrapperLog.ActivateOptions();
|
||||
appenders.Add(wrapperLog);
|
||||
|
@ -924,11 +950,11 @@ namespace WinSW
|
|||
// console log
|
||||
if (enableConsoleLogging)
|
||||
{
|
||||
var consoleAppender = new ConsoleAppender
|
||||
var consoleAppender = new WinSWConsoleAppender
|
||||
{
|
||||
Name = "Wrapper console log",
|
||||
Threshold = consoleLogLevel,
|
||||
Layout = layout,
|
||||
Layout = new PatternLayout("%date{ABSOLUTE} - %message%newline"),
|
||||
};
|
||||
consoleAppender.ActivateOptions();
|
||||
appenders.Add(consoleAppender);
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
using System;
|
||||
using System.ServiceProcess;
|
||||
using System.Threading;
|
||||
using TimeoutException = System.ServiceProcess.TimeoutException;
|
||||
|
||||
namespace WinSW
|
||||
{
|
||||
internal static class ServiceControllerExtension
|
||||
{
|
||||
/// <exception cref="OperationCanceledException" />
|
||||
/// <exception cref="TimeoutException" />
|
||||
internal static void WaitForStatus(this ServiceController serviceController, ServiceControllerStatus desiredStatus, ServiceControllerStatus pendingStatus)
|
||||
internal static void WaitForStatus(this ServiceController serviceController, ServiceControllerStatus desiredStatus, ServiceControllerStatus pendingStatus, CancellationToken ct)
|
||||
{
|
||||
TimeSpan timeout = TimeSpan.FromSeconds(1);
|
||||
TimeSpan timeout = new TimeSpan(TimeSpan.TicksPerSecond);
|
||||
for (; ; )
|
||||
{
|
||||
try
|
||||
|
@ -17,8 +19,10 @@ namespace WinSW
|
|||
serviceController.WaitForStatus(desiredStatus, timeout);
|
||||
break;
|
||||
}
|
||||
catch (TimeoutException) when (serviceController.Status == desiredStatus || serviceController.Status == pendingStatus)
|
||||
catch (TimeoutException)
|
||||
when (serviceController.Status == desiredStatus || serviceController.Status == pendingStatus)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ namespace WinSW
|
|||
|
||||
public WrapperService(XmlServiceConfig config)
|
||||
{
|
||||
this.ServiceName = config.Id;
|
||||
this.ServiceName = config.Name;
|
||||
this.CanStop = true;
|
||||
this.AutoLog = false;
|
||||
|
||||
|
@ -371,7 +371,7 @@ namespace WinSW
|
|||
}
|
||||
}
|
||||
|
||||
Log.Info("Stopping " + this.config.Id);
|
||||
Log.Info("Stopping " + this.config.Name);
|
||||
this.process.EnableRaisingEvents = false;
|
||||
|
||||
string? stopExecutable = this.config.StopExecutable;
|
||||
|
@ -441,7 +441,7 @@ namespace WinSW
|
|||
Console.Beep();
|
||||
}
|
||||
|
||||
Log.Info("Finished " + this.config.Id);
|
||||
Log.Info("Finished " + this.config.Name);
|
||||
|
||||
Process StartProcessLocked(string executable, string? arguments)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue