Browse Source

applied a patch in https://kenai.com/bugzilla/attachment.cgi?id=25

git-svn-id: https://svn.kenai.com/svn/winsw~subversion/trunk@18 c8b2a3fe-9b5b-6a51-a37e-dc31b0e308fa
kohsuke 17 years ago
parent
commit
58d7f0afd2
1 changed files with 620 additions and 619 deletions
  1. 620 619
      Main.cs

+ 620 - 619
Main.cs

@@ -1,619 +1,620 @@
-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
-{
-    /// <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)
-        {
-            var n = dom.SelectSingleNode("//" + tagName);
-            if (n == null) throw new InvalidDataException("<" + tagName + "> is missing in configuration XML");
-            return Environment.ExpandEnvironmentVariables(n.InnerText);
-        }
-
-        /// <summary>
-        /// Path to the executable.
-        /// </summary>
-        public string Executable
-        {
-            get
-            {
-                return SingleElement("executable");
-            }
-        }
-
-        /// <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>
-        /// Combines the contents of all the elements of the given name,
-        /// or return null if no element exists.
-        /// </summary>
-        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);
-            }
-        }
-
-        /// <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 loggingNode.InnerText;
-                }
-                else
-                {
-                    return Path.GetDirectoryName(ExecutablePath);
-                }
-            }
-        }
-
-
-        /// <summary>
-        /// Logmode to 'reset', 'roll' once or 'append' [default] the out.log and err.log files.
-        /// </summary>
-        public string Logmode
-        {
-            get
-            {
-                XmlNode logmodeNode = dom.SelectSingleNode("//logmode");
-
-                if (logmodeNode == null)
-                {
-                    return "append";
-                }
-                else
-                {
-                    return logmodeNode.InnerText;
-                }
-            }
-        }
-
-        /// <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 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;
-            }
-        }
-    }
-
-    public class WrapperService : ServiceBase
-    {
-        private Process process = new Process();
-        private ServiceDescriptor descriptor;
-        private Dictionary<string, string> envs;
-
-        /// <summary>
-        /// 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.
-        /// </summary>
-        private bool orderlyShutdown;
-
-        public WrapperService()
-        {
-            this.descriptor = new ServiceDescriptor();
-            this.ServiceName = descriptor.Id;
-            this.CanStop = true;
-            this.CanPauseAndContinue = false;
-            this.AutoLog = true;
-        }
-
-        /// <summary>
-        /// Copy stuff from StreamReader to StreamWriter
-        /// </summary>
-        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();
-        }
-
-        /// <summary>
-        /// Process the file copy instructions, so that we can replace files that are always in use while
-        /// the service runs.
-        /// </summary>
-        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);
-            }
-        }
-
-        /// <summary>
-        /// Handle the creation of the logfiles based on the optional logmode setting.
-        /// </summary>
-        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);
-
-            // 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;
-
-                StartProcess(new Process(), stoparguments);
-            }
-        }
-
-        private void StartProcess(Process process, string arguments)
-        {
-            var ps = process.StartInfo;
-            ps.FileName = descriptor.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)
-                ps.EnvironmentVariables[key] = envs[key];
-
-            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<Win32Services>();
-                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();
-                    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
+{
+    /// <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)
+        {
+            var n = dom.SelectSingleNode("//" + tagName);
+            if (n == null) throw new InvalidDataException("<" + tagName + "> is missing in configuration XML");
+            return Environment.ExpandEnvironmentVariables(n.InnerText);
+        }
+
+        /// <summary>
+        /// Path to the executable.
+        /// </summary>
+        public string Executable
+        {
+            get
+            {
+                return SingleElement("executable");
+            }
+        }
+
+        /// <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>
+        /// Combines the contents of all the elements of the given name,
+        /// or return null if no element exists.
+        /// </summary>
+        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);
+            }
+        }
+
+        /// <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 loggingNode.InnerText;
+                }
+                else
+                {
+                    return Path.GetDirectoryName(ExecutablePath);
+                }
+            }
+        }
+
+
+        /// <summary>
+        /// Logmode to 'reset', 'roll' once or 'append' [default] the out.log and err.log files.
+        /// </summary>
+        public string Logmode
+        {
+            get
+            {
+                XmlNode logmodeNode = dom.SelectSingleNode("//logmode");
+
+                if (logmodeNode == null)
+                {
+                    return "append";
+                }
+                else
+                {
+                    return logmodeNode.InnerText;
+                }
+            }
+        }
+
+        /// <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 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;
+            }
+        }
+    }
+
+    public class WrapperService : ServiceBase
+    {
+        private Process process = new Process();
+        private ServiceDescriptor descriptor;
+        private Dictionary<string, string> envs;
+
+        /// <summary>
+        /// 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.
+        /// </summary>
+        private bool orderlyShutdown;
+
+        public WrapperService()
+        {
+            this.descriptor = new ServiceDescriptor();
+            this.ServiceName = descriptor.Id;
+            this.CanStop = true;
+            this.CanPauseAndContinue = false;
+            this.AutoLog = true;
+        }
+
+        /// <summary>
+        /// Copy stuff from StreamReader to StreamWriter
+        /// </summary>
+        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();
+        }
+
+        /// <summary>
+        /// Process the file copy instructions, so that we can replace files that are always in use while
+        /// the service runs.
+        /// </summary>
+        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);
+            }
+        }
+
+        /// <summary>
+        /// Handle the creation of the logfiles based on the optional logmode setting.
+        /// </summary>
+        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);
+
+            // 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;
+
+                StartProcess(new Process(), stoparguments);
+            }
+        }
+
+        private void StartProcess(Process process, string arguments)
+        {
+            var ps = process.StartInfo;
+            ps.FileName = descriptor.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<Win32Services>();
+                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();
+                    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());
+        }
+    }
+}