diff --git a/Main.cs b/Main.cs index 2c56a7d..44e39b2 100644 --- a/Main.cs +++ b/Main.cs @@ -10,6 +10,7 @@ using System.Threading; using Microsoft.Win32; using WMI; using ServiceType = WMI.ServiceType; +using System.Reflection; namespace winsw { @@ -28,9 +29,20 @@ namespace winsw private bool _orderlyShutdown; private bool _systemShuttingdown; - public WrapperService() + /// + /// Version of Windows service wrapper + /// + /// + /// The version will be taken from + /// + public static Version Version { - _descriptor = new ServiceDescriptor(); + get { return Assembly.GetExecutingAssembly().GetName().Version; } + } + + public WrapperService(ServiceDescriptor descriptor) + { + _descriptor = descriptor; ServiceName = _descriptor.Id; CanShutdown = true; CanStop = true; @@ -39,6 +51,10 @@ namespace winsw _systemShuttingdown = false; } + public WrapperService() : this (new ServiceDescriptor()) + { + } + /// /// Process the file copy instructions, so that we can replace files that are always in use while /// the service runs. @@ -511,11 +527,11 @@ namespace winsw } // ReSharper disable once InconsistentNaming - public static void Run(string[] _args) + public static void Run(string[] _args, ServiceDescriptor descriptor = null) { if (_args.Length > 0) { - var d = new ServiceDescriptor(); + var d = descriptor ?? new ServiceDescriptor(); Win32Services svc = new WmiRoot().GetCollection(); Win32Service s = svc.Select(d.Id); @@ -612,6 +628,7 @@ namespace winsw } } } + return; } if (args[0] == "uninstall") { @@ -627,16 +644,19 @@ namespace winsw return; // it's already uninstalled, so consider it a success throw e; } + return; } if (args[0] == "start") { if (s == null) ThrowNoSuchService(); s.StartService(); + return; } if (args[0] == "stop") { if (s == null) ThrowNoSuchService(); s.StopService(); + return; } if (args[0] == "restart") { @@ -653,6 +673,7 @@ namespace winsw } s.StartService(); + return; } if (args[0] == "restart!") { @@ -666,6 +687,7 @@ namespace winsw { throw new Exception("Failed to invoke restart: "+Marshal.GetLastWin32Error()); } + return; } if (args[0] == "status") { @@ -675,6 +697,7 @@ namespace winsw Console.WriteLine("Started"); else Console.WriteLine("Stopped"); + return; } if (args[0] == "test") { @@ -682,8 +705,24 @@ namespace winsw wsvc.OnStart(args.ToArray()); Thread.Sleep(1000); wsvc.OnStop(); + return; } - return; + if (args[0] == "help" || args[0] == "--help" || args[0] == "-h" + || args[0] == "-?" || args[0] == "/?") + { + printHelp(); + return; + } + if (args[0] == "version") + { + printVersion(); + return; + } + + Console.WriteLine("Unknown command: " + args[0]); + printAvailableCommandsInfo(); + throw new Exception("Unknown command: " + args[0]); + } Run(new WrapperService()); } @@ -711,5 +750,43 @@ namespace winsw } } } + + private static void printHelp() + { + Console.WriteLine("A wrapper binary that can be used to host executables as Windows services"); + Console.WriteLine(""); + Console.WriteLine("Usage: winsw [/redirect file] []"); + Console.WriteLine(" Missing arguments trigger the service mode"); + Console.WriteLine(""); + printAvailableCommandsInfo(); + Console.WriteLine(""); + Console.WriteLine("Extra options:"); + Console.WriteLine("- '/redirect' - redirect the wrapper's STDOUT and STDERR to the specified file"); + Console.WriteLine(""); + printVersion(); + Console.WriteLine("More info: https://github.com/kohsuke/winsw"); + Console.WriteLine("Bug tracker: https://github.com/kohsuke/winsw/issues"); + } + + //TODO: Rework to enum in winsw-2.0 + private static void printAvailableCommandsInfo() + { + Console.WriteLine("Available commands:"); + Console.WriteLine("- 'install' - install the service to Windows Service Controller"); + Console.WriteLine("- 'uninstall' - uninstall the service"); + Console.WriteLine("- 'start' - start the service (must be installed before)"); + Console.WriteLine("- 'stop' - stop the service"); + Console.WriteLine("- 'restart' - restart the service"); + Console.WriteLine("- 'restart!' - self-restart (can be called from child processes)"); + Console.WriteLine("- 'status' - check the current status of the service"); + Console.WriteLine("- 'test' - check if the service can be started and then stopped"); + Console.WriteLine("- 'version' - print the version info"); + Console.WriteLine("- 'help' - print the help info (aliases: -h,--help,-?,/?)"); + } + + private static void printVersion() + { + Console.WriteLine("WinSW " + Version); + } } } diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index 0a13fa9..1531349 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -28,5 +28,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.1.0.0")] -[assembly: AssemblyFileVersion("1.1.0.0")] +[assembly: AssemblyVersion("1.17.0.0")] +[assembly: AssemblyFileVersion("1.17.0.0")] diff --git a/Tests/winswTests/MainTest.cs b/Tests/winswTests/MainTest.cs new file mode 100644 index 0000000..6bf8402 --- /dev/null +++ b/Tests/winswTests/MainTest.cs @@ -0,0 +1,48 @@ +using NUnit.Framework; +using winsw; +using winswTests.Util; + +namespace winswTests +{ + [TestFixture] + class MainTest + { + + [Test] + public void PrintVersion() + { + string expectedVersion = WrapperService.Version.ToString(); + string cliOut = CLITestHelper.CLITest(new[] { "version" }); + StringAssert.Contains(expectedVersion, cliOut, "Expected that version contains " + expectedVersion); + } + + [Test] + public void PrintHelp() + { + string expectedVersion = WrapperService.Version.ToString(); + string cliOut = CLITestHelper.CLITest(new[] { "help" }); + + StringAssert.Contains(expectedVersion, cliOut, "Expected that help contains " + expectedVersion); + StringAssert.Contains("start", cliOut, "Expected that help refers start command"); + StringAssert.Contains("help", cliOut, "Expected that help refers help command"); + StringAssert.Contains("version", cliOut, "Expected that help refers version command"); + //TODO: check all commands after the migration of ccommands to enum + + // Extra options + StringAssert.Contains("/redirect", cliOut, "Expected that help message refers the redirect message"); + } + + [Test] + public void FailOnUnsupportedCommand() + { + const string commandName = "nonExistentCommand"; + string expectedMessage = "Unknown command: " + commandName.ToLower(); + CLITestResult res = CLITestHelper.CLIErrorTest(new[] {commandName}); + + Assert.True(res.HasException, "Expected an exception due to the wrong command"); + StringAssert.Contains(expectedMessage, res.Out, "Expected the message about unknown command"); + // ReSharper disable once PossibleNullReferenceException + StringAssert.Contains(expectedMessage, res.Exception.Message, "Expected the message about unknown command"); + } + } +} diff --git a/Tests/winswTests/Util/CLITestHelper.cs b/Tests/winswTests/Util/CLITestHelper.cs new file mode 100644 index 0000000..193c6b7 --- /dev/null +++ b/Tests/winswTests/Util/CLITestHelper.cs @@ -0,0 +1,116 @@ +using System; +using System.IO; +using JetBrains.Annotations; +using winsw; + +namespace winswTests.Util +{ + /// + /// Helper for WinSW CLI testing + /// + public static class CLITestHelper + { + private const string SeedXml = "" + + "service.exe" + + "Service" + + "The service." + + "node.exe" + + "My Arguments" + + "rotate" + + "" + + @"C:\winsw\workdir" + + "" + + @"C:\winsw\logs" + + ""; + private static readonly ServiceDescriptor DefaultServiceDescriptor = ServiceDescriptor.FromXML(SeedXml); + + /// + /// Runs a simle test, which returns the output CLI + /// + /// CLI arguments to be passed + /// Optional Service descriptor (will be used for initializationpurposes) + /// STDOUT if there's no exceptions + /// Command failure + [NotNull] + public static string CLITest(String[] args, ServiceDescriptor descriptor = null) + { + using (StringWriter sw = new StringWriter()) + { + TextWriter tmp = Console.Out; + Console.SetOut(sw); + WrapperService.Run(args, descriptor ?? DefaultServiceDescriptor); + Console.SetOut(tmp); + Console.Write(sw.ToString()); + return sw.ToString(); + } + } + + /// + /// Runs a simle test, which returns the output CLI + /// + /// CLI arguments to be passed + /// Optional Service descriptor (will be used for initializationpurposes) + /// Test results + [NotNull] + public static CLITestResult CLIErrorTest(String[] args, ServiceDescriptor descriptor = null) + { + StringWriter swOut, swErr; + Exception testEx = null; + TextWriter tmpOut = Console.Out; + TextWriter tmpErr = Console.Error; + + using (swOut = new StringWriter()) + using (swErr = new StringWriter()) + try + { + Console.SetOut(swOut); + Console.SetError(swErr); + WrapperService.Run(args, descriptor ?? DefaultServiceDescriptor); + } + catch (Exception ex) + { + testEx = ex; + } + finally + { + Console.SetOut(tmpOut); + Console.SetError(tmpErr); + Console.WriteLine("\n>>> Output: "); + 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); + } + } + + /// + /// Aggregated test report + /// + public class CLITestResult + { + [NotNull] + public String Out { get; private set; } + + [NotNull] + public String Err { get; private set; } + + [CanBeNull] + public Exception Exception { get; private set; } + + public bool HasException { get { return Exception != null; } } + + public CLITestResult(String output, String err, Exception exception = null) + { + Out = output; + Err = err; + Exception = exception; + } + } +} diff --git a/Tests/winswTests/packages.config b/Tests/winswTests/packages.config new file mode 100644 index 0000000..e38473a --- /dev/null +++ b/Tests/winswTests/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Tests/winswTests/winswTests.csproj b/Tests/winswTests/winswTests.csproj index 9e37360..5992f0f 100644 --- a/Tests/winswTests/winswTests.csproj +++ b/Tests/winswTests/winswTests.csproj @@ -36,17 +36,24 @@ 4 + + False + ..\..\packages\JetBrains.Annotations.8.0.5.0\lib\net20\JetBrains.Annotations.dll + False ..\Lib\nunit.framework.dll + + + @@ -54,6 +61,9 @@ winsw + + +