mirror of https://github.com/winsw/winsw
New command-line interface
parent
40b566d330
commit
3ea68e2d20
|
@ -1,3 +1,4 @@
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
[assembly: InternalsVisibleTo("WinSW")]
|
[assembly: InternalsVisibleTo("WinSW")]
|
||||||
|
[assembly: InternalsVisibleTo("WinSW.Tests")]
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using static WinSW.Native.CredentialApis;
|
||||||
|
|
||||||
|
namespace WinSW.Native
|
||||||
|
{
|
||||||
|
internal static class Credentials
|
||||||
|
{
|
||||||
|
internal static void PropmtForCredentialsDialog(ref string? userName, ref string? password, string caption, string message)
|
||||||
|
{
|
||||||
|
userName ??= string.Empty;
|
||||||
|
password ??= string.Empty;
|
||||||
|
|
||||||
|
int inBufferSize = 0;
|
||||||
|
_ = CredPackAuthenticationBuffer(
|
||||||
|
0,
|
||||||
|
userName,
|
||||||
|
password,
|
||||||
|
IntPtr.Zero,
|
||||||
|
ref inBufferSize);
|
||||||
|
|
||||||
|
IntPtr inBuffer = Marshal.AllocCoTaskMem(inBufferSize);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!CredPackAuthenticationBuffer(
|
||||||
|
0,
|
||||||
|
userName,
|
||||||
|
password,
|
||||||
|
inBuffer,
|
||||||
|
ref inBufferSize))
|
||||||
|
{
|
||||||
|
Throw.Command.Win32Exception("Failed to pack auth buffer.");
|
||||||
|
}
|
||||||
|
|
||||||
|
CREDUI_INFO info = new CREDUI_INFO
|
||||||
|
{
|
||||||
|
Size = Marshal.SizeOf(typeof(CREDUI_INFO)),
|
||||||
|
CaptionText = caption,
|
||||||
|
MessageText = message,
|
||||||
|
};
|
||||||
|
uint authPackage = 0;
|
||||||
|
bool save = false;
|
||||||
|
int error = CredUIPromptForWindowsCredentials(
|
||||||
|
info,
|
||||||
|
0,
|
||||||
|
ref authPackage,
|
||||||
|
inBuffer,
|
||||||
|
inBufferSize,
|
||||||
|
out IntPtr outBuffer,
|
||||||
|
out uint outBufferSize,
|
||||||
|
ref save,
|
||||||
|
CREDUIWIN_GENERIC);
|
||||||
|
|
||||||
|
if (error != Errors.ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
throw new Win32Exception(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int userNameLength = 0;
|
||||||
|
int passwordLength = 0;
|
||||||
|
_ = CredUnPackAuthenticationBuffer(
|
||||||
|
0,
|
||||||
|
outBuffer,
|
||||||
|
outBufferSize,
|
||||||
|
null,
|
||||||
|
ref userNameLength,
|
||||||
|
default,
|
||||||
|
default,
|
||||||
|
null,
|
||||||
|
ref passwordLength);
|
||||||
|
|
||||||
|
userName = userNameLength == 0 ? null : new string('\0', userNameLength - 1);
|
||||||
|
password = passwordLength == 0 ? null : new string('\0', passwordLength - 1);
|
||||||
|
|
||||||
|
if (!CredUnPackAuthenticationBuffer(
|
||||||
|
0,
|
||||||
|
outBuffer,
|
||||||
|
outBufferSize,
|
||||||
|
userName,
|
||||||
|
ref userNameLength,
|
||||||
|
default,
|
||||||
|
default,
|
||||||
|
password,
|
||||||
|
ref passwordLength))
|
||||||
|
{
|
||||||
|
Throw.Command.Win32Exception("Failed to unpack auth buffer.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Marshal.FreeCoTaskMem(outBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Marshal.FreeCoTaskMem(inBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -76,24 +76,6 @@ namespace WinSW.Native
|
||||||
string? username,
|
string? username,
|
||||||
string? password)
|
string? password)
|
||||||
{
|
{
|
||||||
int arrayLength = 1;
|
|
||||||
for (int i = 0; i < dependencies.Length; i++)
|
|
||||||
{
|
|
||||||
arrayLength += dependencies[i].Length + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder? array = null;
|
|
||||||
if (dependencies.Length != 0)
|
|
||||||
{
|
|
||||||
array = new StringBuilder(arrayLength);
|
|
||||||
for (int i = 0; i < dependencies.Length; i++)
|
|
||||||
{
|
|
||||||
_ = array.Append(dependencies[i]).Append('\0');
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = array.Append('\0');
|
|
||||||
}
|
|
||||||
|
|
||||||
IntPtr handle = ServiceApis.CreateService(
|
IntPtr handle = ServiceApis.CreateService(
|
||||||
this.handle,
|
this.handle,
|
||||||
serviceName,
|
serviceName,
|
||||||
|
@ -105,7 +87,7 @@ namespace WinSW.Native
|
||||||
executablePath,
|
executablePath,
|
||||||
default,
|
default,
|
||||||
default,
|
default,
|
||||||
array,
|
Service.GetNativeDependencies(dependencies),
|
||||||
username,
|
username,
|
||||||
password);
|
password);
|
||||||
if (handle == IntPtr.Zero)
|
if (handle == IntPtr.Zero)
|
||||||
|
@ -171,6 +153,29 @@ namespace WinSW.Native
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static StringBuilder? GetNativeDependencies(string[] dependencies)
|
||||||
|
{
|
||||||
|
int arrayLength = 1;
|
||||||
|
for (int i = 0; i < dependencies.Length; i++)
|
||||||
|
{
|
||||||
|
arrayLength += dependencies[i].Length + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder? array = null;
|
||||||
|
if (dependencies.Length != 0)
|
||||||
|
{
|
||||||
|
array = new StringBuilder(arrayLength);
|
||||||
|
for (int i = 0; i < dependencies.Length; i++)
|
||||||
|
{
|
||||||
|
_ = array.Append(dependencies[i]).Append('\0');
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = array.Append('\0');
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
/// <exception cref="CommandException" />
|
/// <exception cref="CommandException" />
|
||||||
internal void SetStatus(IntPtr statusHandle, ServiceControllerStatus state)
|
internal void SetStatus(IntPtr statusHandle, ServiceControllerStatus state)
|
||||||
{
|
{
|
||||||
|
@ -192,7 +197,8 @@ namespace WinSW.Native
|
||||||
/// <exception cref="CommandException" />
|
/// <exception cref="CommandException" />
|
||||||
internal void ChangeConfig(
|
internal void ChangeConfig(
|
||||||
string displayName,
|
string displayName,
|
||||||
ServiceStartMode startMode)
|
ServiceStartMode startMode,
|
||||||
|
string[] dependencies)
|
||||||
{
|
{
|
||||||
if (!ChangeServiceConfig(
|
if (!ChangeServiceConfig(
|
||||||
this.handle,
|
this.handle,
|
||||||
|
@ -202,7 +208,7 @@ namespace WinSW.Native
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
IntPtr.Zero,
|
IntPtr.Zero,
|
||||||
null,
|
GetNativeDependencies(dependencies),
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
displayName))
|
displayName))
|
||||||
|
|
|
@ -21,6 +21,8 @@ namespace WinSW
|
||||||
|
|
||||||
private readonly Dictionary<string, string> environmentVariables;
|
private readonly Dictionary<string, string> environmentVariables;
|
||||||
|
|
||||||
|
internal static ServiceDescriptor? TestDescriptor;
|
||||||
|
|
||||||
public static DefaultWinSWSettings Defaults { get; } = new DefaultWinSWSettings();
|
public static DefaultWinSWSettings Defaults { get; } = new DefaultWinSWSettings();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -42,34 +44,17 @@ namespace WinSW
|
||||||
|
|
||||||
public ServiceDescriptor()
|
public ServiceDescriptor()
|
||||||
{
|
{
|
||||||
// find co-located configuration xml. We search up to the ancestor directories to simplify debugging,
|
string path = this.ExecutablePath;
|
||||||
// as well as trimming off ".vshost" suffix (which is used during debugging)
|
string baseName = Path.GetFileNameWithoutExtension(path);
|
||||||
// Get the first parent to go into the recursive loop
|
string baseDir = Path.GetDirectoryName(path)!;
|
||||||
string p = this.ExecutablePath;
|
|
||||||
string baseName = Path.GetFileNameWithoutExtension(p);
|
|
||||||
if (baseName.EndsWith(".vshost"))
|
|
||||||
{
|
|
||||||
baseName = baseName.Substring(0, baseName.Length - 7);
|
|
||||||
}
|
|
||||||
|
|
||||||
DirectoryInfo d = new DirectoryInfo(Path.GetDirectoryName(p));
|
if (!File.Exists(Path.Combine(baseDir, baseName + ".xml")))
|
||||||
while (true)
|
|
||||||
{
|
{
|
||||||
if (File.Exists(Path.Combine(d.FullName, baseName + ".xml")))
|
throw new FileNotFoundException("Unable to locate " + baseName + ".xml file within executable directory");
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (d.Parent is null)
|
|
||||||
{
|
|
||||||
throw new FileNotFoundException("Unable to locate " + baseName + ".xml file within executable directory or any parents");
|
|
||||||
}
|
|
||||||
|
|
||||||
d = d.Parent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.BaseName = baseName;
|
this.BaseName = baseName;
|
||||||
this.BasePath = Path.Combine(d.FullName, this.BaseName);
|
this.BasePath = Path.Combine(baseDir, baseName);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -81,7 +66,45 @@ namespace WinSW
|
||||||
}
|
}
|
||||||
|
|
||||||
// register the base directory as environment variable so that future expansions can refer to this.
|
// register the base directory as environment variable so that future expansions can refer to this.
|
||||||
Environment.SetEnvironmentVariable("BASE", d.FullName);
|
Environment.SetEnvironmentVariable("BASE", baseDir);
|
||||||
|
|
||||||
|
// ditto for ID
|
||||||
|
Environment.SetEnvironmentVariable("SERVICE_ID", this.Id);
|
||||||
|
|
||||||
|
// New name
|
||||||
|
Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameExecutablePath, this.ExecutablePath);
|
||||||
|
|
||||||
|
// Also inject system environment variables
|
||||||
|
Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameServiceId, this.Id);
|
||||||
|
|
||||||
|
this.environmentVariables = this.LoadEnvironmentVariables();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <exception cref="FileNotFoundException" />
|
||||||
|
public ServiceDescriptor(string path)
|
||||||
|
{
|
||||||
|
if (!File.Exists(path))
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException(null, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
string baseName = Path.GetFileNameWithoutExtension(path);
|
||||||
|
string baseDir = Path.GetDirectoryName(Path.GetFullPath(path))!;
|
||||||
|
|
||||||
|
this.BaseName = baseName;
|
||||||
|
this.BasePath = Path.Combine(baseDir, baseName);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.dom.Load(path);
|
||||||
|
}
|
||||||
|
catch (XmlException e)
|
||||||
|
{
|
||||||
|
throw new InvalidDataException(e.Message, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// register the base directory as environment variable so that future expansions can refer to this.
|
||||||
|
Environment.SetEnvironmentVariable("BASE", baseDir);
|
||||||
|
|
||||||
// ditto for ID
|
// ditto for ID
|
||||||
Environment.SetEnvironmentVariable("SERVICE_ID", this.Id);
|
Environment.SetEnvironmentVariable("SERVICE_ID", this.Id);
|
||||||
|
@ -107,6 +130,11 @@ namespace WinSW
|
||||||
this.environmentVariables = this.LoadEnvironmentVariables();
|
this.environmentVariables = this.LoadEnvironmentVariables();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static ServiceDescriptor Create(string? path)
|
||||||
|
{
|
||||||
|
return path != null ? new ServiceDescriptor(path) : TestDescriptor ?? new ServiceDescriptor();
|
||||||
|
}
|
||||||
|
|
||||||
public static ServiceDescriptor FromXml(string xml)
|
public static ServiceDescriptor FromXml(string xml)
|
||||||
{
|
{
|
||||||
var dom = new XmlDocument();
|
var dom = new XmlDocument();
|
||||||
|
|
|
@ -4,9 +4,9 @@ using Xunit;
|
||||||
namespace WinSW.Tests
|
namespace WinSW.Tests
|
||||||
{
|
{
|
||||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
|
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
|
||||||
internal sealed class ElevatedFactAttribute : FactAttribute
|
public sealed class ElevatedFactAttribute : FactAttribute
|
||||||
{
|
{
|
||||||
internal ElevatedFactAttribute()
|
public ElevatedFactAttribute()
|
||||||
{
|
{
|
||||||
if (!Program.IsProcessElevated())
|
if (!Program.IsProcessElevated())
|
||||||
{
|
{
|
||||||
|
|
|
@ -12,10 +12,10 @@ namespace WinSW.Tests
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_ = CLITestHelper.CLITest(new[] { "install" });
|
_ = CommandLineTestHelper.Test(new[] { "install" });
|
||||||
|
|
||||||
using ServiceController controller = new ServiceController(CLITestHelper.Id);
|
using ServiceController controller = new ServiceController(CommandLineTestHelper.Id);
|
||||||
Assert.Equal(CLITestHelper.Name, controller.DisplayName);
|
Assert.Equal(CommandLineTestHelper.Name, controller.DisplayName);
|
||||||
Assert.False(controller.CanStop);
|
Assert.False(controller.CanStop);
|
||||||
Assert.False(controller.CanShutdown);
|
Assert.False(controller.CanShutdown);
|
||||||
Assert.False(controller.CanPauseAndContinue);
|
Assert.False(controller.CanPauseAndContinue);
|
||||||
|
@ -24,42 +24,18 @@ namespace WinSW.Tests
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_ = CLITestHelper.CLITest(new[] { "uninstall" });
|
_ = CommandLineTestHelper.Test(new[] { "uninstall" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void PrintVersion()
|
public void FailOnUnknownCommand()
|
||||||
{
|
{
|
||||||
string expectedVersion = WrapperService.Version.ToString();
|
const string commandName = "unknown";
|
||||||
string cliOut = CLITestHelper.CLITest(new[] { "version" });
|
|
||||||
Assert.Contains(expectedVersion, cliOut);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
CommandLineTestResult result = CommandLineTestHelper.ErrorTest(new[] { commandName });
|
||||||
public void PrintHelp()
|
|
||||||
{
|
|
||||||
string expectedVersion = WrapperService.Version.ToString();
|
|
||||||
string cliOut = CLITestHelper.CLITest(new[] { "help" });
|
|
||||||
|
|
||||||
Assert.Contains(expectedVersion, cliOut);
|
Assert.Equal($"Unrecognized command or argument '{commandName}'\r\n\r\n", result.Error);
|
||||||
Assert.Contains("start", cliOut);
|
|
||||||
Assert.Contains("help", cliOut);
|
|
||||||
Assert.Contains("version", cliOut);
|
|
||||||
|
|
||||||
// TODO: check all commands after the migration of ccommands to enum
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void FailOnUnsupportedCommand()
|
|
||||||
{
|
|
||||||
const string commandName = "nonExistentCommand";
|
|
||||||
string expectedMessage = "Unknown command: " + commandName;
|
|
||||||
CLITestResult result = CLITestHelper.CLIErrorTest(new[] { commandName });
|
|
||||||
|
|
||||||
Assert.True(result.HasException);
|
|
||||||
Assert.Contains(expectedMessage, result.Out);
|
|
||||||
Assert.Contains(expectedMessage, result.Exception.Message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -68,7 +44,7 @@ namespace WinSW.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ShouldNotPrintLogsForStatusCommand()
|
public void ShouldNotPrintLogsForStatusCommand()
|
||||||
{
|
{
|
||||||
string cliOut = CLITestHelper.CLITest(new[] { "status" });
|
string cliOut = CommandLineTestHelper.Test(new[] { "status" });
|
||||||
Assert.Equal("NonExistent" + Environment.NewLine, cliOut);
|
Assert.Equal("NonExistent" + Environment.NewLine, cliOut);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ namespace WinSW.Tests.Util
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Helper for WinSW CLI testing
|
/// Helper for WinSW CLI testing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class CLITestHelper
|
public static class CommandLineTestHelper
|
||||||
{
|
{
|
||||||
public const string Id = "WinSW.Tests";
|
public const string Id = "WinSW.Tests";
|
||||||
public const string Name = "WinSW Test Service";
|
public const string Name = "WinSW Test Service";
|
||||||
|
@ -33,28 +33,29 @@ $@"<service>
|
||||||
/// <param name="descriptor">Optional Service descriptor (will be used for initializationpurposes)</param>
|
/// <param name="descriptor">Optional Service descriptor (will be used for initializationpurposes)</param>
|
||||||
/// <returns>STDOUT if there's no exceptions</returns>
|
/// <returns>STDOUT if there's no exceptions</returns>
|
||||||
/// <exception cref="Exception">Command failure</exception>
|
/// <exception cref="Exception">Command failure</exception>
|
||||||
public static string CLITest(string[] arguments, ServiceDescriptor descriptor = null)
|
public static string Test(string[] arguments, ServiceDescriptor descriptor = null)
|
||||||
{
|
{
|
||||||
TextWriter tmpOut = Console.Out;
|
TextWriter tmpOut = Console.Out;
|
||||||
TextWriter tmpErr = Console.Error;
|
TextWriter tmpError = Console.Error;
|
||||||
|
|
||||||
using StringWriter swOut = new StringWriter();
|
using StringWriter swOut = new StringWriter();
|
||||||
using StringWriter swErr = new StringWriter();
|
using StringWriter swError = new StringWriter();
|
||||||
|
|
||||||
Console.SetOut(swOut);
|
Console.SetOut(swOut);
|
||||||
Console.SetError(swErr);
|
Console.SetError(swError);
|
||||||
|
ServiceDescriptor.TestDescriptor = descriptor ?? DefaultServiceDescriptor;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Program.Run(arguments, descriptor ?? DefaultServiceDescriptor);
|
_ = Program.Run(arguments);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
Console.SetOut(tmpOut);
|
Console.SetOut(tmpOut);
|
||||||
Console.SetError(tmpErr);
|
Console.SetError(tmpError);
|
||||||
|
ServiceDescriptor.TestDescriptor = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.Equal(0, swErr.GetStringBuilder().Length);
|
Assert.Equal(string.Empty, swError.ToString());
|
||||||
Console.Write(swOut.ToString());
|
|
||||||
return swOut.ToString();
|
return swOut.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,49 +65,44 @@ $@"<service>
|
||||||
/// <param name="arguments">CLI arguments to be passed</param>
|
/// <param name="arguments">CLI arguments to be passed</param>
|
||||||
/// <param name="descriptor">Optional Service descriptor (will be used for initializationpurposes)</param>
|
/// <param name="descriptor">Optional Service descriptor (will be used for initializationpurposes)</param>
|
||||||
/// <returns>Test results</returns>
|
/// <returns>Test results</returns>
|
||||||
public static CLITestResult CLIErrorTest(string[] arguments, ServiceDescriptor descriptor = null)
|
public static CommandLineTestResult ErrorTest(string[] arguments, ServiceDescriptor descriptor = null)
|
||||||
{
|
{
|
||||||
Exception testEx = null;
|
Exception exception = null;
|
||||||
|
|
||||||
TextWriter tmpOut = Console.Out;
|
TextWriter tmpOut = Console.Out;
|
||||||
TextWriter tmpErr = Console.Error;
|
TextWriter tmpError = Console.Error;
|
||||||
|
|
||||||
using StringWriter swOut = new StringWriter();
|
using StringWriter swOut = new StringWriter();
|
||||||
using StringWriter swErr = new StringWriter();
|
using StringWriter swError = new StringWriter();
|
||||||
|
|
||||||
Console.SetOut(swOut);
|
Console.SetOut(swOut);
|
||||||
Console.SetError(swErr);
|
Console.SetError(swError);
|
||||||
|
ServiceDescriptor.TestDescriptor = descriptor ?? DefaultServiceDescriptor;
|
||||||
|
Program.TestExceptionHandler = (e, _) => exception = e;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Program.Run(arguments, descriptor ?? DefaultServiceDescriptor);
|
_ = Program.Run(arguments);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
testEx = ex;
|
exception = e;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
Console.SetOut(tmpOut);
|
Console.SetOut(tmpOut);
|
||||||
Console.SetError(tmpErr);
|
Console.SetError(tmpError);
|
||||||
|
ServiceDescriptor.TestDescriptor = null;
|
||||||
|
Program.TestExceptionHandler = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.WriteLine("\n>>> Output: ");
|
return new CommandLineTestResult(swOut.ToString(), swError.ToString(), exception);
|
||||||
Console.Write(swOut.ToString());
|
|
||||||
Console.WriteLine("\n>>> Error: ");
|
|
||||||
Console.Write(swErr.ToString());
|
|
||||||
if (testEx != null)
|
|
||||||
{
|
|
||||||
Console.WriteLine("\n>>> Exception: ");
|
|
||||||
Console.WriteLine(testEx);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new CLITestResult(swOut.ToString(), swErr.ToString(), testEx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Aggregated test report
|
/// Aggregated test report
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CLITestResult
|
public class CommandLineTestResult
|
||||||
{
|
{
|
||||||
public string Out { get; }
|
public string Out { get; }
|
||||||
|
|
||||||
|
@ -116,7 +112,7 @@ $@"<service>
|
||||||
|
|
||||||
public bool HasException => this.Exception != null;
|
public bool HasException => this.Exception != null;
|
||||||
|
|
||||||
public CLITestResult(string output, string error, Exception exception = null)
|
public CommandLineTestResult(string output, string error, Exception exception = null)
|
||||||
{
|
{
|
||||||
this.Out = output;
|
this.Out = output;
|
||||||
this.Error = error;
|
this.Error = error;
|
File diff suppressed because it is too large
Load Diff
|
@ -6,17 +6,26 @@ namespace WinSW
|
||||||
{
|
{
|
||||||
internal static class ServiceControllerExtension
|
internal static class ServiceControllerExtension
|
||||||
{
|
{
|
||||||
internal static bool TryWaitForStatus(/*this*/ ServiceController serviceController, ServiceControllerStatus desiredStatus, TimeSpan timeout)
|
/// <exception cref="TimeoutException" />
|
||||||
|
internal static void WaitForStatus(this ServiceController serviceController, ServiceControllerStatus desiredStatus, ServiceControllerStatus pendingStatus)
|
||||||
|
{
|
||||||
|
TimeSpan timeout = TimeSpan.FromSeconds(1);
|
||||||
|
for (; ; )
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
serviceController.WaitForStatus(desiredStatus, timeout);
|
serviceController.WaitForStatus(desiredStatus, timeout);
|
||||||
return true;
|
break;
|
||||||
}
|
}
|
||||||
catch (TimeoutException)
|
catch (TimeoutException) when (serviceController.Status == desiredStatus || serviceController.Status == pendingStatus)
|
||||||
{
|
{
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static bool HasAnyStartedDependentService(this ServiceController serviceController)
|
||||||
|
{
|
||||||
|
return Array.Exists(serviceController.DependentServices, service => service.Status != ServiceControllerStatus.Stopped);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace WinSW
|
|
||||||
{
|
|
||||||
internal sealed class UserException : Exception
|
|
||||||
{
|
|
||||||
internal UserException(string message)
|
|
||||||
: base(message)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
internal UserException(string? message, Exception inner)
|
|
||||||
: base(message, inner)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -19,6 +19,10 @@
|
||||||
<ILMergeVersion>3.0.40</ILMergeVersion>
|
<ILMergeVersion>3.0.40</ILMergeVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20303.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1'">
|
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1'">
|
||||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="4.7.0" />
|
<PackageReference Include="System.ServiceProcess.ServiceController" Version="4.7.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -59,6 +63,7 @@
|
||||||
<InputAssemblies>$(InputAssemblies) "$(OutDir)WinSW.Core.dll"</InputAssemblies>
|
<InputAssemblies>$(InputAssemblies) "$(OutDir)WinSW.Core.dll"</InputAssemblies>
|
||||||
<InputAssemblies>$(InputAssemblies) "$(OutDir)WinSW.Plugins.dll"</InputAssemblies>
|
<InputAssemblies>$(InputAssemblies) "$(OutDir)WinSW.Plugins.dll"</InputAssemblies>
|
||||||
<InputAssemblies>$(InputAssemblies) "$(OutDir)log4net.dll"</InputAssemblies>
|
<InputAssemblies>$(InputAssemblies) "$(OutDir)log4net.dll"</InputAssemblies>
|
||||||
|
<InputAssemblies>$(InputAssemblies) "$(OutDir)System.CommandLine.dll"</InputAssemblies>
|
||||||
<OutputAssembly>"$(ArtifactsDir)WinSW.$(TargetFrameworkSuffix).exe"</OutputAssembly>
|
<OutputAssembly>"$(ArtifactsDir)WinSW.$(TargetFrameworkSuffix).exe"</OutputAssembly>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue