diff --git a/Main.cs b/Main.cs index b91ef7e..3dc448f 100644 --- a/Main.cs +++ b/Main.cs @@ -1,650 +1,705 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Diagnostics; -using System.ServiceProcess; -using System.Text; -using System.IO; -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', 'roll' 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 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; - } - } - } - - public class WrapperService : ServiceBase - { - private Process process = new Process(); - private ServiceDescriptor descriptor; - private Dictionary envs; - - /// - /// Indicates to the watch dog thread that we are going to terminate the process, - /// so don't try to kill us when the child exits. - /// - private bool orderlyShutdown; - - public WrapperService() - { - this.descriptor = new ServiceDescriptor(); - this.ServiceName = descriptor.Id; - this.CanStop = true; - this.CanPauseAndContinue = false; - this.AutoLog = true; - } - - /// - /// Copy stuff from StreamReader to StreamWriter - /// - private void CopyStream(StreamReader i, StreamWriter o) - { - char[] buf = new char[1024]; - while (true) - { - int sz = i.Read(buf, 0, buf.Length); - if (sz == 0) break; - o.Write(buf, 0, sz); - o.Flush(); - } - i.Close(); - o.Close(); - } - - /// - /// Process the file copy instructions, so that we can replace files that are always in use while - /// the service runs. - /// - private void HandleFileCopies() - { - var file = descriptor.BasePath + ".copies"; - if (!File.Exists(file)) - return; // nothing to handle - - try - { - using (var tr = new StreamReader(file,Encoding.UTF8)) - { - string line; - while ((line = tr.ReadLine()) != null) - { - EventLog.WriteEntry("Handling copy: " + line); - string[] tokens = line.Split('>'); - if (tokens.Length > 2) - { - EventLog.WriteEntry("Too many delimiters in " + line); - continue; - } - - CopyFile(tokens[0], tokens[1]); - } - } - } - finally - { - File.Delete(file); - } - - } - - private void CopyFile(string sourceFileName, string destFileName) - { - try - { - File.Delete(destFileName); - File.Move(sourceFileName, destFileName); - } - catch (IOException e) - { - EventLog.WriteEntry("Failed to copy :" + sourceFileName + " to " + destFileName + " because " + e.Message); - } - } - - /// - /// Handle the creation of the logfiles based on the optional logmode setting. - /// - private void HandleLogfiles() - { - string logDirectory = descriptor.LogDirectory; - - if (!Directory.Exists(logDirectory)) - { - Directory.CreateDirectory(logDirectory); - } - - string baseName = descriptor.BaseName; - string errorLogfilename = Path.Combine(logDirectory, baseName + ".err.log"); - string outputLogfilename = Path.Combine(logDirectory, baseName + ".out.log"); - - System.IO.FileMode fileMode = FileMode.Append; - - if (descriptor.Logmode == "reset") - { - fileMode = FileMode.Create; - } - else if (descriptor.Logmode == "roll") - { - CopyFile(outputLogfilename, outputLogfilename + ".old"); - CopyFile(errorLogfilename, errorLogfilename + ".old"); - } - - new Thread(delegate() { CopyStream(process.StandardOutput, new StreamWriter(new FileStream(outputLogfilename, fileMode))); }).Start(); - new Thread(delegate() { CopyStream(process.StandardError, new StreamWriter(new FileStream(errorLogfilename, fileMode))); }).Start(); - } - - protected override void OnStart(string[] args) - { - envs = descriptor.EnvironmentVariables; - foreach (string key in envs.Keys) - { - EventLog.WriteEntry("envar " + key + '=' + envs[key]); - } - - HandleFileCopies(); - - string startarguments = descriptor.Startarguments; - - if (startarguments == null) - { - startarguments = descriptor.Arguments; - } - else - { - startarguments += " " + descriptor.Arguments; - } - - EventLog.WriteEntry("Starting " + descriptor.Executable + ' ' + startarguments); - - StartProcess(process, startarguments, descriptor.Executable); - - // send stdout and stderr to its respective output file. - HandleLogfiles(); - - process.StandardInput.Close(); // nothing for you to read! - } - - protected override void OnStop() - { - string stoparguments = descriptor.Stoparguments; - EventLog.WriteEntry("Stopping " + descriptor.Id); - orderlyShutdown = true; - - if (stoparguments == null) - { - try - { - process.Kill(); - } - catch (InvalidOperationException) - { - // already terminated - } - } - else - { - stoparguments += " " + descriptor.Arguments; - - Process stopProcess = new Process(); - String executable = descriptor.StopExecutable; - - if (executable == null) - { - executable = descriptor.Executable; - } - - StartProcess(stopProcess, stoparguments, executable); -// stopProcess.WaitForExit(); - process.WaitForExit(); - } - } - - private void StartProcess(Process process, string arguments, String executable) - { - var ps = process.StartInfo; - ps.FileName = executable; - ps.Arguments = arguments; - ps.CreateNoWindow = false; - ps.UseShellExecute = false; - ps.RedirectStandardInput = true; // this creates a pipe for stdin to the new process, instead of having it inherit our stdin. - ps.RedirectStandardOutput = true; - ps.RedirectStandardError = true; - - foreach (string key in envs.Keys) - System.Environment.SetEnvironmentVariable(key, envs[key]); - // ps.EnvironmentVariables[key] = envs[key]; // bugged (lower cases all variable names due to StringDictionary being used, see http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=326163) - - process.Start(); - - // monitor the completion of the process - new Thread(delegate() - { - string msg = process.Id + " - " + process.StartInfo.FileName + " " + process.StartInfo.Arguments; - process.WaitForExit(); - - try - { - if (orderlyShutdown) - { - EventLog.WriteEntry("Child process [" + msg + "] terminated with " + process.ExitCode, EventLogEntryType.Information); - } - else - { - EventLog.WriteEntry("Child process [" + msg + "] terminated with " + process.ExitCode, EventLogEntryType.Warning); - Environment.Exit(process.ExitCode); - } - } - catch (InvalidOperationException ioe) - { - EventLog.WriteEntry("WaitForExit " + ioe.Message); - } - - try - { - process.Dispose(); - } - catch (InvalidOperationException ioe) - { - EventLog.WriteEntry("Dispose " + ioe.Message); - } - }).Start(); - } - - public static int Main(string[] args) - { - try - { - Run(args); - return 0; - } - catch (WmiException e) - { - Console.Error.WriteLine(e); - return (int)e.ErrorCode; - } - catch (Exception e) - { - Console.Error.WriteLine(e); - return -1; - } - } - - private static void ThrowNoSuchService() - { - throw new WmiException(ReturnValue.NoSuchService); - } - - public static void Run(string[] args) - { - if (args.Length > 0) - { - var d = new ServiceDescriptor(); - Win32Services svc = new WmiRoot().GetCollection(); - Win32Service s = svc.Select(d.Id); - - args[0] = args[0].ToLower(); - if (args[0] == "install") - { - svc.Create( - d.Id, - d.Caption, - ServiceDescriptor.ExecutablePath, - WMI.ServiceType.OwnProcess, - ErrorControl.UserNotified, - StartMode.Automatic, - d.Interactive, - d.ServiceDependencies); - // update the description - /* Somehow this doesn't work, even though it doesn't report an error - Win32Service s = svc.Select(d.Id); - s.Description = d.Description; - s.Commit(); - */ - - // so using a classic method to set the description. Ugly. - Registry.LocalMachine.OpenSubKey("System").OpenSubKey("CurrentControlSet").OpenSubKey("Services") - .OpenSubKey(d.Id, true).SetValue("Description", d.Description); - } - if (args[0] == "uninstall") - { - if (s == null) - return; // there's no such service, so consider it already uninstalled - try - { - s.Delete(); - } - catch (WmiException e) - { - if (e.ErrorCode == ReturnValue.ServiceMarkedForDeletion) - return; // it's already uninstalled, so consider it a success - throw e; - } - } - if (args[0] == "start") - { - if (s == null) ThrowNoSuchService(); - s.StartService(); - } - if (args[0] == "stop") - { - if (s == null) ThrowNoSuchService(); - s.StopService(); - } - if (args[0] == "restart") - { - if (s == null) - ThrowNoSuchService(); - - if(s.Started) - s.StopService(); - - while (s.Started) - { - Thread.Sleep(1000); - s = svc.Select(d.Id); - } - - s.StartService(); - } - if (args[0] == "status") - { - if (s == null) - Console.WriteLine("NonExistent"); - else if (s.Started) - Console.WriteLine("Started"); - else - Console.WriteLine("Stopped"); - } - if (args[0] == "test") - { - WrapperService wsvc = new WrapperService(); - wsvc.OnStart(args); - Thread.Sleep(1000); - wsvc.OnStop(); - } - return; - } - ServiceBase.Run(new WrapperService()); - } - } -} +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Diagnostics; +using System.ServiceProcess; +using System.Text; +using System.IO; +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', 'roll' 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 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; + } + } + } + + public class WrapperService : ServiceBase + { + private Process process = new Process(); + private ServiceDescriptor descriptor; + private Dictionary envs; + + /// + /// Indicates to the watch dog thread that we are going to terminate the process, + /// so don't try to kill us when the child exits. + /// + private bool orderlyShutdown; + private bool systemShuttingdown; + + public WrapperService() + { + this.descriptor = new ServiceDescriptor(); + this.ServiceName = descriptor.Id; + this.CanShutdown = true; + this.CanStop = true; + this.CanPauseAndContinue = false; + this.AutoLog = true; + this.systemShuttingdown = false; + } + + /// + /// Copy stuff from StreamReader to StreamWriter + /// + private void CopyStream(StreamReader i, StreamWriter o) + { + char[] buf = new char[1024]; + while (true) + { + int sz = i.Read(buf, 0, buf.Length); + if (sz == 0) break; + o.Write(buf, 0, sz); + o.Flush(); + } + i.Close(); + o.Close(); + } + + /// + /// Process the file copy instructions, so that we can replace files that are always in use while + /// the service runs. + /// + private void HandleFileCopies() + { + var file = descriptor.BasePath + ".copies"; + if (!File.Exists(file)) + return; // nothing to handle + + try + { + using (var tr = new StreamReader(file,Encoding.UTF8)) + { + string line; + while ((line = tr.ReadLine()) != null) + { + LogEvent("Handling copy: " + line); + string[] tokens = line.Split('>'); + if (tokens.Length > 2) + { + LogEvent("Too many delimiters in " + line); + continue; + } + + CopyFile(tokens[0], tokens[1]); + } + } + } + finally + { + File.Delete(file); + } + + } + + private void CopyFile(string sourceFileName, string destFileName) + { + try + { + File.Delete(destFileName); + File.Move(sourceFileName, destFileName); + } + catch (IOException e) + { + LogEvent("Failed to copy :" + sourceFileName + " to " + destFileName + " because " + e.Message); + } + } + + /// + /// Handle the creation of the logfiles based on the optional logmode setting. + /// + private void HandleLogfiles() + { + string logDirectory = descriptor.LogDirectory; + + if (!Directory.Exists(logDirectory)) + { + Directory.CreateDirectory(logDirectory); + } + + string baseName = descriptor.BaseName; + string errorLogfilename = Path.Combine(logDirectory, baseName + ".err.log"); + string outputLogfilename = Path.Combine(logDirectory, baseName + ".out.log"); + + System.IO.FileMode fileMode = FileMode.Append; + + if (descriptor.Logmode == "reset") + { + fileMode = FileMode.Create; + } + else if (descriptor.Logmode == "roll") + { + CopyFile(outputLogfilename, outputLogfilename + ".old"); + CopyFile(errorLogfilename, errorLogfilename + ".old"); + } + + new Thread(delegate() { CopyStream(process.StandardOutput, new StreamWriter(new FileStream(outputLogfilename, fileMode))); }).Start(); + new Thread(delegate() { CopyStream(process.StandardError, new StreamWriter(new FileStream(errorLogfilename, fileMode))); }).Start(); + } + + private void LogEvent(String message) + { + if (systemShuttingdown) + { + /* NOP - cannot call EventLog because of shutdown. */ + } + else + { + EventLog.WriteEntry(message); + } + } + + private void LogEvent(String message, EventLogEntryType type) + { + if (systemShuttingdown) + { + /* NOP - cannot call EventLog because of shutdown. */ + } + else + { + EventLog.WriteEntry(message, type); + } + } + + private void WriteEvent(String message) + { + string logfilename = Path.Combine(descriptor.LogDirectory, descriptor.BaseName + ".log"); + StreamWriter log = new StreamWriter(logfilename, true); + + log.WriteLine(message); + log.Flush(); + log.Close(); + } + + protected override void OnStart(string[] args) + { + envs = descriptor.EnvironmentVariables; + foreach (string key in envs.Keys) + { + LogEvent("envar " + key + '=' + envs[key]); + } + + HandleFileCopies(); + + string startarguments = descriptor.Startarguments; + + if (startarguments == null) + { + startarguments = descriptor.Arguments; + } + else + { + startarguments += " " + descriptor.Arguments; + } + + LogEvent("Starting " + descriptor.Executable + ' ' + startarguments); + + StartProcess(process, startarguments, descriptor.Executable); + + // send stdout and stderr to its respective output file. + HandleLogfiles(); + + process.StandardInput.Close(); // nothing for you to read! + } + + protected override void OnShutdown() + { + try + { + this.systemShuttingdown = true; + StopIt(); + } + catch (Exception ex) + { + WriteEvent("Shutdown exception:"+ex.Message); + } + } + + protected override void OnStop() + { + StopIt(); + } + + private void StopIt() + { + string stoparguments = descriptor.Stoparguments; + LogEvent("Stopping " + descriptor.Id); + orderlyShutdown = true; + + if (stoparguments == null) + { + try + { + process.Kill(); + } + catch (InvalidOperationException) + { + // already terminated + } + } + else + { + stoparguments += " " + descriptor.Arguments; + + Process stopProcess = new Process(); + String executable = descriptor.StopExecutable; + + if (executable == null) + { + executable = descriptor.Executable; + } + + StartProcess(stopProcess, stoparguments, executable); +// stopProcess.WaitForExit(); + process.WaitForExit(); + } + } + + private void StartProcess(Process process, string arguments, String executable) + { + var ps = process.StartInfo; + ps.FileName = executable; + ps.Arguments = arguments; + ps.CreateNoWindow = false; + ps.UseShellExecute = false; + ps.RedirectStandardInput = true; // this creates a pipe for stdin to the new process, instead of having it inherit our stdin. + ps.RedirectStandardOutput = true; + ps.RedirectStandardError = true; + + foreach (string key in envs.Keys) + System.Environment.SetEnvironmentVariable(key, envs[key]); + // ps.EnvironmentVariables[key] = envs[key]; // bugged (lower cases all variable names due to StringDictionary being used, see http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=326163) + + process.Start(); + + // monitor the completion of the process + new Thread(delegate() + { + string msg = process.Id + " - " + process.StartInfo.FileName + " " + process.StartInfo.Arguments; + process.WaitForExit(); + + try + { + if (orderlyShutdown) + { + LogEvent("Child process [" + msg + "] terminated with " + process.ExitCode, EventLogEntryType.Information); + } + else + { + LogEvent("Child process [" + msg + "] terminated with " + process.ExitCode, EventLogEntryType.Warning); + Environment.Exit(process.ExitCode); + } + } + catch (InvalidOperationException ioe) + { + LogEvent("WaitForExit " + ioe.Message); + } + + try + { + process.Dispose(); + } + catch (InvalidOperationException ioe) + { + LogEvent("Dispose " + ioe.Message); + } + }).Start(); + } + + public static int Main(string[] args) + { + try + { + Run(args); + return 0; + } + catch (WmiException e) + { + Console.Error.WriteLine(e); + return (int)e.ErrorCode; + } + catch (Exception e) + { + Console.Error.WriteLine(e); + return -1; + } + } + + private static void ThrowNoSuchService() + { + throw new WmiException(ReturnValue.NoSuchService); + } + + public static void Run(string[] args) + { + if (args.Length > 0) + { + var d = new ServiceDescriptor(); + Win32Services svc = new WmiRoot().GetCollection(); + Win32Service s = svc.Select(d.Id); + + args[0] = args[0].ToLower(); + if (args[0] == "install") + { + svc.Create( + d.Id, + d.Caption, + ServiceDescriptor.ExecutablePath, + WMI.ServiceType.OwnProcess, + ErrorControl.UserNotified, + StartMode.Automatic, + d.Interactive, + d.ServiceDependencies); + // update the description + /* Somehow this doesn't work, even though it doesn't report an error + Win32Service s = svc.Select(d.Id); + s.Description = d.Description; + s.Commit(); + */ + + // so using a classic method to set the description. Ugly. + Registry.LocalMachine.OpenSubKey("System").OpenSubKey("CurrentControlSet").OpenSubKey("Services") + .OpenSubKey(d.Id, true).SetValue("Description", d.Description); + } + if (args[0] == "uninstall") + { + if (s == null) + return; // there's no such service, so consider it already uninstalled + try + { + s.Delete(); + } + catch (WmiException e) + { + if (e.ErrorCode == ReturnValue.ServiceMarkedForDeletion) + return; // it's already uninstalled, so consider it a success + throw e; + } + } + if (args[0] == "start") + { + if (s == null) ThrowNoSuchService(); + s.StartService(); + } + if (args[0] == "stop") + { + if (s == null) ThrowNoSuchService(); + s.StopService(); + } + if (args[0] == "restart") + { + if (s == null) + ThrowNoSuchService(); + + if(s.Started) + s.StopService(); + + while (s.Started) + { + Thread.Sleep(1000); + s = svc.Select(d.Id); + } + + s.StartService(); + } + if (args[0] == "status") + { + if (s == null) + Console.WriteLine("NonExistent"); + else if (s.Started) + Console.WriteLine("Started"); + else + Console.WriteLine("Stopped"); + } + if (args[0] == "test") + { + WrapperService wsvc = new WrapperService(); + wsvc.OnStart(args); + Thread.Sleep(1000); + wsvc.OnStop(); + } + return; + } + ServiceBase.Run(new WrapperService()); + } + } +} diff --git a/winsw.sln b/winsw.sln index 8c2835e..38a18c3 100644 --- a/winsw.sln +++ b/winsw.sln @@ -1,6 +1,6 @@  Microsoft Visual Studio Solution File, Format Version 10.00 -# Visual Studio 2008 +# Visual C# Express 2008 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "winsw", "winsw.csproj", "{0DE77F55-ADE5-43C1-999A-0BC81153B039}" EndProject Global @@ -15,8 +15,8 @@ Global GlobalSection(ProjectConfigurationPlatforms) = postSolution {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Mixed Platforms.ActiveCfg = Release|Any CPU + {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Mixed Platforms.Build.0 = Release|Any CPU {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Win32.ActiveCfg = Debug|Any CPU {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Release|Any CPU.ActiveCfg = Release|Any CPU {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Release|Any CPU.Build.0 = Release|Any CPU