Main.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Data;
  5. using System.Diagnostics;
  6. using System.Runtime.InteropServices;
  7. using System.ServiceProcess;
  8. using System.Text;
  9. using System.IO;
  10. using System.Net;
  11. using WMI;
  12. using System.Xml;
  13. using System.Threading;
  14. using Microsoft.Win32;
  15. namespace winsw
  16. {
  17. public struct SERVICE_STATUS
  18. {
  19. public int serviceType;
  20. public int currentState;
  21. public int controlsAccepted;
  22. public int win32ExitCode;
  23. public int serviceSpecificExitCode;
  24. public int checkPoint;
  25. public int waitHint;
  26. }
  27. public enum State
  28. {
  29. SERVICE_STOPPED = 0x00000001,
  30. SERVICE_START_PENDING = 0x00000002,
  31. SERVICE_STOP_PENDING = 0x00000003,
  32. SERVICE_RUNNING = 0x00000004,
  33. SERVICE_CONTINUE_PENDING = 0x00000005,
  34. SERVICE_PAUSE_PENDING = 0x00000006,
  35. SERVICE_PAUSED = 0x00000007,
  36. }
  37. /// <summary>
  38. /// In-memory representation of the configuration file.
  39. /// </summary>
  40. public class ServiceDescriptor
  41. {
  42. private readonly XmlDocument dom = new XmlDocument();
  43. /// <summary>
  44. /// Where did we find the configuration file?
  45. ///
  46. /// This string is "c:\abc\def\ghi" when the configuration XML is "c:\abc\def\ghi.xml"
  47. /// </summary>
  48. public readonly string BasePath;
  49. /// <summary>
  50. /// The file name portion of the configuration file.
  51. ///
  52. /// In the above example, this would be "ghi".
  53. /// </summary>
  54. public readonly string BaseName;
  55. public static string ExecutablePath
  56. {
  57. get
  58. {
  59. // this returns the executable name as given by the calling process, so
  60. // it needs to be absolutized.
  61. string p = Environment.GetCommandLineArgs()[0];
  62. return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, p);
  63. }
  64. }
  65. public ServiceDescriptor()
  66. {
  67. // find co-located configuration xml. We search up to the ancestor directories to simplify debugging,
  68. // as well as trimming off ".vshost" suffix (which is used during debugging)
  69. string p = ExecutablePath;
  70. string baseName = Path.GetFileNameWithoutExtension(p);
  71. if (baseName.EndsWith(".vshost")) baseName = baseName.Substring(0, baseName.Length - 7);
  72. while (true)
  73. {
  74. p = Path.GetDirectoryName(p);
  75. if (File.Exists(Path.Combine(p, baseName + ".xml")))
  76. break;
  77. }
  78. // register the base directory as environment variable so that future expansions can refer to this.
  79. Environment.SetEnvironmentVariable("BASE", p);
  80. BaseName = baseName;
  81. BasePath = Path.Combine(p, BaseName);
  82. dom.Load(BasePath+".xml");
  83. }
  84. private string SingleElement(string tagName)
  85. {
  86. var n = dom.SelectSingleNode("//" + tagName);
  87. if (n == null) throw new InvalidDataException("<" + tagName + "> is missing in configuration XML");
  88. return Environment.ExpandEnvironmentVariables(n.InnerText);
  89. }
  90. /// <summary>
  91. /// Path to the executable.
  92. /// </summary>
  93. public string Executable
  94. {
  95. get
  96. {
  97. return SingleElement("executable");
  98. }
  99. }
  100. /// <summary>
  101. /// Optionally specify a different Path to an executable to shutdown the service.
  102. /// </summary>
  103. public string StopExecutable
  104. {
  105. get
  106. {
  107. return AppendTags("stopexecutable");
  108. }
  109. }
  110. /// <summary>
  111. /// Arguments or multiple optional argument elements which overrule the arguments element.
  112. /// </summary>
  113. public string Arguments
  114. {
  115. get
  116. {
  117. string arguments = AppendTags("argument");
  118. if (arguments == null)
  119. {
  120. var tagName = "arguments";
  121. var argumentsNode = dom.SelectSingleNode("//" + tagName);
  122. if (argumentsNode == null)
  123. {
  124. if (AppendTags("startargument") == null)
  125. {
  126. throw new InvalidDataException("<" + tagName + "> is missing in configuration XML");
  127. }
  128. else
  129. {
  130. return "";
  131. }
  132. }
  133. return Environment.ExpandEnvironmentVariables(argumentsNode.InnerText);
  134. }
  135. else
  136. {
  137. return arguments;
  138. }
  139. }
  140. }
  141. /// <summary>
  142. /// Multiple optional startargument elements.
  143. /// </summary>
  144. public string Startarguments
  145. {
  146. get
  147. {
  148. return AppendTags("startargument");
  149. }
  150. }
  151. /// <summary>
  152. /// Multiple optional stopargument elements.
  153. /// </summary>
  154. public string Stoparguments
  155. {
  156. get
  157. {
  158. return AppendTags("stopargument");
  159. }
  160. }
  161. /// <summary>
  162. /// Combines the contents of all the elements of the given name,
  163. /// or return null if no element exists.
  164. /// </summary>
  165. private string AppendTags(string tagName)
  166. {
  167. XmlNode argumentNode = dom.SelectSingleNode("//" + tagName);
  168. if (argumentNode == null)
  169. {
  170. return null;
  171. }
  172. else
  173. {
  174. string arguments = "";
  175. foreach (XmlNode argument in dom.SelectNodes("//" + tagName))
  176. {
  177. arguments += " " + argument.InnerText;
  178. }
  179. return Environment.ExpandEnvironmentVariables(arguments);
  180. }
  181. }
  182. /// <summary>
  183. /// LogDirectory is the service wrapper executable directory or the optionally specified logpath element.
  184. /// </summary>
  185. public string LogDirectory
  186. {
  187. get
  188. {
  189. XmlNode loggingNode = dom.SelectSingleNode("//logpath");
  190. if (loggingNode != null)
  191. {
  192. return loggingNode.InnerText;
  193. }
  194. else
  195. {
  196. return Path.GetDirectoryName(ExecutablePath);
  197. }
  198. }
  199. }
  200. /// <summary>
  201. /// Logmode to 'reset', 'roll' once or 'append' [default] the out.log and err.log files.
  202. /// </summary>
  203. public string Logmode
  204. {
  205. get
  206. {
  207. XmlNode logmodeNode = dom.SelectSingleNode("//logmode");
  208. if (logmodeNode == null)
  209. {
  210. return "append";
  211. }
  212. else
  213. {
  214. return logmodeNode.InnerText;
  215. }
  216. }
  217. }
  218. /// <summary>
  219. /// Optionally specified depend services that must start before this service starts.
  220. /// </summary>
  221. public string[] ServiceDependencies
  222. {
  223. get
  224. {
  225. System.Collections.ArrayList serviceDependencies = new System.Collections.ArrayList();
  226. foreach (XmlNode depend in dom.SelectNodes("//depend"))
  227. {
  228. serviceDependencies.Add(depend.InnerText);
  229. }
  230. return (string[])serviceDependencies.ToArray(typeof(string));
  231. }
  232. }
  233. public string Id
  234. {
  235. get
  236. {
  237. return SingleElement("id");
  238. }
  239. }
  240. public string Caption
  241. {
  242. get
  243. {
  244. return SingleElement("name");
  245. }
  246. }
  247. public string Description
  248. {
  249. get
  250. {
  251. return SingleElement("description");
  252. }
  253. }
  254. /// <summary>
  255. /// True if the service should when finished on shutdown.
  256. /// </summary>
  257. public bool BeepOnShutdown
  258. {
  259. get
  260. {
  261. return dom.SelectSingleNode("//beeponshutdown") != null;
  262. }
  263. }
  264. /// <summary>
  265. /// The estimated time required for a pending stop operation, in milliseconds (default 15 secs).
  266. /// Before the specified amount of time has elapsed, the service should make its next call to the SetServiceStatus function
  267. /// with either an incremented checkPoint value or a change in currentState. (see http://msdn.microsoft.com/en-us/library/ms685996.aspx)
  268. /// </summary>
  269. public int WaitHint
  270. {
  271. get
  272. {
  273. XmlNode waithintNode = dom.SelectSingleNode("//waithint");
  274. if (waithintNode == null)
  275. {
  276. return 15000;
  277. }
  278. else
  279. {
  280. return int.Parse(waithintNode.InnerText);
  281. }
  282. }
  283. }
  284. /// <summary>
  285. /// The time, in milliseconds (default 1 sec), before the service should make its next call to the SetServiceStatus function
  286. /// with an incremented checkPoint value.
  287. /// Do not wait longer than the wait hint. A good interval is one-tenth of the wait hint but not less than 1 second and not more than 10 seconds.
  288. /// </summary>
  289. public int SleepTime
  290. {
  291. get
  292. {
  293. XmlNode sleeptimeNode = dom.SelectSingleNode("//sleeptime");
  294. if (sleeptimeNode == null)
  295. {
  296. return 1000;
  297. }
  298. else
  299. {
  300. return int.Parse(sleeptimeNode.InnerText);
  301. }
  302. }
  303. }
  304. /// <summary>
  305. /// True if the service can interact with the desktop.
  306. /// </summary>
  307. public bool Interactive
  308. {
  309. get
  310. {
  311. return dom.SelectSingleNode("//interactive") != null;
  312. }
  313. }
  314. /// <summary>
  315. /// Environment variable overrides
  316. /// </summary>
  317. public Dictionary<string, string> EnvironmentVariables
  318. {
  319. get
  320. {
  321. Dictionary<string, string> map = new Dictionary<string, string>();
  322. foreach (XmlNode n in dom.SelectNodes("//env"))
  323. {
  324. string key = n.Attributes["name"].Value;
  325. string value = Environment.ExpandEnvironmentVariables(n.Attributes["value"].Value);
  326. map[key] = value;
  327. Environment.SetEnvironmentVariable(key, value);
  328. }
  329. return map;
  330. }
  331. }
  332. /// <summary>
  333. /// List of downloads to be performed by the wrapper before starting
  334. /// a service.
  335. /// </summary>
  336. public List<Download> Downloads
  337. {
  338. get
  339. {
  340. List<Download> r = new List<Download>();
  341. foreach (XmlNode n in dom.SelectNodes("//download"))
  342. {
  343. r.Add(new Download(n));
  344. }
  345. return r;
  346. }
  347. }
  348. }
  349. /// <summary>
  350. /// Specify the download activities prior to the launch.
  351. /// This enables self-updating services.
  352. /// </summary>
  353. public class Download
  354. {
  355. public readonly string From;
  356. public readonly string To;
  357. internal Download(XmlNode n)
  358. {
  359. From = n.Attributes["from"].Value;
  360. To = n.Attributes["to"].Value;
  361. }
  362. public void Perform()
  363. {
  364. WebRequest req = WebRequest.Create(From);
  365. WebResponse rsp = req.GetResponse();
  366. FileStream tmpstream = new FileStream(To+".tmp", FileMode.Create);
  367. CopyStream(rsp.GetResponseStream(), tmpstream);
  368. // only after we successfully downloaded a file, overwrite the existing one
  369. File.Delete(To);
  370. File.Move(To + ".tmp", To);
  371. }
  372. private static void CopyStream(Stream i, Stream o)
  373. {
  374. byte[] buf = new byte[8192];
  375. while (true)
  376. {
  377. int len = i.Read(buf, 0, buf.Length);
  378. if (len <= 0) return;
  379. o.Write(buf, 0, len);
  380. }
  381. i.Close();
  382. o.Close();
  383. }
  384. }
  385. public class WrapperService : ServiceBase
  386. {
  387. [DllImport("ADVAPI32.DLL", EntryPoint = "SetServiceStatus")]
  388. private static extern bool SetServiceStatus(IntPtr hServiceStatus, ref SERVICE_STATUS lpServiceStatus);
  389. private SERVICE_STATUS wrapperServiceStatus;
  390. private Process process = new Process();
  391. private ServiceDescriptor descriptor;
  392. private Dictionary<string, string> envs;
  393. /// <summary>
  394. /// Indicates to the watch dog thread that we are going to terminate the process,
  395. /// so don't try to kill us when the child exits.
  396. /// </summary>
  397. private bool orderlyShutdown;
  398. private bool systemShuttingdown;
  399. public WrapperService()
  400. {
  401. this.descriptor = new ServiceDescriptor();
  402. this.ServiceName = descriptor.Id;
  403. this.CanShutdown = true;
  404. this.CanStop = true;
  405. this.CanPauseAndContinue = false;
  406. this.AutoLog = true;
  407. this.systemShuttingdown = false;
  408. }
  409. /// <summary>
  410. /// Copy stuff from StreamReader to StreamWriter
  411. /// </summary>
  412. private void CopyStream(StreamReader i, StreamWriter o)
  413. {
  414. char[] buf = new char[1024];
  415. while (true)
  416. {
  417. int sz = i.Read(buf, 0, buf.Length);
  418. if (sz == 0) break;
  419. o.Write(buf, 0, sz);
  420. o.Flush();
  421. }
  422. i.Close();
  423. o.Close();
  424. }
  425. /// <summary>
  426. /// Process the file copy instructions, so that we can replace files that are always in use while
  427. /// the service runs.
  428. /// </summary>
  429. private void HandleFileCopies()
  430. {
  431. var file = descriptor.BasePath + ".copies";
  432. if (!File.Exists(file))
  433. return; // nothing to handle
  434. try
  435. {
  436. using (var tr = new StreamReader(file,Encoding.UTF8))
  437. {
  438. string line;
  439. while ((line = tr.ReadLine()) != null)
  440. {
  441. LogEvent("Handling copy: " + line);
  442. string[] tokens = line.Split('>');
  443. if (tokens.Length > 2)
  444. {
  445. LogEvent("Too many delimiters in " + line);
  446. continue;
  447. }
  448. CopyFile(tokens[0], tokens[1]);
  449. }
  450. }
  451. }
  452. finally
  453. {
  454. File.Delete(file);
  455. }
  456. }
  457. private void CopyFile(string sourceFileName, string destFileName)
  458. {
  459. try
  460. {
  461. File.Delete(destFileName);
  462. File.Move(sourceFileName, destFileName);
  463. }
  464. catch (IOException e)
  465. {
  466. LogEvent("Failed to copy :" + sourceFileName + " to " + destFileName + " because " + e.Message);
  467. }
  468. }
  469. /// <summary>
  470. /// Handle the creation of the logfiles based on the optional logmode setting.
  471. /// </summary>
  472. private void HandleLogfiles()
  473. {
  474. string logDirectory = descriptor.LogDirectory;
  475. if (!Directory.Exists(logDirectory))
  476. {
  477. Directory.CreateDirectory(logDirectory);
  478. }
  479. string baseName = descriptor.BaseName;
  480. string errorLogfilename = Path.Combine(logDirectory, baseName + ".err.log");
  481. string outputLogfilename = Path.Combine(logDirectory, baseName + ".out.log");
  482. System.IO.FileMode fileMode = FileMode.Append;
  483. if (descriptor.Logmode == "reset")
  484. {
  485. fileMode = FileMode.Create;
  486. }
  487. else if (descriptor.Logmode == "roll")
  488. {
  489. CopyFile(outputLogfilename, outputLogfilename + ".old");
  490. CopyFile(errorLogfilename, errorLogfilename + ".old");
  491. }
  492. new Thread(delegate() { CopyStream(process.StandardOutput, new StreamWriter(new FileStream(outputLogfilename, fileMode))); }).Start();
  493. new Thread(delegate() { CopyStream(process.StandardError, new StreamWriter(new FileStream(errorLogfilename, fileMode))); }).Start();
  494. }
  495. private void LogEvent(String message)
  496. {
  497. if (systemShuttingdown)
  498. {
  499. /* NOP - cannot call EventLog because of shutdown. */
  500. }
  501. else
  502. {
  503. EventLog.WriteEntry(message);
  504. }
  505. }
  506. private void LogEvent(String message, EventLogEntryType type)
  507. {
  508. if (systemShuttingdown)
  509. {
  510. /* NOP - cannot call EventLog because of shutdown. */
  511. }
  512. else
  513. {
  514. EventLog.WriteEntry(message, type);
  515. }
  516. }
  517. private void WriteEvent(String message, Exception exception)
  518. {
  519. WriteEvent(message + "\nMessage:" + exception.Message + "\nStacktrace:" + exception.StackTrace);
  520. }
  521. private void WriteEvent(String message)
  522. {
  523. string logfilename = Path.Combine(descriptor.LogDirectory, descriptor.BaseName + ".wrapper.log");
  524. StreamWriter log = new StreamWriter(logfilename, true);
  525. log.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " - " + message);
  526. log.Flush();
  527. log.Close();
  528. }
  529. protected override void OnStart(string[] args)
  530. {
  531. envs = descriptor.EnvironmentVariables;
  532. foreach (string key in envs.Keys)
  533. {
  534. LogEvent("envar " + key + '=' + envs[key]);
  535. }
  536. HandleFileCopies();
  537. // handle downloads
  538. foreach (Download d in descriptor.Downloads)
  539. {
  540. LogEvent("Downloading: " + d.From+ " to "+d.To);
  541. try
  542. {
  543. d.Perform();
  544. }
  545. catch (Exception e)
  546. {
  547. LogEvent("Failed to download " + d.From, EventLogEntryType.Warning);
  548. // but just keep going
  549. }
  550. }
  551. string startarguments = descriptor.Startarguments;
  552. if (startarguments == null)
  553. {
  554. startarguments = descriptor.Arguments;
  555. }
  556. else
  557. {
  558. startarguments += " " + descriptor.Arguments;
  559. }
  560. LogEvent("Starting " + descriptor.Executable + ' ' + startarguments);
  561. WriteEvent("Starting " + descriptor.Executable + ' ' + startarguments);
  562. StartProcess(process, startarguments, descriptor.Executable);
  563. // send stdout and stderr to its respective output file.
  564. HandleLogfiles();
  565. process.StandardInput.Close(); // nothing for you to read!
  566. }
  567. protected override void OnShutdown()
  568. {
  569. // WriteEvent("OnShutdown");
  570. try
  571. {
  572. this.systemShuttingdown = true;
  573. StopIt();
  574. }
  575. catch (Exception ex)
  576. {
  577. WriteEvent("Shutdown exception", ex);
  578. }
  579. }
  580. protected override void OnStop()
  581. {
  582. // WriteEvent("OnStop");
  583. try
  584. {
  585. StopIt();
  586. }
  587. catch (Exception ex)
  588. {
  589. WriteEvent("Stop exception", ex);
  590. }
  591. }
  592. private void StopIt()
  593. {
  594. string stoparguments = descriptor.Stoparguments;
  595. LogEvent("Stopping " + descriptor.Id);
  596. WriteEvent("Stopping " + descriptor.Id);
  597. orderlyShutdown = true;
  598. if (stoparguments == null)
  599. {
  600. try
  601. {
  602. WriteEvent("ProcessKill " + process.Id);
  603. process.Kill();
  604. }
  605. catch (InvalidOperationException)
  606. {
  607. // already terminated
  608. }
  609. }
  610. else
  611. {
  612. SignalShutdownPending();
  613. stoparguments += " " + descriptor.Arguments;
  614. Process stopProcess = new Process();
  615. String executable = descriptor.StopExecutable;
  616. if (executable == null)
  617. {
  618. executable = descriptor.Executable;
  619. }
  620. StartProcess(stopProcess, stoparguments, executable);
  621. WriteEvent("WaitForProcessToExit "+process.Id+"+"+stopProcess.Id);
  622. WaitForProcessToExit(process);
  623. WaitForProcessToExit(stopProcess);
  624. SignalShutdownComplete();
  625. }
  626. if (systemShuttingdown && descriptor.BeepOnShutdown)
  627. {
  628. Console.Beep();
  629. }
  630. WriteEvent("Finished " + descriptor.Id);
  631. }
  632. private void WaitForProcessToExit(Process process)
  633. {
  634. SignalShutdownPending();
  635. try
  636. {
  637. // WriteEvent("WaitForProcessToExit [start]");
  638. while (!process.WaitForExit(descriptor.SleepTime))
  639. {
  640. SignalShutdownPending();
  641. // WriteEvent("WaitForProcessToExit [repeat]");
  642. }
  643. }
  644. catch (InvalidOperationException)
  645. {
  646. // already terminated
  647. }
  648. // WriteEvent("WaitForProcessToExit [finished]");
  649. }
  650. private void SignalShutdownPending()
  651. {
  652. IntPtr handle = this.ServiceHandle;
  653. wrapperServiceStatus.checkPoint++;
  654. wrapperServiceStatus.waitHint = descriptor.WaitHint;
  655. // WriteEvent("SignalShutdownPending " + wrapperServiceStatus.checkPoint + ":" + wrapperServiceStatus.waitHint);
  656. wrapperServiceStatus.currentState = (int)State.SERVICE_STOP_PENDING;
  657. SetServiceStatus(handle, ref wrapperServiceStatus);
  658. }
  659. private void SignalShutdownComplete()
  660. {
  661. IntPtr handle = this.ServiceHandle;
  662. wrapperServiceStatus.checkPoint++;
  663. // WriteEvent("SignalShutdownComplete " + wrapperServiceStatus.checkPoint + ":" + wrapperServiceStatus.waitHint);
  664. wrapperServiceStatus.currentState = (int)State.SERVICE_STOPPED;
  665. SetServiceStatus(handle, ref wrapperServiceStatus);
  666. }
  667. private void StartProcess(Process process, string arguments, String executable)
  668. {
  669. var ps = process.StartInfo;
  670. ps.FileName = executable;
  671. ps.Arguments = arguments;
  672. ps.CreateNoWindow = false;
  673. ps.UseShellExecute = false;
  674. ps.RedirectStandardInput = true; // this creates a pipe for stdin to the new process, instead of having it inherit our stdin.
  675. ps.RedirectStandardOutput = true;
  676. ps.RedirectStandardError = true;
  677. foreach (string key in envs.Keys)
  678. System.Environment.SetEnvironmentVariable(key, envs[key]);
  679. // 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)
  680. process.Start();
  681. WriteEvent("Started " + process.Id);
  682. // monitor the completion of the process
  683. new Thread(delegate()
  684. {
  685. string msg = process.Id + " - " + process.StartInfo.FileName + " " + process.StartInfo.Arguments;
  686. process.WaitForExit();
  687. try
  688. {
  689. if (orderlyShutdown)
  690. {
  691. LogEvent("Child process [" + msg + "] terminated with " + process.ExitCode, EventLogEntryType.Information);
  692. }
  693. else
  694. {
  695. LogEvent("Child process [" + msg + "] terminated with " + process.ExitCode, EventLogEntryType.Warning);
  696. Environment.Exit(process.ExitCode);
  697. }
  698. }
  699. catch (InvalidOperationException ioe)
  700. {
  701. LogEvent("WaitForExit " + ioe.Message);
  702. }
  703. try
  704. {
  705. process.Dispose();
  706. }
  707. catch (InvalidOperationException ioe)
  708. {
  709. LogEvent("Dispose " + ioe.Message);
  710. }
  711. }).Start();
  712. }
  713. public static int Main(string[] args)
  714. {
  715. try
  716. {
  717. Run(args);
  718. return 0;
  719. }
  720. catch (WmiException e)
  721. {
  722. Console.Error.WriteLine(e);
  723. return (int)e.ErrorCode;
  724. }
  725. catch (Exception e)
  726. {
  727. Console.Error.WriteLine(e);
  728. return -1;
  729. }
  730. }
  731. private static void ThrowNoSuchService()
  732. {
  733. throw new WmiException(ReturnValue.NoSuchService);
  734. }
  735. public static void Run(string[] args)
  736. {
  737. if (args.Length > 0)
  738. {
  739. var d = new ServiceDescriptor();
  740. Win32Services svc = new WmiRoot().GetCollection<Win32Services>();
  741. Win32Service s = svc.Select(d.Id);
  742. args[0] = args[0].ToLower();
  743. if (args[0] == "install")
  744. {
  745. svc.Create(
  746. d.Id,
  747. d.Caption,
  748. ServiceDescriptor.ExecutablePath,
  749. WMI.ServiceType.OwnProcess,
  750. ErrorControl.UserNotified,
  751. StartMode.Automatic,
  752. d.Interactive,
  753. d.ServiceDependencies);
  754. // update the description
  755. /* Somehow this doesn't work, even though it doesn't report an error
  756. Win32Service s = svc.Select(d.Id);
  757. s.Description = d.Description;
  758. s.Commit();
  759. */
  760. // so using a classic method to set the description. Ugly.
  761. Registry.LocalMachine.OpenSubKey("System").OpenSubKey("CurrentControlSet").OpenSubKey("Services")
  762. .OpenSubKey(d.Id, true).SetValue("Description", d.Description);
  763. }
  764. if (args[0] == "uninstall")
  765. {
  766. if (s == null)
  767. return; // there's no such service, so consider it already uninstalled
  768. try
  769. {
  770. s.Delete();
  771. }
  772. catch (WmiException e)
  773. {
  774. if (e.ErrorCode == ReturnValue.ServiceMarkedForDeletion)
  775. return; // it's already uninstalled, so consider it a success
  776. throw e;
  777. }
  778. }
  779. if (args[0] == "start")
  780. {
  781. if (s == null) ThrowNoSuchService();
  782. s.StartService();
  783. }
  784. if (args[0] == "stop")
  785. {
  786. if (s == null) ThrowNoSuchService();
  787. s.StopService();
  788. }
  789. if (args[0] == "restart")
  790. {
  791. if (s == null)
  792. ThrowNoSuchService();
  793. if(s.Started)
  794. s.StopService();
  795. while (s.Started)
  796. {
  797. Thread.Sleep(1000);
  798. s = svc.Select(d.Id);
  799. }
  800. s.StartService();
  801. }
  802. if (args[0] == "status")
  803. {
  804. if (s == null)
  805. Console.WriteLine("NonExistent");
  806. else if (s.Started)
  807. Console.WriteLine("Started");
  808. else
  809. Console.WriteLine("Stopped");
  810. }
  811. if (args[0] == "test")
  812. {
  813. WrapperService wsvc = new WrapperService();
  814. wsvc.OnStart(args);
  815. Thread.Sleep(1000);
  816. wsvc.OnStop();
  817. }
  818. return;
  819. }
  820. ServiceBase.Run(new WrapperService());
  821. }
  822. }
  823. }