Files
winsw/ServiceDescriptor.cs
teddy 39891488bf change the wrapped process working directory
set the working directory of the wrapped process with the location of the winsw executable, or the path defined in the configuration file.
2013-03-19 01:53:52 +01:00

429 lines
14 KiB
C#
Executable File

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.ServiceProcess;
using System.Text;
using System.IO;
using System.Net;
using WMI;
using System.Xml;
using System.Threading;
using Microsoft.Win32;
namespace winsw
{
/// <summary>
/// In-memory representation of the configuration file.
/// </summary>
public class ServiceDescriptor
{
private readonly XmlDocument dom = new XmlDocument();
/// <summary>
/// Where did we find the configuration file?
///
/// This string is "c:\abc\def\ghi" when the configuration XML is "c:\abc\def\ghi.xml"
/// </summary>
public readonly string BasePath;
/// <summary>
/// The file name portion of the configuration file.
///
/// In the above example, this would be "ghi".
/// </summary>
public readonly string BaseName;
public static string ExecutablePath
{
get
{
// this returns the executable name as given by the calling process, so
// it needs to be absolutized.
string p = Environment.GetCommandLineArgs()[0];
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, p);
}
}
public ServiceDescriptor()
{
// find co-located configuration xml. We search up to the ancestor directories to simplify debugging,
// as well as trimming off ".vshost" suffix (which is used during debugging)
string p = ExecutablePath;
string baseName = Path.GetFileNameWithoutExtension(p);
if (baseName.EndsWith(".vshost")) baseName = baseName.Substring(0, baseName.Length - 7);
while (true)
{
p = Path.GetDirectoryName(p);
if (File.Exists(Path.Combine(p, baseName + ".xml")))
break;
}
// register the base directory as environment variable so that future expansions can refer to this.
Environment.SetEnvironmentVariable("BASE", p);
BaseName = baseName;
BasePath = Path.Combine(p, BaseName);
dom.Load(BasePath + ".xml");
}
private string SingleElement(string tagName, bool optional = false)
{
var n = dom.SelectSingleNode("//" + tagName);
if (n == null && !optional) throw new InvalidDataException("<" + tagName + "> is missing in configuration XML");
return n == null ? null : Environment.ExpandEnvironmentVariables(n.InnerText);
}
private int SingleIntElement(XmlNode parent, string tagName, int defaultValue)
{
var e = parent.SelectSingleNode(tagName);
if (e == null)
{
return defaultValue;
}
else
{
return int.Parse(e.InnerText);
}
}
/// <summary>
/// Path to the executable.
/// </summary>
public string Executable
{
get
{
return SingleElement("executable");
}
}
/// <summary>
/// Optionally specify a different Path to an executable to shutdown the service.
/// </summary>
public string StopExecutable
{
get
{
return SingleElement("stopexecutable");
}
}
/// <summary>
/// Arguments or multiple optional argument elements which overrule the arguments element.
/// </summary>
public string Arguments
{
get
{
string arguments = AppendTags("argument");
if (arguments == null)
{
var tagName = "arguments";
var argumentsNode = dom.SelectSingleNode("//" + tagName);
if (argumentsNode == null)
{
if (AppendTags("startargument") == null)
{
throw new InvalidDataException("<" + tagName + "> is missing in configuration XML");
}
else
{
return "";
}
}
return Environment.ExpandEnvironmentVariables(argumentsNode.InnerText);
}
else
{
return arguments;
}
}
}
/// <summary>
/// Multiple optional startargument elements.
/// </summary>
public string Startarguments
{
get
{
return AppendTags("startargument");
}
}
/// <summary>
/// Multiple optional stopargument elements.
/// </summary>
public string Stoparguments
{
get
{
return AppendTags("stopargument");
}
}
/// <summary>
/// Optional working directory.
/// </summary>
public string WorkingDirectory {
get {
var wd = SingleElement("workingdirectory", true);
return String.IsNullOrEmpty(wd) ? Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) : wd;
}
}
/// <summary>
/// Combines the contents of all the elements of the given name,
/// or return null if no element exists. Handles whitespace quotation.
/// </summary>
private string AppendTags(string tagName)
{
XmlNode argumentNode = dom.SelectSingleNode("//" + tagName);
if (argumentNode == null)
{
return null;
}
else
{
string arguments = "";
foreach (XmlElement argument in dom.SelectNodes("//" + tagName))
{
string token = Environment.ExpandEnvironmentVariables(argument.InnerText);
if (token.StartsWith("\"") && token.EndsWith("\""))
{
// for backward compatibility, if the argument is already quoted, leave it as is.
// in earlier versions we didn't handle quotation, so the user might have worked
// around it by themselves
}
else
{
if (token.Contains(" "))
{
token = '"' + token + '"';
}
}
arguments += " " + token;
}
return arguments;
}
}
/// <summary>
/// LogDirectory is the service wrapper executable directory or the optionally specified logpath element.
/// </summary>
public string LogDirectory
{
get
{
XmlNode loggingNode = dom.SelectSingleNode("//logpath");
if (loggingNode != null)
{
return Environment.ExpandEnvironmentVariables(loggingNode.InnerText);
}
else
{
return Path.GetDirectoryName(ExecutablePath);
}
}
}
public LogHandler LogHandler
{
get
{
string mode=null;
// first, backward compatibility with older configuration
XmlElement e = (XmlElement)dom.SelectSingleNode("//logmode");
if (e!=null) {
mode = e.InnerText;
} else {
// this is more modern way, to support nested elements as configuration
e = (XmlElement)dom.SelectSingleNode("//log");
if (e!=null)
mode = e.GetAttribute("mode");
}
if (mode == null) mode = "append";
switch (mode)
{
case "rotate":
return new SizeBasedRollingLogAppender(LogDirectory, BaseName);
case "reset":
return new ResetLogAppender(LogDirectory, BaseName);
case "roll":
return new RollingLogAppender(LogDirectory, BaseName);
case "roll-by-time":
XmlNode patternNode = e.SelectSingleNode("pattern");
if (patternNode == null)
{
throw new InvalidDataException("Time Based rolling policy is specified but no pattern can be found in configuration XML.");
}
string pattern = patternNode.InnerText;
int period = SingleIntElement(e,"period",1);
return new TimeBasedRollingLogAppender(LogDirectory, BaseName, pattern, period);
case "roll-by-size":
int sizeThreshold = SingleIntElement(e,"sizeThreshold",10*1024) * SizeBasedRollingLogAppender.BYTES_PER_KB;
int keepFiles = SingleIntElement(e,"keepFiles",SizeBasedRollingLogAppender.DEFAULT_FILES_TO_KEEP);
return new SizeBasedRollingLogAppender(LogDirectory, BaseName, sizeThreshold, keepFiles);
case "append":
return new DefaultLogAppender(LogDirectory, BaseName);
default:
throw new InvalidDataException("Undefined logging mode: " + mode);
}
}
}
/// <summary>
/// Optionally specified depend services that must start before this service starts.
/// </summary>
public string[] ServiceDependencies
{
get
{
System.Collections.ArrayList serviceDependencies = new System.Collections.ArrayList();
foreach (XmlNode depend in dom.SelectNodes("//depend"))
{
serviceDependencies.Add(depend.InnerText);
}
return (string[])serviceDependencies.ToArray(typeof(string));
}
}
public string Id
{
get
{
return SingleElement("id");
}
}
public string Caption
{
get
{
return SingleElement("name");
}
}
public string Description
{
get
{
return SingleElement("description");
}
}
/// <summary>
/// True if the service should when finished on shutdown.
/// This doesn't work on some OSes. See http://msdn.microsoft.com/en-us/library/ms679277%28VS.85%29.aspx
/// </summary>
public bool BeepOnShutdown
{
get
{
return dom.SelectSingleNode("//beeponshutdown") != null;
}
}
/// <summary>
/// The estimated time required for a pending stop operation, in milliseconds (default 15 secs).
/// Before the specified amount of time has elapsed, the service should make its next call to the SetServiceStatus function
/// with either an incremented checkPoint value or a change in currentState. (see http://msdn.microsoft.com/en-us/library/ms685996.aspx)
/// </summary>
public int WaitHint
{
get
{
return SingleIntElement(dom, "waithint", 15000);
}
}
/// <summary>
/// The time, in milliseconds (default 1 sec), before the service should make its next call to the SetServiceStatus function
/// with an incremented checkPoint value.
/// Do not wait longer than the wait hint. A good interval is one-tenth of the wait hint but not less than 1 second and not more than 10 seconds.
/// </summary>
public int SleepTime
{
get
{
return SingleIntElement(dom, "sleeptime", 15000);
}
}
/// <summary>
/// True if the service can interact with the desktop.
/// </summary>
public bool Interactive
{
get
{
return dom.SelectSingleNode("//interactive") != null;
}
}
/// <summary>
/// Environment variable overrides
/// </summary>
public Dictionary<string, string> EnvironmentVariables
{
get
{
Dictionary<string, string> map = new Dictionary<string, string>();
foreach (XmlNode n in dom.SelectNodes("//env"))
{
string key = n.Attributes["name"].Value;
string value = Environment.ExpandEnvironmentVariables(n.Attributes["value"].Value);
map[key] = value;
Environment.SetEnvironmentVariable(key, value);
}
return map;
}
}
/// <summary>
/// List of downloads to be performed by the wrapper before starting
/// a service.
/// </summary>
public List<Download> Downloads
{
get
{
List<Download> r = new List<Download>();
foreach (XmlNode n in dom.SelectNodes("//download"))
{
r.Add(new Download(n));
}
return r;
}
}
}
}