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