Merge pull request #81 from oleg-nenashev/winsw-help-commands

Add "version" and "help", handle unsupported commands
pull/90/head
Oleg Nenashev 2015-02-03 00:07:40 +03:00
commit 95c9c23f59
7 changed files with 266 additions and 7 deletions

87
Main.cs
View File

@ -10,6 +10,7 @@ using System.Threading;
using Microsoft.Win32; using Microsoft.Win32;
using WMI; using WMI;
using ServiceType = WMI.ServiceType; using ServiceType = WMI.ServiceType;
using System.Reflection;
namespace winsw namespace winsw
{ {
@ -28,9 +29,20 @@ namespace winsw
private bool _orderlyShutdown; private bool _orderlyShutdown;
private bool _systemShuttingdown; private bool _systemShuttingdown;
public WrapperService() /// <summary>
/// Version of Windows service wrapper
/// </summary>
/// <remarks>
/// The version will be taken from <see cref="AssemblyInfo"/>
/// </remarks>
public static Version Version
{ {
_descriptor = new ServiceDescriptor(); get { return Assembly.GetExecutingAssembly().GetName().Version; }
}
public WrapperService(ServiceDescriptor descriptor)
{
_descriptor = descriptor;
ServiceName = _descriptor.Id; ServiceName = _descriptor.Id;
CanShutdown = true; CanShutdown = true;
CanStop = true; CanStop = true;
@ -39,6 +51,10 @@ namespace winsw
_systemShuttingdown = false; _systemShuttingdown = false;
} }
public WrapperService() : this (new ServiceDescriptor())
{
}
/// <summary> /// <summary>
/// Process the file copy instructions, so that we can replace files that are always in use while /// Process the file copy instructions, so that we can replace files that are always in use while
/// the service runs. /// the service runs.
@ -511,11 +527,11 @@ namespace winsw
} }
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
public static void Run(string[] _args) public static void Run(string[] _args, ServiceDescriptor descriptor = null)
{ {
if (_args.Length > 0) if (_args.Length > 0)
{ {
var d = new ServiceDescriptor(); var d = descriptor ?? new ServiceDescriptor();
Win32Services svc = new WmiRoot().GetCollection<Win32Services>(); Win32Services svc = new WmiRoot().GetCollection<Win32Services>();
Win32Service s = svc.Select(d.Id); Win32Service s = svc.Select(d.Id);
@ -612,6 +628,7 @@ namespace winsw
} }
} }
} }
return;
} }
if (args[0] == "uninstall") if (args[0] == "uninstall")
{ {
@ -627,16 +644,19 @@ namespace winsw
return; // it's already uninstalled, so consider it a success return; // it's already uninstalled, so consider it a success
throw e; throw e;
} }
return;
} }
if (args[0] == "start") if (args[0] == "start")
{ {
if (s == null) ThrowNoSuchService(); if (s == null) ThrowNoSuchService();
s.StartService(); s.StartService();
return;
} }
if (args[0] == "stop") if (args[0] == "stop")
{ {
if (s == null) ThrowNoSuchService(); if (s == null) ThrowNoSuchService();
s.StopService(); s.StopService();
return;
} }
if (args[0] == "restart") if (args[0] == "restart")
{ {
@ -653,6 +673,7 @@ namespace winsw
} }
s.StartService(); s.StartService();
return;
} }
if (args[0] == "restart!") if (args[0] == "restart!")
{ {
@ -666,6 +687,7 @@ namespace winsw
{ {
throw new Exception("Failed to invoke restart: "+Marshal.GetLastWin32Error()); throw new Exception("Failed to invoke restart: "+Marshal.GetLastWin32Error());
} }
return;
} }
if (args[0] == "status") if (args[0] == "status")
{ {
@ -675,6 +697,7 @@ namespace winsw
Console.WriteLine("Started"); Console.WriteLine("Started");
else else
Console.WriteLine("Stopped"); Console.WriteLine("Stopped");
return;
} }
if (args[0] == "test") if (args[0] == "test")
{ {
@ -682,8 +705,24 @@ namespace winsw
wsvc.OnStart(args.ToArray()); wsvc.OnStart(args.ToArray());
Thread.Sleep(1000); Thread.Sleep(1000);
wsvc.OnStop(); 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()); 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] <command> [<args>]");
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);
}
} }
} }

View File

@ -28,5 +28,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.1.0.0")] [assembly: AssemblyVersion("1.17.0.0")]
[assembly: AssemblyFileVersion("1.1.0.0")] [assembly: AssemblyFileVersion("1.17.0.0")]

View File

@ -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");
}
}
}

View File

@ -0,0 +1,116 @@
using System;
using System.IO;
using JetBrains.Annotations;
using winsw;
namespace winswTests.Util
{
/// <summary>
/// Helper for WinSW CLI testing
/// </summary>
public static class CLITestHelper
{
private const string SeedXml = "<service>"
+ "<id>service.exe</id>"
+ "<name>Service</name>"
+ "<description>The service.</description>"
+ "<executable>node.exe</executable>"
+ "<arguments>My Arguments</arguments>"
+ "<logmode>rotate</logmode>"
+ "<workingdirectory>"
+ @"C:\winsw\workdir"
+ "</workingdirectory>"
+ @"<logpath>C:\winsw\logs</logpath>"
+ "</service>";
private static readonly ServiceDescriptor DefaultServiceDescriptor = ServiceDescriptor.FromXML(SeedXml);
/// <summary>
/// Runs a simle test, which returns the output CLI
/// </summary>
/// <param name="args">CLI arguments to be passed</param>
/// <param name="descriptor">Optional Service descriptor (will be used for initializationpurposes)</param>
/// <returns>STDOUT if there's no exceptions</returns>
/// <exception cref="Exception">Command failure</exception>
[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();
}
}
/// <summary>
/// Runs a simle test, which returns the output CLI
/// </summary>
/// <param name="args">CLI arguments to be passed</param>
/// <param name="descriptor">Optional Service descriptor (will be used for initializationpurposes)</param>
/// <returns>Test results</returns>
[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);
}
}
/// <summary>
/// Aggregated test report
/// </summary>
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;
}
}
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="JetBrains.Annotations" version="8.0.5.0" targetFramework="net20" />
</packages>

View File

@ -36,17 +36,24 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="JetBrains.Annotations, Version=8.0.5.0, Culture=neutral, PublicKeyToken=1010a0d8d6380325, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\packages\JetBrains.Annotations.8.0.5.0\lib\net20\JetBrains.Annotations.dll</HintPath>
</Reference>
<Reference Include="nunit.framework, Version=2.6.2.12296, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL"> <Reference Include="nunit.framework, Version=2.6.2.12296, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>..\Lib\nunit.framework.dll</HintPath> <HintPath>..\Lib\nunit.framework.dll</HintPath>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Data" /> <Reference Include="System.Data" />
<Reference Include="System.ServiceProcess" />
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="MainTest.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ServiceDescriptorTests.cs" /> <Compile Include="ServiceDescriptorTests.cs" />
<Compile Include="Util\CLITestHelper.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\winsw.csproj"> <ProjectReference Include="..\..\winsw.csproj">
@ -54,6 +61,9 @@
<Name>winsw</Name> <Name>winsw</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<repositories>
<repository path="..\Tests\winswTests\packages.config" />
</repositories>