#region Copyright 2013-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0
/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.ServiceProcess;
using System.Text;
using System.Threading;
using CSharpTest.Net.Services;
using CSharpTest.Net.Utils;
namespace CSharpTest.Net.Commands
{
///
/// Provide this type to the CommandInterpreter to enable running as a service.
///
public static class ServiceCommands
{
///
/// Provide this type to the CommandInterpreter to enable installing any command as a service.
///
public static class Installation
{
///
/// Installs a Win32 service that runs a single command on this executable when started. While
/// typically used for the HostHTTP command, it will operate for any command. Commands hosted as a
/// service may use the Console.ReadLine() to wait for the service-stop command.
///
/// The name of the Win32 Service to install.
/// The description of the Win32 Service.
/// The service startup type: Automatic, Manual, or Disabled.
/// The service account: LocalService, NetworkService, or LocalSystem.
/// The command to run as a service.
/// The arguments required to run the command.
/// The current command interpreter, used to verify the command 'RunAsService' and the commandName provided.
/// Optional, The path to the executable that is running.
[Command("InstallService", Description = "Installs the specified command to run as a service."), HttpIgnore]
public static void InstallService(
[Argument("serviceName", Description = "The service name to install as.")] string svcName,
[Argument("displayName", Description = "The display name of the service.")] string displayName,
[Argument("startupType", Description = "The service startup type: Automatic, Manual, or Disabled.")] ServiceStartMode startupType,
[Argument("serviceAccount", Description = "The service account: LocalService, NetworkService, or LocalSystem.")] ServiceAccount serviceAccount,
[Argument("command", Description = "The command to run as a service.")] string commandName,
[Argument("arguments", DefaultValue = null, Description = "The arguments required to run the command.")] string[] arguments,
ICommandInterpreter ci,
[Argument("executable", Visible = false), System.ComponentModel.DefaultValue(null)]
string executable
)
{
ICommand cmd;
if (!ci.TryGetCommand("RunAsService", out cmd))
throw new ApplicationException(
"You must add typeof(ServiceCommands) to the commands or provide your own RunAsService command.");
if (!ci.TryGetCommand(commandName, out cmd))
throw new ApplicationException("The command name '" + commandName + "' was not found.");
if (String.IsNullOrEmpty(svcName))
throw new ArgumentNullException("svcName");
string exe = Path.GetFullPath(executable ?? new Uri(Constants.EntryAssembly.Location).AbsolutePath);
List args = new List();
args.Add("RunAsService");
args.Add("/serviceName=" + svcName);
args.Add(commandName);
args.AddRange(arguments ?? new string[0]);
using (
SvcControlManager scm = SvcControlManager.Create(svcName, displayName, false, startupType, exe,
args.ToArray(),
SvcControlManager.NT_AUTHORITY.Account(
serviceAccount), null))
{
args.RemoveRange(0, 2);
args.Insert(0, Path.GetFileName(exe));
scm.SetDescription(ArgumentList.EscapeArguments(args.ToArray()));
}
}
///
/// Removes a Win32 Service that was previously installed.
///
/// The name of the service to remove.
[Command("UninstallService", Description = "Uninstalls the specified service."), HttpIgnore]
public static void UninstallService(
[Argument("serviceName", Description = "The service name to uninstall.")] string svcName
)
{
using (SvcControlManager scm = new SvcControlManager(svcName))
{
scm.Delete();
}
}
}
///
/// Used to host a command-interpreter's command as a Win32 service. Use ServiceCommands.Installation.InstallService
/// to install the service.
///
[Command("RunAsService", Visible = false), HttpIgnore]
public static void RunAsService(
ICommandInterpreter ci,
[Argument("serviceName")] string name,
[AllArguments] string[] rawArgs
)
{
string svcName;
ArgumentList.Remove(ref rawArgs, "serviceName", out svcName);
Console.SetIn(TextReader.Null);
Environment.CurrentDirectory = AppDomain.CurrentDomain.BaseDirectory;
ServiceBase.Run(new ServiceProcess(ci, svcName, rawArgs));
}
class ServiceProcess : ServiceBase
{
private readonly ICommandInterpreter _ci;
private readonly string[] _arguments;
private Thread _worker;
private ManualResetEvent _shutdown;
public ServiceProcess(ICommandInterpreter ci, string svcName, string[] arguments)
{
_ci = ci;
_arguments = arguments;
AutoLog = true;
CanStop = true;
ServiceName = svcName;
_shutdown = new ManualResetEvent(false);
}
protected override void OnStart(string[] args)
{
_worker = new Thread(Run);
_worker.IsBackground = true;
_worker.Name = ServiceName;
_worker.Start();
}
private void Run()
{
//bool debugging = false;
//while (!debugging)
// Thread.Sleep(100);
var wtr = new EventLogWriter(ServiceName);
try
{
wtr.WriteLine("Service initializing...");
_ci.Run(_arguments, wtr, wtr, new BlockingReader(_shutdown));
}
catch (Exception e)
{
try
{
wtr.WriteLine(e.ToString());
}
catch
{
}
}
finally
{
if (_shutdown.WaitOne(0, false) == false)
{
wtr.WriteLine("The command unexpectedly terminated.");
Environment.Exit(1);
}
}
}
protected override void OnStop()
{
try
{
_shutdown.Set();
if (_worker != null)
{
RequestAdditionalTime(30000);
if (!_worker.Join(15000))
{
RequestAdditionalTime(30000);
if (!_worker.Join(15000))
{
_worker.Abort();
_worker.Join();
}
}
}
}
finally
{
_worker = null;
}
}
}
private class EventLogWriter : TextWriter
{
private readonly string _name;
EventLog _log;
private StringBuilder _sb;
public EventLogWriter(string name)
{
_name = name;
_sb = new StringBuilder(1024);
try { _log = new EventLog("Application", ".", name); }
catch { _log = null; }
}
public override Encoding Encoding
{
get { return Encoding.UTF8; }
}
public override void Write(char ch)
{
if (ch == '\n' || ch == '\r')
{
if (_sb.Length > 0)
{
if (_log != null)
{
try
{
var msg = _sb.ToString().Trim();
if (msg.Length > 0)
{
lock (_log)
_log.WriteEntry(String.Format("[{0}]: {1}", _name, msg));
}
}
catch
{
_log = null;
}
}
_sb.Length = 0;
}
}
else
{
_sb.Append(ch);
}
}
public override void Write(char[] buffer, int index, int count)
{
_sb.Append(buffer, index, count);
if (count > 0 && buffer[index + count-1] == '\n' || buffer[index + count-1] == '\r')
Write('\n');
}
}
private class BlockingReader : TextReader
{
private readonly WaitHandle _stop;
public BlockingReader(WaitHandle stop)
{
_stop = stop;
}
public override int Peek()
{
_stop.WaitOne();
return -1;
}
public override int Read()
{
return Peek();
}
}
}
}