using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; 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 { /// /// In-memory representation of the configuration file. /// public class ServiceDescriptor { private readonly XmlDocument dom = new XmlDocument(); /// /// Where did we find the configuration file? /// /// This string is "c:\abc\def\ghi" when the configuration XML is "c:\abc\def\ghi.xml" /// public readonly string BasePath; /// /// The file name portion of the configuration file. /// /// In the above example, this would be "ghi". /// 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) { var n = dom.SelectSingleNode("//" + tagName); if (n == null) throw new InvalidDataException("<" + tagName + "> is missing in configuration XML"); return Environment.ExpandEnvironmentVariables(n.InnerText); } /// /// Path to the executable. /// public string Executable { get { return SingleElement("executable"); } } /// /// Optionally specify a different Path to an executable to shutdown the service. /// public string StopExecutable { get { return AppendTags("stopexecutable"); } } /// /// Arguments or multiple optional argument elements which overrule the arguments element. /// 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; } } } /// /// Multiple optional startargument elements. /// public string Startarguments { get { return AppendTags("startargument"); } } /// /// Multiple optional stopargument elements. /// public string Stoparguments { get { return AppendTags("stopargument"); } } /// /// Combines the contents of all the elements of the given name, /// or return null if no element exists. /// private string AppendTags(string tagName) { XmlNode argumentNode = dom.SelectSingleNode("//" + tagName); if (argumentNode == null) { return null; } else { string arguments = ""; foreach (XmlNode argument in dom.SelectNodes("//" + tagName)) { arguments += " " + argument.InnerText; } return Environment.ExpandEnvironmentVariables(arguments); } } /// /// LogDirectory is the service wrapper executable directory or the optionally specified logpath element. /// public string LogDirectory { get { XmlNode loggingNode = dom.SelectSingleNode("//logpath"); if (loggingNode != null) { return loggingNode.InnerText; } else { return Path.GetDirectoryName(ExecutablePath); } } } /// /// Logmode to 'reset', 'rotate' once or 'append' [default] the out.log and err.log files. /// public string Logmode { get { XmlNode logmodeNode = dom.SelectSingleNode("//logmode"); if (logmodeNode == null) { return "append"; } else { return logmodeNode.InnerText; } } } /// /// Optionally specified depend services that must start before this service starts. /// 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"); } } /// /// True if the service should when finished on shutdown. /// public bool BeepOnShutdown { get { return dom.SelectSingleNode("//beeponshutdown") != null; } } /// /// 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) /// public int WaitHint { get { XmlNode waithintNode = dom.SelectSingleNode("//waithint"); if (waithintNode == null) { return 15000; } else { return int.Parse(waithintNode.InnerText); } } } /// /// 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. /// public int SleepTime { get { XmlNode sleeptimeNode = dom.SelectSingleNode("//sleeptime"); if (sleeptimeNode == null) { return 1000; } else { return int.Parse(sleeptimeNode.InnerText); } } } /// /// True if the service can interact with the desktop. /// public bool Interactive { get { return dom.SelectSingleNode("//interactive") != null; } } /// /// Environment variable overrides /// public Dictionary EnvironmentVariables { get { Dictionary map = new Dictionary(); 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; } } /// /// List of downloads to be performed by the wrapper before starting /// a service. /// public List Downloads { get { List r = new List(); foreach (XmlNode n in dom.SelectNodes("//download")) { r.Add(new Download(n)); } return r; } } } }