Main.cs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.IO;
  5. using System.Management;
  6. using System.Runtime.InteropServices;
  7. using System.ServiceProcess;
  8. using System.Text;
  9. using System.Threading;
  10. using log4net;
  11. using log4net.Appender;
  12. using log4net.Config;
  13. using log4net.Core;
  14. using log4net.Layout;
  15. using log4net.Repository.Hierarchy;
  16. using Microsoft.Win32;
  17. using winsw.Extensions;
  18. using winsw.Util;
  19. using WMI;
  20. using ServiceType = WMI.ServiceType;
  21. using winsw.Native;
  22. using System.Reflection;
  23. namespace winsw
  24. {
  25. public class WrapperService : ServiceBase, EventLogger, IEventWriter
  26. {
  27. private SERVICE_STATUS _wrapperServiceStatus;
  28. private readonly Process _process = new Process();
  29. private readonly ServiceDescriptor _descriptor;
  30. private Dictionary<string, string> _envs;
  31. internal WinSWExtensionManager ExtensionManager { private set; get; }
  32. private static readonly ILog Log = LogManager.GetLogger("WinSW");
  33. /// <summary>
  34. /// Indicates to the watch dog thread that we are going to terminate the process,
  35. /// so don't try to kill us when the child exits.
  36. /// </summary>
  37. private bool _orderlyShutdown;
  38. private bool _systemShuttingdown;
  39. /// <summary>
  40. /// Version of Windows service wrapper
  41. /// </summary>
  42. /// <remarks>
  43. /// The version will be taken from <see cref="AssemblyInfo"/>
  44. /// </remarks>
  45. public static Version Version
  46. {
  47. get { return Assembly.GetExecutingAssembly().GetName().Version; }
  48. }
  49. public WrapperService(ServiceDescriptor descriptor)
  50. {
  51. _descriptor = descriptor;
  52. ServiceName = _descriptor.Id;
  53. ExtensionManager = new WinSWExtensionManager(_descriptor);
  54. CanShutdown = true;
  55. CanStop = true;
  56. CanPauseAndContinue = false;
  57. AutoLog = true;
  58. _systemShuttingdown = false;
  59. }
  60. public WrapperService() : this (new ServiceDescriptor())
  61. {
  62. }
  63. /// <summary>
  64. /// Process the file copy instructions, so that we can replace files that are always in use while
  65. /// the service runs.
  66. /// </summary>
  67. private void HandleFileCopies()
  68. {
  69. var file = _descriptor.BasePath + ".copies";
  70. if (!File.Exists(file))
  71. return; // nothing to handle
  72. try
  73. {
  74. using (var tr = new StreamReader(file,Encoding.UTF8))
  75. {
  76. string line;
  77. while ((line = tr.ReadLine()) != null)
  78. {
  79. LogEvent("Handling copy: " + line);
  80. string[] tokens = line.Split('>');
  81. if (tokens.Length > 2)
  82. {
  83. LogEvent("Too many delimiters in " + line);
  84. continue;
  85. }
  86. CopyFile(tokens[0], tokens[1]);
  87. }
  88. }
  89. }
  90. finally
  91. {
  92. File.Delete(file);
  93. }
  94. }
  95. /// <summary>
  96. /// File replacement.
  97. /// </summary>
  98. private void CopyFile(string sourceFileName, string destFileName)
  99. {
  100. try
  101. {
  102. File.Delete(destFileName);
  103. File.Move(sourceFileName, destFileName);
  104. }
  105. catch (IOException e)
  106. {
  107. LogEvent("Failed to copy :" + sourceFileName + " to " + destFileName + " because " + e.Message);
  108. }
  109. }
  110. /// <summary>
  111. /// Starts a thread that protects the execution with a try/catch block.
  112. /// It appears that in .NET, unhandled exception in any thread causes the app to terminate
  113. /// http://msdn.microsoft.com/en-us/library/ms228965.aspx
  114. /// </summary>
  115. private void StartThread(ThreadStart main)
  116. {
  117. new Thread(delegate() {
  118. try
  119. {
  120. main();
  121. }
  122. catch (Exception e)
  123. {
  124. WriteEvent("Thread failed unexpectedly",e);
  125. }
  126. }).Start();
  127. }
  128. /// <summary>
  129. /// Handle the creation of the logfiles based on the optional logmode setting.
  130. /// </summary>
  131. private void HandleLogfiles()
  132. {
  133. string logDirectory = _descriptor.LogDirectory;
  134. if (!Directory.Exists(logDirectory))
  135. {
  136. Directory.CreateDirectory(logDirectory);
  137. }
  138. LogHandler logAppender = _descriptor.LogHandler;
  139. logAppender.EventLogger = this;
  140. logAppender.log(_process.StandardOutput.BaseStream, _process.StandardError.BaseStream);
  141. }
  142. public void LogEvent(String message)
  143. {
  144. if (_systemShuttingdown)
  145. {
  146. /* NOP - cannot call EventLog because of shutdown. */
  147. }
  148. else
  149. {
  150. try
  151. {
  152. EventLog.WriteEntry(message);
  153. }
  154. catch (Exception e)
  155. {
  156. WriteEvent("Failed to log event in Windows Event Log: " + message + "; Reason: ", e);
  157. }
  158. }
  159. }
  160. public void LogEvent(String message, EventLogEntryType type)
  161. {
  162. if (_systemShuttingdown)
  163. {
  164. /* NOP - cannot call EventLog because of shutdown. */
  165. }
  166. else
  167. {
  168. try
  169. {
  170. EventLog.WriteEntry(message, type);
  171. }
  172. catch (Exception e)
  173. {
  174. WriteEvent("Failed to log event in Windows Event Log. Reason: ", e);
  175. }
  176. }
  177. }
  178. private void WriteEvent(Exception exception)
  179. {
  180. //TODO: pass exception to logger
  181. WriteEvent(exception.Message + "\nStacktrace:" + exception.StackTrace, Level.Error);
  182. }
  183. private void WriteEvent(String message, Exception exception)
  184. {
  185. //TODO: pass exception to logger
  186. WriteEvent(message + "\nMessage:" + exception.Message + "\nStacktrace:" + exception.StackTrace, Level.Error);
  187. }
  188. private void WriteEvent(String message, Level logLevel = null, Exception ex = null)
  189. {
  190. Log.Logger.Log(GetType(), logLevel ?? Level.Info, message, ex);
  191. }
  192. protected override void OnStart(string[] _)
  193. {
  194. _envs = _descriptor.EnvironmentVariables;
  195. foreach (string key in _envs.Keys)
  196. {
  197. LogEvent("envar " + key + '=' + _envs[key]);
  198. }
  199. HandleFileCopies();
  200. // handle downloads
  201. foreach (Download d in _descriptor.Downloads)
  202. {
  203. LogEvent("Downloading: " + d.From+ " to "+d.To);
  204. try
  205. {
  206. d.Perform();
  207. }
  208. catch (Exception e)
  209. {
  210. LogEvent("Failed to download " + d.From + " to " + d.To + "\n" + e.Message);
  211. WriteEvent("Failed to download " + d.From +" to "+d.To, e);
  212. // but just keep going
  213. }
  214. }
  215. string startarguments = _descriptor.Startarguments;
  216. if (startarguments == null)
  217. {
  218. startarguments = _descriptor.Arguments;
  219. }
  220. else
  221. {
  222. startarguments += " " + _descriptor.Arguments;
  223. }
  224. LogEvent("Starting " + _descriptor.Executable + ' ' + startarguments);
  225. WriteEvent("Starting " + _descriptor.Executable + ' ' + startarguments);
  226. // Load and start extensions
  227. ExtensionManager.LoadExtensions(this);
  228. try
  229. {
  230. ExtensionManager.OnStart(this);
  231. }
  232. catch (ExtensionException ex)
  233. {
  234. LogEvent("Failed to start extension " + ex.ExtensionId + "\n" + ex.Message, EventLogEntryType.Error);
  235. WriteEvent("Failed to start extension " + ex.ExtensionId, ex);
  236. //TODO: Exit on error?
  237. }
  238. LogEvent("Starting " + _descriptor.Executable + ' ' + startarguments);
  239. WriteEvent("Starting " + _descriptor.Executable + ' ' + startarguments);
  240. StartProcess(_process, startarguments, _descriptor.Executable);
  241. // send stdout and stderr to its respective output file.
  242. HandleLogfiles();
  243. _process.StandardInput.Close(); // nothing for you to read!
  244. }
  245. protected override void OnShutdown()
  246. {
  247. // WriteEvent("OnShutdown");
  248. try
  249. {
  250. _systemShuttingdown = true;
  251. StopIt();
  252. }
  253. catch (Exception ex)
  254. {
  255. WriteEvent("Shutdown exception", ex);
  256. }
  257. }
  258. protected override void OnStop()
  259. {
  260. // WriteEvent("OnStop");
  261. try
  262. {
  263. StopIt();
  264. }
  265. catch (Exception ex)
  266. {
  267. WriteEvent("Stop exception", ex);
  268. }
  269. }
  270. /// <summary>
  271. /// Called when we are told by Windows SCM to exit.
  272. /// </summary>
  273. private void StopIt()
  274. {
  275. string stoparguments = _descriptor.Stoparguments;
  276. LogEvent("Stopping " + _descriptor.Id);
  277. WriteEvent("Stopping " + _descriptor.Id);
  278. _orderlyShutdown = true;
  279. if (stoparguments == null)
  280. {
  281. try
  282. {
  283. WriteEvent("ProcessKill " + _process.Id);
  284. StopProcessAndChildren(_process.Id);
  285. }
  286. catch (InvalidOperationException)
  287. {
  288. // already terminated
  289. }
  290. }
  291. else
  292. {
  293. SignalShutdownPending();
  294. stoparguments += " " + _descriptor.Arguments;
  295. Process stopProcess = new Process();
  296. String executable = _descriptor.StopExecutable;
  297. if (executable == null)
  298. {
  299. executable = _descriptor.Executable;
  300. }
  301. StartProcess(stopProcess, stoparguments, executable);
  302. WriteEvent("WaitForProcessToExit "+_process.Id+"+"+stopProcess.Id);
  303. WaitForProcessToExit(_process);
  304. WaitForProcessToExit(stopProcess);
  305. SignalShutdownComplete();
  306. }
  307. // Stop extensions
  308. try
  309. {
  310. ExtensionManager.OnStop(this);
  311. }
  312. catch (ExtensionException ex)
  313. {
  314. LogEvent("Failed to stop extension " + ex.ExtensionId + "\n" + ex.Message, EventLogEntryType.Error);
  315. WriteEvent("Failed to stop extension " + ex.ExtensionId, ex);
  316. }
  317. if (_systemShuttingdown && _descriptor.BeepOnShutdown)
  318. {
  319. Console.Beep();
  320. }
  321. WriteEvent("Finished " + _descriptor.Id);
  322. }
  323. private void StopProcessAndChildren(int pid)
  324. {
  325. var childPids = GetChildPids(pid);
  326. if (_descriptor.StopParentProcessFirst)
  327. {
  328. StopProcess(pid);
  329. foreach (var childPid in childPids)
  330. {
  331. StopProcessAndChildren(childPid);
  332. }
  333. }
  334. else
  335. {
  336. foreach (var childPid in childPids)
  337. {
  338. StopProcessAndChildren(childPid);
  339. }
  340. StopProcess(pid);
  341. }
  342. }
  343. private List<int> GetChildPids(int pid)
  344. {
  345. var searcher = new ManagementObjectSearcher("Select * From Win32_Process Where ParentProcessID=" + pid);
  346. var childPids = new List<int>();
  347. foreach (var mo in searcher.Get())
  348. {
  349. var childProcessId = mo["ProcessID"];
  350. WriteEvent("Found child process: " + childProcessId + " Name: " + mo["Name"]);
  351. childPids.Add(Convert.ToInt32(childProcessId));
  352. }
  353. return childPids;
  354. }
  355. private void StopProcess(int pid)
  356. {
  357. WriteEvent("Stopping process " + pid);
  358. Process proc;
  359. try
  360. {
  361. proc = Process.GetProcessById(pid);
  362. }
  363. catch (ArgumentException)
  364. {
  365. WriteEvent("Process " + pid + " is already stopped");
  366. return;
  367. }
  368. WriteEvent("Send SIGINT " + pid);
  369. bool successful = SigIntHelper.SendSIGINTToProcess(proc, _descriptor.StopTimeout);
  370. if (successful)
  371. {
  372. WriteEvent("SIGINT to" + pid + " successful");
  373. }
  374. else
  375. {
  376. try
  377. {
  378. WriteEvent("SIGINT to " + pid + " failed - Killing as fallback", Level.Warn);
  379. proc.Kill();
  380. }
  381. catch (ArgumentException)
  382. {
  383. // Process already exited.
  384. }
  385. }
  386. }
  387. private void WaitForProcessToExit(Process processoWait)
  388. {
  389. SignalShutdownPending();
  390. int effectiveProcessWaitSleepTime;
  391. if (_descriptor.SleepTime.TotalMilliseconds > Int32.MaxValue)
  392. {
  393. Log.Warn("The requested sleep time " + _descriptor.SleepTime.TotalMilliseconds + "is greater that the max value " +
  394. Int32.MaxValue + ". The value will be truncated");
  395. effectiveProcessWaitSleepTime = Int32.MaxValue;
  396. }
  397. else
  398. {
  399. effectiveProcessWaitSleepTime = (int)_descriptor.SleepTime.TotalMilliseconds;
  400. }
  401. try
  402. {
  403. // WriteEvent("WaitForProcessToExit [start]");
  404. while (!processoWait.WaitForExit(effectiveProcessWaitSleepTime))
  405. {
  406. SignalShutdownPending();
  407. // WriteEvent("WaitForProcessToExit [repeat]");
  408. }
  409. }
  410. catch (InvalidOperationException)
  411. {
  412. // already terminated
  413. }
  414. // WriteEvent("WaitForProcessToExit [finished]");
  415. }
  416. private void SignalShutdownPending()
  417. {
  418. int effectiveWaitHint;
  419. if (_descriptor.WaitHint.TotalMilliseconds > Int32.MaxValue)
  420. {
  421. Log.Warn("The requested WaitHint value (" + _descriptor.WaitHint.TotalMilliseconds + " ms) is greater that the max value " +
  422. Int32.MaxValue + ". The value will be truncated");
  423. effectiveWaitHint = Int32.MaxValue;
  424. }
  425. else
  426. {
  427. effectiveWaitHint = (int)_descriptor.WaitHint.TotalMilliseconds;
  428. }
  429. IntPtr handle = ServiceHandle;
  430. _wrapperServiceStatus.checkPoint++;
  431. _wrapperServiceStatus.waitHint = effectiveWaitHint;
  432. // WriteEvent("SignalShutdownPending " + wrapperServiceStatus.checkPoint + ":" + wrapperServiceStatus.waitHint);
  433. _wrapperServiceStatus.currentState = (int)State.SERVICE_STOP_PENDING;
  434. Advapi32.SetServiceStatus(handle, ref _wrapperServiceStatus);
  435. }
  436. private void SignalShutdownComplete()
  437. {
  438. IntPtr handle = ServiceHandle;
  439. _wrapperServiceStatus.checkPoint++;
  440. // WriteEvent("SignalShutdownComplete " + wrapperServiceStatus.checkPoint + ":" + wrapperServiceStatus.waitHint);
  441. _wrapperServiceStatus.currentState = (int)State.SERVICE_STOPPED;
  442. Advapi32.SetServiceStatus(handle, ref _wrapperServiceStatus);
  443. }
  444. private void StartProcess(Process processToStart, string arguments, String executable)
  445. {
  446. var ps = processToStart.StartInfo;
  447. ps.FileName = executable;
  448. ps.Arguments = arguments;
  449. ps.WorkingDirectory = _descriptor.WorkingDirectory;
  450. ps.CreateNoWindow = false;
  451. ps.UseShellExecute = false;
  452. ps.RedirectStandardInput = true; // this creates a pipe for stdin to the new process, instead of having it inherit our stdin.
  453. ps.RedirectStandardOutput = true;
  454. ps.RedirectStandardError = true;
  455. foreach (string key in _envs.Keys)
  456. Environment.SetEnvironmentVariable(key, _envs[key]);
  457. // 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)
  458. processToStart.Start();
  459. WriteEvent("Started " + processToStart.Id);
  460. var priority = _descriptor.Priority;
  461. if (priority != ProcessPriorityClass.Normal)
  462. processToStart.PriorityClass = priority;
  463. // monitor the completion of the process
  464. StartThread(delegate
  465. {
  466. string msg = processToStart.Id + " - " + processToStart.StartInfo.FileName + " " + processToStart.StartInfo.Arguments;
  467. processToStart.WaitForExit();
  468. try
  469. {
  470. if (_orderlyShutdown)
  471. {
  472. LogEvent("Child process [" + msg + "] terminated with " + processToStart.ExitCode, EventLogEntryType.Information);
  473. }
  474. else
  475. {
  476. LogEvent("Child process [" + msg + "] finished with " + processToStart.ExitCode, EventLogEntryType.Warning);
  477. // if we finished orderly, report that to SCM.
  478. // by not reporting unclean shutdown, we let Windows SCM to decide if it wants to
  479. // restart the service automatically
  480. if (processToStart.ExitCode == 0)
  481. SignalShutdownComplete();
  482. Environment.Exit(processToStart.ExitCode);
  483. }
  484. }
  485. catch (InvalidOperationException ioe)
  486. {
  487. LogEvent("WaitForExit " + ioe.Message);
  488. }
  489. try
  490. {
  491. processToStart.Dispose();
  492. }
  493. catch (InvalidOperationException ioe)
  494. {
  495. LogEvent("Dispose " + ioe.Message);
  496. }
  497. });
  498. }
  499. public static int Main(string[] args)
  500. {
  501. // Run app
  502. try
  503. {
  504. Run(args);
  505. return 0;
  506. }
  507. catch (WmiException e)
  508. {
  509. Console.Error.WriteLine(e);
  510. return (int)e.ErrorCode;
  511. }
  512. catch (Exception e)
  513. {
  514. Console.Error.WriteLine(e);
  515. return -1;
  516. }
  517. }
  518. private static void ThrowNoSuchService()
  519. {
  520. throw new WmiException(ReturnValue.NoSuchService);
  521. }
  522. // ReSharper disable once InconsistentNaming
  523. public static void Run(string[] _args, ServiceDescriptor descriptor = null)
  524. {
  525. bool isCLIMode = _args.Length > 0;
  526. var d = descriptor ?? new ServiceDescriptor();
  527. // Configure the wrapper-internal logging
  528. // STDIN and STDOUT of the child process will be handled independently
  529. InitLoggers(d, isCLIMode);
  530. if (isCLIMode) // CLI mode
  531. {
  532. Log.Debug("Starting ServiceWrapper in CLI mode");
  533. // Get service info for the future use
  534. Win32Services svc = new WmiRoot().GetCollection<Win32Services>();
  535. Win32Service s = svc.Select(d.Id);
  536. var args = new List<string>(Array.AsReadOnly(_args));
  537. if (args[0] == "/redirect")
  538. {
  539. // Redirect output
  540. // One might ask why we support this when the caller
  541. // can redirect the output easily. The answer is for supporting UAC.
  542. // On UAC-enabled Windows such as Vista, SCM operation requires
  543. // elevated privileges, thus winsw.exe needs to be launched
  544. // accordingly. This in turn limits what the caller can do,
  545. // and among other things it makes it difficult for the caller
  546. // to read stdout/stderr. Thus redirection becomes handy.
  547. var f = new FileStream(args[1], FileMode.Create);
  548. var w = new StreamWriter(f) {AutoFlush = true};
  549. Console.SetOut(w);
  550. Console.SetError(w);
  551. var handle = f.Handle;
  552. Kernel32.SetStdHandle(-11, handle); // set stdout
  553. Kernel32.SetStdHandle(-12, handle); // set stder
  554. args = args.GetRange(2, args.Count - 2);
  555. }
  556. args[0] = args[0].ToLower();
  557. if (args[0] == "install")
  558. {
  559. // Check if the service exists
  560. if (s != null)
  561. {
  562. Console.WriteLine("Service with id '" + d.Id + "' already exists");
  563. Console.WriteLine("To install the service, delete the existing one or change service Id in the configuration file");
  564. throw new Exception("Installation failure: Service with id '" + d.Id + "' already exists");
  565. }
  566. string username=null, password=null;
  567. bool setallowlogonasaserviceright = false;
  568. if (args.Count > 1 && args[1] == "/p")
  569. {
  570. // we expected username/password on stdin
  571. Console.Write("Username: ");
  572. username = Console.ReadLine();
  573. Console.Write("Password: ");
  574. password = ReadPassword();
  575. Console.WriteLine();
  576. Console.Write("Set Account rights to allow log on as a service (y/n)?: ");
  577. var keypressed = Console.ReadKey();
  578. Console.WriteLine();
  579. if (keypressed.Key == ConsoleKey.Y)
  580. {
  581. setallowlogonasaserviceright = true;
  582. }
  583. }
  584. else
  585. {
  586. if (d.HasServiceAccount())
  587. {
  588. username = d.ServiceAccountUser;
  589. password = d.ServiceAccountPassword;
  590. setallowlogonasaserviceright = d.AllowServiceAcountLogonRight;
  591. }
  592. }
  593. if (setallowlogonasaserviceright)
  594. {
  595. LogonAsAService.AddLogonAsAServiceRight(username);
  596. }
  597. svc.Create (
  598. d.Id,
  599. d.Caption,
  600. "\"" + d.ExecutablePath + "\"",
  601. ServiceType.OwnProcess,
  602. ErrorControl.UserNotified,
  603. d.StartMode,
  604. d.Interactive,
  605. username,
  606. password,
  607. d.ServiceDependencies);
  608. // update the description
  609. /* Somehow this doesn't work, even though it doesn't report an error
  610. Win32Service s = svc.Select(d.Id);
  611. s.Description = d.Description;
  612. s.Commit();
  613. */
  614. // so using a classic method to set the description. Ugly.
  615. Registry.LocalMachine.OpenSubKey("System").OpenSubKey("CurrentControlSet").OpenSubKey("Services")
  616. .OpenSubKey(d.Id, true).SetValue("Description", d.Description);
  617. var actions = d.FailureActions;
  618. if (actions.Count > 0)
  619. {// set the failure actions
  620. using (ServiceManager scm = new ServiceManager())
  621. {
  622. using (Service sc = scm.Open(d.Id))
  623. {
  624. sc.ChangeConfig(d.ResetFailureAfter, actions);
  625. }
  626. }
  627. }
  628. return;
  629. }
  630. if (args[0] == "uninstall")
  631. {
  632. if (s == null)
  633. {
  634. Console.WriteLine("Warning! The service with id '" + d.Id + "' does not exist. Nothing to uninstall");
  635. return; // there's no such service, so consider it already uninstalled
  636. }
  637. try
  638. {
  639. s.Delete();
  640. }
  641. catch (WmiException e)
  642. {
  643. if (e.ErrorCode == ReturnValue.ServiceMarkedForDeletion)
  644. return; // it's already uninstalled, so consider it a success
  645. throw e;
  646. }
  647. return;
  648. }
  649. if (args[0] == "start")
  650. {
  651. if (s == null) ThrowNoSuchService();
  652. s.StartService();
  653. return;
  654. }
  655. if (args[0] == "stop")
  656. {
  657. if (s == null) ThrowNoSuchService();
  658. s.StopService();
  659. return;
  660. }
  661. if (args[0] == "restart")
  662. {
  663. if (s == null)
  664. ThrowNoSuchService();
  665. if(s.Started)
  666. s.StopService();
  667. while (s.Started)
  668. {
  669. Thread.Sleep(1000);
  670. s = svc.Select(d.Id);
  671. }
  672. s.StartService();
  673. return;
  674. }
  675. if (args[0] == "restart!")
  676. {
  677. // run restart from another process group. see README.md for why this is useful.
  678. STARTUPINFO si = new STARTUPINFO();
  679. PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
  680. 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);
  681. if (!result)
  682. {
  683. throw new Exception("Failed to invoke restart: "+Marshal.GetLastWin32Error());
  684. }
  685. return;
  686. }
  687. if (args[0] == "status")
  688. {
  689. Log.Warn("User requested the status");
  690. if (s == null)
  691. Console.WriteLine("NonExistent");
  692. else if (s.Started)
  693. Console.WriteLine("Started");
  694. else
  695. Console.WriteLine("Stopped");
  696. return;
  697. }
  698. if (args[0] == "test")
  699. {
  700. WrapperService wsvc = new WrapperService();
  701. wsvc.OnStart(args.ToArray());
  702. Thread.Sleep(1000);
  703. wsvc.OnStop();
  704. return;
  705. }
  706. if (args[0] == "help" || args[0] == "--help" || args[0] == "-h"
  707. || args[0] == "-?" || args[0] == "/?")
  708. {
  709. printHelp();
  710. return;
  711. }
  712. if (args[0] == "version")
  713. {
  714. printVersion();
  715. return;
  716. }
  717. Console.WriteLine("Unknown command: " + args[0]);
  718. printAvailableCommandsInfo();
  719. throw new Exception("Unknown command: " + args[0]);
  720. }
  721. Run(new WrapperService());
  722. }
  723. private static void InitLoggers(ServiceDescriptor d, bool enableCLILogging)
  724. {
  725. Level logLevel = Level.Debug;
  726. // Legacy format from winsw-1.x: (DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " - " + message);
  727. PatternLayout pl = new PatternLayout { ConversionPattern = "%d %-5p - %m%n" };
  728. pl.ActivateOptions();
  729. // wrapper.log
  730. String wrapperLogPath = Path.Combine(d.LogDirectory, d.BaseName + ".wrapper.log");
  731. var wrapperLog = new FileAppender
  732. {
  733. AppendToFile = true,
  734. File = wrapperLogPath,
  735. ImmediateFlush = true,
  736. Name = "Wrapper file log",
  737. Threshold = logLevel,
  738. LockingModel = new FileAppender.MinimalLock(),
  739. Layout = pl
  740. };
  741. wrapperLog.ActivateOptions();
  742. BasicConfigurator.Configure(wrapperLog);
  743. // Also display logs in CLI if required
  744. if (enableCLILogging)
  745. {
  746. var consoleAppender = new ConsoleAppender
  747. {
  748. Name = "Wrapper console log",
  749. Threshold = logLevel,
  750. Layout = pl
  751. };
  752. consoleAppender.ActivateOptions();
  753. ((Logger)Log.Logger).AddAppender(consoleAppender);
  754. }
  755. }
  756. private static string ReadPassword()
  757. {
  758. StringBuilder buf = new StringBuilder();
  759. ConsoleKeyInfo key;
  760. while (true)
  761. {
  762. key = Console.ReadKey(true);
  763. if (key.Key == ConsoleKey.Enter)
  764. {
  765. return buf.ToString();
  766. }
  767. else if (key.Key == ConsoleKey.Backspace)
  768. {
  769. buf.Remove(buf.Length - 1, 1);
  770. Console.Write("\b \b");
  771. }
  772. else
  773. {
  774. Console.Write('*');
  775. buf.Append(key.KeyChar);
  776. }
  777. }
  778. }
  779. private static void printHelp()
  780. {
  781. Console.WriteLine("A wrapper binary that can be used to host executables as Windows services");
  782. Console.WriteLine("");
  783. Console.WriteLine("Usage: winsw [/redirect file] <command> [<args>]");
  784. Console.WriteLine(" Missing arguments trigger the service mode");
  785. Console.WriteLine("");
  786. printAvailableCommandsInfo();
  787. Console.WriteLine("");
  788. Console.WriteLine("Extra options:");
  789. Console.WriteLine("- '/redirect' - redirect the wrapper's STDOUT and STDERR to the specified file");
  790. Console.WriteLine("");
  791. printVersion();
  792. Console.WriteLine("More info: https://github.com/kohsuke/winsw");
  793. Console.WriteLine("Bug tracker: https://github.com/kohsuke/winsw/issues");
  794. }
  795. //TODO: Rework to enum in winsw-2.0
  796. private static void printAvailableCommandsInfo()
  797. {
  798. Console.WriteLine("Available commands:");
  799. Console.WriteLine("- 'install' - install the service to Windows Service Controller");
  800. Console.WriteLine("- 'uninstall' - uninstall the service");
  801. Console.WriteLine("- 'start' - start the service (must be installed before)");
  802. Console.WriteLine("- 'stop' - stop the service");
  803. Console.WriteLine("- 'restart' - restart the service");
  804. Console.WriteLine("- 'restart!' - self-restart (can be called from child processes)");
  805. Console.WriteLine("- 'status' - check the current status of the service");
  806. Console.WriteLine("- 'test' - check if the service can be started and then stopped");
  807. Console.WriteLine("- 'version' - print the version info");
  808. Console.WriteLine("- 'help' - print the help info (aliases: -h,--help,-?,/?)");
  809. }
  810. private static void printVersion()
  811. {
  812. Console.WriteLine("WinSW " + Version);
  813. }
  814. }
  815. }