| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650 |
- 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;
- using System.Management;
- namespace winsw
- {
- public class WrapperService : ServiceBase, EventLogger
- {
- private SERVICE_STATUS wrapperServiceStatus;
- 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;
- 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;
- }
- /// <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)
- {
- 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);
- }
- }
- /// <summary>
- /// File replacement.
- /// </summary>
- 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);
- }
- }
- /// <summary>
- /// Starts a thread that protects the execution with a try/catch block.
- /// It appears that in .NET, unhandled exception in any thread causes the app to terminate
- /// http://msdn.microsoft.com/en-us/library/ms228965.aspx
- /// </summary>
- private void StartThread(ThreadStart main)
- {
- new Thread(delegate() {
- try
- {
- main();
- }
- catch (Exception e)
- {
- WriteEvent("Thread failed unexpectedly",e);
- }
- }).Start();
- }
- /// <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);
- }
- LogHandler logAppender = descriptor.LogHandler;
- logAppender.EventLogger = this;
- logAppender.log(process.StandardOutput.BaseStream, process.StandardError.BaseStream);
- }
- public void LogEvent(String message)
- {
- if (systemShuttingdown)
- {
- /* NOP - cannot call EventLog because of shutdown. */
- }
- else
- {
- EventLog.WriteEntry(message);
- }
- }
- public void LogEvent(String message, EventLogEntryType type)
- {
- if (systemShuttingdown)
- {
- /* NOP - cannot call EventLog because of shutdown. */
- }
- else
- {
- EventLog.WriteEntry(message, type);
- }
- }
- private void WriteEvent(Exception exception)
- {
- WriteEvent(exception.Message + "\nStacktrace:" + exception.StackTrace);
- }
- private void WriteEvent(String message, Exception exception)
- {
- WriteEvent(message + "\nMessage:" + exception.Message + "\nStacktrace:" + exception.StackTrace);
- }
- private void WriteEvent(String message)
- {
- string logfilename = Path.Combine(descriptor.LogDirectory, descriptor.BaseName + ".wrapper.log");
- StreamWriter log = new StreamWriter(logfilename, true);
- log.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " - " + message);
- log.Flush();
- log.Close();
- }
- protected override void OnStart(string[] _)
- {
- envs = descriptor.EnvironmentVariables;
- foreach (string key in envs.Keys)
- {
- LogEvent("envar " + key + '=' + envs[key]);
- }
- HandleFileCopies();
- // handle downloads
- foreach (Download d in descriptor.Downloads)
- {
- LogEvent("Downloading: " + d.From+ " to "+d.To);
- try
- {
- d.Perform();
- }
- catch (Exception e)
- {
- LogEvent("Failed to download " + d.From + " to " + d.To + "\n" + e.Message);
- WriteEvent("Failed to download " + d.From +" to "+d.To, e);
- // but just keep going
- }
- }
- string startarguments = descriptor.Startarguments;
- if (startarguments == null)
- {
- startarguments = descriptor.Arguments;
- }
- else
- {
- startarguments += " " + descriptor.Arguments;
- }
- LogEvent("Starting " + descriptor.Executable + ' ' + startarguments);
- WriteEvent("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()
- {
- // WriteEvent("OnShutdown");
- try
- {
- this.systemShuttingdown = true;
- StopIt();
- }
- catch (Exception ex)
- {
- WriteEvent("Shutdown exception", ex);
- }
- }
- protected override void OnStop()
- {
- // WriteEvent("OnStop");
- try
- {
- StopIt();
- }
- catch (Exception ex)
- {
- WriteEvent("Stop exception", ex);
- }
- }
- /// <summary>
- /// Called when we are told by Windows SCM to exit.
- /// </summary>
- private void StopIt()
- {
- string stoparguments = descriptor.Stoparguments;
- LogEvent("Stopping " + descriptor.Id);
- WriteEvent("Stopping " + descriptor.Id);
- orderlyShutdown = true;
- if (stoparguments == null)
- {
- try
- {
- WriteEvent("ProcessKill " + process.Id);
- StopProcessAndChildren(process.Id);
- }
- catch (InvalidOperationException)
- {
- // already terminated
- }
- }
- else
- {
- SignalShutdownPending();
- stoparguments += " " + descriptor.Arguments;
- Process stopProcess = new Process();
- String executable = descriptor.StopExecutable;
- if (executable == null)
- {
- executable = descriptor.Executable;
- }
- StartProcess(stopProcess, stoparguments, executable);
- WriteEvent("WaitForProcessToExit "+process.Id+"+"+stopProcess.Id);
- WaitForProcessToExit(process);
- WaitForProcessToExit(stopProcess);
- SignalShutdownComplete();
- }
- if (systemShuttingdown && descriptor.BeepOnShutdown)
- {
- Console.Beep();
- }
- WriteEvent("Finished " + descriptor.Id);
- }
- private void StopProcessAndChildren(int pid)
- {
- var searcher = new ManagementObjectSearcher("Select * From Win32_Process Where ParentProcessID=" + pid);
- foreach (var mo in searcher.Get())
- {
- StopProcessAndChildren(Convert.ToInt32(mo["ProcessID"]));
- }
- var proc = Process.GetProcessById(pid);
- WriteEvent("Send SIGINT " + process.Id);
- bool successful = SigIntHelper.SendSIGINTToProcess(proc,descriptor.StopTimeout);
- if (successful)
- {
- WriteEvent("SIGINT to" + process.Id + " successful");
- }
- else
- {
- try
- {
- WriteEvent("SIGINT to " + process.Id + " failed - Killing as fallback");
- proc.Kill();
- }
- catch (ArgumentException)
- {
- // Process already exited.
- }
- }
- }
- private void WaitForProcessToExit(Process process)
- {
- SignalShutdownPending();
- try
- {
- // WriteEvent("WaitForProcessToExit [start]");
- while (!process.WaitForExit(descriptor.SleepTime.Milliseconds))
- {
- SignalShutdownPending();
- // WriteEvent("WaitForProcessToExit [repeat]");
- }
- }
- catch (InvalidOperationException)
- {
- // already terminated
- }
- // WriteEvent("WaitForProcessToExit [finished]");
- }
- private void SignalShutdownPending()
- {
- IntPtr handle = this.ServiceHandle;
- wrapperServiceStatus.checkPoint++;
- wrapperServiceStatus.waitHint = descriptor.WaitHint.Milliseconds;
- // WriteEvent("SignalShutdownPending " + wrapperServiceStatus.checkPoint + ":" + wrapperServiceStatus.waitHint);
- wrapperServiceStatus.currentState = (int)State.SERVICE_STOP_PENDING;
- Advapi32.SetServiceStatus(handle, ref wrapperServiceStatus);
- }
- private void SignalShutdownComplete()
- {
- IntPtr handle = this.ServiceHandle;
- wrapperServiceStatus.checkPoint++;
- // WriteEvent("SignalShutdownComplete " + wrapperServiceStatus.checkPoint + ":" + wrapperServiceStatus.waitHint);
- wrapperServiceStatus.currentState = (int)State.SERVICE_STOPPED;
- Advapi32.SetServiceStatus(handle, ref wrapperServiceStatus);
- }
- private void StartProcess(Process process, string arguments, String executable)
- {
- var ps = process.StartInfo;
- ps.FileName = executable;
- ps.Arguments = arguments;
- ps.WorkingDirectory = descriptor.WorkingDirectory;
- 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();
- WriteEvent("Started " + process.Id);
- var priority = descriptor.Priority;
- if (priority != ProcessPriorityClass.Normal)
- process.PriorityClass = priority;
- // monitor the completion of the process
- StartThread(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 + "] finished with " + process.ExitCode, EventLogEntryType.Warning);
- // if we finished orderly, report that to SCM.
- // by not reporting unclean shutdown, we let Windows SCM to decide if it wants to
- // restart the service automatically
- if (process.ExitCode == 0)
- SignalShutdownComplete();
- Environment.Exit(process.ExitCode);
- }
- }
- catch (InvalidOperationException ioe)
- {
- LogEvent("WaitForExit " + ioe.Message);
- }
- try
- {
- process.Dispose();
- }
- catch (InvalidOperationException ioe)
- {
- LogEvent("Dispose " + ioe.Message);
- }
- });
- }
- 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);
- var args = new List<string>(Array.AsReadOnly(_args));
- if (args[0] == "/redirect")
- {
- // Redirect output
- // One might ask why we support this when the caller
- // can redirect the output easily. The answer is for supporting UAC.
- // On UAC-enabled Windows such as Vista, SCM operation requires
- // elevated privileges, thus winsw.exe needs to be launched
- // accordingly. This in turn limits what the caller can do,
- // and among other things it makes it difficult for the caller
- // to read stdout/stderr. Thus redirection becomes handy.
- var f = new FileStream(args[1], FileMode.Create);
- var w = new StreamWriter(f);
- w.AutoFlush = true;
- Console.SetOut(w);
- Console.SetError(w);
- var handle = f.Handle;
- Kernel32.SetStdHandle(-11, handle); // set stdout
- Kernel32.SetStdHandle(-12, handle); // set stder
- args = args.GetRange(2, args.Count - 2);
- }
- args[0] = args[0].ToLower();
- if (args[0] == "install")
- {
- string username=null, password=null;
- if (args.Count > 1 && args[1] == "/p")
- {
- // we expected username/password on stdin
- Console.Write("Username: ");
- username = Console.ReadLine();
- Console.Write("Password: ");
- password = ReadPassword();
- }
- else
- {
- if (d.HasServiceAccount())
- {
- username = d.ServiceAccountUser;
- password = d.ServiceAccountPassword;
- }
- }
- svc.Create (
- d.Id,
- d.Caption,
- "\"" + d.ExecutablePath + "\"",
- WMI.ServiceType.OwnProcess,
- ErrorControl.UserNotified,
- StartMode.Automatic,
- d.Interactive,
- username,
- password,
- 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);
- var actions = d.FailureActions;
- if (actions.Count > 0)
- {// set the failure actions
- using (ServiceManager scm = new ServiceManager())
- {
- using (Service sc = scm.Open(d.Id))
- {
- sc.ChangeConfig(d.ResetFailureAfter, actions);
- }
- }
- }
- }
- 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] == "restart!")
- {
- // run restart from another process group. see README.md for why this is useful.
- STARTUPINFO si = new STARTUPINFO();
- PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
- bool result = Kernel32.CreateProcess(null, d.ExecutablePath+" restart", IntPtr.Zero, IntPtr.Zero, false, 0x200/*CREATE_NEW_PROCESS_GROUP*/, IntPtr.Zero, null, ref si, out pi);
- if (!result)
- {
- throw new Exception("Failed to invoke restart: "+Marshal.GetLastWin32Error());
- }
- }
- 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.ToArray());
- Thread.Sleep(1000);
- wsvc.OnStop();
- }
- return;
- }
- ServiceBase.Run(new WrapperService());
- }
- private static string ReadPassword()
- {
- StringBuilder buf = new StringBuilder();
- ConsoleKeyInfo key;
- while (true)
- {
- key = Console.ReadKey(true);
- if (key.Key == ConsoleKey.Enter)
- {
- return buf.ToString();
- }
- else if (key.Key == ConsoleKey.Backspace)
- {
- buf.Remove(buf.Length - 1, 1);
- Console.Write("\b \b");
- }
- else
- {
- Console.Write('*');
- buf.Append(key.KeyChar);
- }
- }
- }
- }
- }
|