Main.cs 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005
  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 = Environment.ExpandEnvironmentVariables(n.Attributes["from"].Value);
  360. To = Environment.ExpandEnvironmentVariables(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. if(File.Exists(To))
  370. File.Delete(To);
  371. File.Move(To + ".tmp", To);
  372. }
  373. private static void CopyStream(Stream i, Stream o)
  374. {
  375. byte[] buf = new byte[8192];
  376. while (true)
  377. {
  378. int len = i.Read(buf, 0, buf.Length);
  379. if (len <= 0) break;
  380. o.Write(buf, 0, len);
  381. }
  382. i.Close();
  383. o.Close();
  384. }
  385. }
  386. public class WrapperService : ServiceBase
  387. {
  388. [DllImport("ADVAPI32.DLL", EntryPoint = "SetServiceStatus")]
  389. private static extern bool SetServiceStatus(IntPtr hServiceStatus, ref SERVICE_STATUS lpServiceStatus);
  390. private SERVICE_STATUS wrapperServiceStatus;
  391. private Process process = new Process();
  392. private ServiceDescriptor descriptor;
  393. private Dictionary<string, string> envs;
  394. /// <summary>
  395. /// Indicates to the watch dog thread that we are going to terminate the process,
  396. /// so don't try to kill us when the child exits.
  397. /// </summary>
  398. private bool orderlyShutdown;
  399. private bool systemShuttingdown;
  400. public WrapperService()
  401. {
  402. this.descriptor = new ServiceDescriptor();
  403. this.ServiceName = descriptor.Id;
  404. this.CanShutdown = true;
  405. this.CanStop = true;
  406. this.CanPauseAndContinue = false;
  407. this.AutoLog = true;
  408. this.systemShuttingdown = false;
  409. }
  410. /// <summary>
  411. /// Copy stuff from StreamReader to StreamWriter
  412. /// </summary>
  413. private void CopyStream(StreamReader i, StreamWriter o)
  414. {
  415. char[] buf = new char[1024];
  416. while (true)
  417. {
  418. int sz = i.Read(buf, 0, buf.Length);
  419. if (sz == 0) break;
  420. o.Write(buf, 0, sz);
  421. o.Flush();
  422. }
  423. i.Close();
  424. o.Close();
  425. }
  426. /// <summary>
  427. /// Works like the CopyStream method but does a log rotation.
  428. /// </summary>
  429. private void CopyStreamWithRotation(FileStream i, string baseName, string ext)
  430. {
  431. int THRESHOLD = 10 * 1024 * 1024; // rotate every 10MB. should be made configurable.
  432. byte[] buf = new byte[1024];
  433. FileStream w = new FileStream(baseName + ext,FileMode.Append);
  434. int sz = new FileInfo(baseName + ext).Length;
  435. while (true)
  436. {
  437. int len = i.Read(buf,0,buf.Length);
  438. if (len == 0) break;
  439. if (sz + len < THRESHOLD)
  440. {// typical case. write the whole thing into the current file
  441. w.Write(buf, 0, len);
  442. }
  443. else
  444. {
  445. // rotate at the line boundary
  446. int s = 0;
  447. for (int i = 0; i < len; i++)
  448. {
  449. if (buf[i] != 0x0A) continue;
  450. if (sz + i < THRESHOLD) continue;
  451. // at the line boundary and exceeded the rotation unit.
  452. // time to rotate.
  453. w.Write(buf, s, i + 1);
  454. w.Close();
  455. s = i + 1;
  456. try
  457. {
  458. for (int j = 8; j >= 0; j--)
  459. {
  460. string d = baseName + "." + (j + 1) + ext;
  461. string s = baseName + "." + (j + 0) + ext;
  462. if (File.Exists(d))
  463. File.Delete(d);
  464. File.Move(s, d);
  465. }
  466. File.Move(baseName + ext, baseName + ".0" + ext);
  467. }
  468. catch (IOException e)
  469. {
  470. LogEvent("Failed to rotate log: " + e.Message);
  471. }
  472. w = new FileStream(baseName + ext, FileMode.Append);
  473. sz = new FileInfo(baseName + ext).Length;
  474. }
  475. }
  476. }
  477. i.Close();
  478. w.Close();
  479. }
  480. /// <summary>
  481. /// Process the file copy instructions, so that we can replace files that are always in use while
  482. /// the service runs.
  483. /// </summary>
  484. private void HandleFileCopies()
  485. {
  486. var file = descriptor.BasePath + ".copies";
  487. if (!File.Exists(file))
  488. return; // nothing to handle
  489. try
  490. {
  491. using (var tr = new StreamReader(file,Encoding.UTF8))
  492. {
  493. string line;
  494. while ((line = tr.ReadLine()) != null)
  495. {
  496. LogEvent("Handling copy: " + line);
  497. string[] tokens = line.Split('>');
  498. if (tokens.Length > 2)
  499. {
  500. LogEvent("Too many delimiters in " + line);
  501. continue;
  502. }
  503. CopyFile(tokens[0], tokens[1]);
  504. }
  505. }
  506. }
  507. finally
  508. {
  509. File.Delete(file);
  510. }
  511. }
  512. private void CopyFile(string sourceFileName, string destFileName)
  513. {
  514. try
  515. {
  516. File.Delete(destFileName);
  517. File.Move(sourceFileName, destFileName);
  518. }
  519. catch (IOException e)
  520. {
  521. LogEvent("Failed to copy :" + sourceFileName + " to " + destFileName + " because " + e.Message);
  522. }
  523. }
  524. /// <summary>
  525. /// Handle the creation of the logfiles based on the optional logmode setting.
  526. /// </summary>
  527. private void HandleLogfiles()
  528. {
  529. string logDirectory = descriptor.LogDirectory;
  530. if (!Directory.Exists(logDirectory))
  531. {
  532. Directory.CreateDirectory(logDirectory);
  533. }
  534. string baseName = descriptor.BaseName;
  535. string errorLogfilename = Path.Combine(logDirectory, baseName + ".err.log");
  536. string outputLogfilename = Path.Combine(logDirectory, baseName + ".out.log");
  537. if (descriptor.Logmode == "rotate")
  538. {
  539. string logName = Path.Combine(logDirectory, baseName);
  540. new Thread(delegate() { CopyStreamWithRotation(process.StandardOutput, logName, ".out.log"); }).Start();
  541. new Thread(delegate() { CopyStreamWithRotation(process.StandardError, logName, ".err.log"); }).Start();
  542. return;
  543. }
  544. System.IO.FileMode fileMode = FileMode.Append;
  545. if (descriptor.Logmode == "reset")
  546. {
  547. fileMode = FileMode.Create;
  548. }
  549. else if (descriptor.Logmode == "roll")
  550. {
  551. CopyFile(outputLogfilename, outputLogfilename + ".old");
  552. CopyFile(errorLogfilename, errorLogfilename + ".old");
  553. }
  554. new Thread(delegate() { CopyStream(process.StandardOutput, new StreamWriter(new FileStream(outputLogfilename, fileMode))); }).Start();
  555. new Thread(delegate() { CopyStream(process.StandardError, new StreamWriter(new FileStream(errorLogfilename, fileMode))); }).Start();
  556. }
  557. private void LogEvent(String message)
  558. {
  559. if (systemShuttingdown)
  560. {
  561. /* NOP - cannot call EventLog because of shutdown. */
  562. }
  563. else
  564. {
  565. EventLog.WriteEntry(message);
  566. }
  567. }
  568. private void LogEvent(String message, EventLogEntryType type)
  569. {
  570. if (systemShuttingdown)
  571. {
  572. /* NOP - cannot call EventLog because of shutdown. */
  573. }
  574. else
  575. {
  576. EventLog.WriteEntry(message, type);
  577. }
  578. }
  579. private void WriteEvent(String message, Exception exception)
  580. {
  581. WriteEvent(message + "\nMessage:" + exception.Message + "\nStacktrace:" + exception.StackTrace);
  582. }
  583. private void WriteEvent(String message)
  584. {
  585. string logfilename = Path.Combine(descriptor.LogDirectory, descriptor.BaseName + ".wrapper.log");
  586. StreamWriter log = new StreamWriter(logfilename, true);
  587. log.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " - " + message);
  588. log.Flush();
  589. log.Close();
  590. }
  591. protected override void OnStart(string[] args)
  592. {
  593. envs = descriptor.EnvironmentVariables;
  594. foreach (string key in envs.Keys)
  595. {
  596. LogEvent("envar " + key + '=' + envs[key]);
  597. }
  598. HandleFileCopies();
  599. // handle downloads
  600. foreach (Download d in descriptor.Downloads)
  601. {
  602. LogEvent("Downloading: " + d.From+ " to "+d.To);
  603. try
  604. {
  605. d.Perform();
  606. }
  607. catch (Exception e)
  608. {
  609. LogEvent("Failed to download " + d.From + " to " + d.To + "\n" + e.Message);
  610. WriteEvent("Failed to download " + d.From +" to "+d.To, e);
  611. // but just keep going
  612. }
  613. }
  614. string startarguments = descriptor.Startarguments;
  615. if (startarguments == null)
  616. {
  617. startarguments = descriptor.Arguments;
  618. }
  619. else
  620. {
  621. startarguments += " " + descriptor.Arguments;
  622. }
  623. LogEvent("Starting " + descriptor.Executable + ' ' + startarguments);
  624. WriteEvent("Starting " + descriptor.Executable + ' ' + startarguments);
  625. StartProcess(process, startarguments, descriptor.Executable);
  626. // send stdout and stderr to its respective output file.
  627. HandleLogfiles();
  628. process.StandardInput.Close(); // nothing for you to read!
  629. }
  630. protected override void OnShutdown()
  631. {
  632. // WriteEvent("OnShutdown");
  633. try
  634. {
  635. this.systemShuttingdown = true;
  636. StopIt();
  637. }
  638. catch (Exception ex)
  639. {
  640. WriteEvent("Shutdown exception", ex);
  641. }
  642. }
  643. protected override void OnStop()
  644. {
  645. // WriteEvent("OnStop");
  646. try
  647. {
  648. StopIt();
  649. }
  650. catch (Exception ex)
  651. {
  652. WriteEvent("Stop exception", ex);
  653. }
  654. }
  655. private void StopIt()
  656. {
  657. string stoparguments = descriptor.Stoparguments;
  658. LogEvent("Stopping " + descriptor.Id);
  659. WriteEvent("Stopping " + descriptor.Id);
  660. orderlyShutdown = true;
  661. if (stoparguments == null)
  662. {
  663. try
  664. {
  665. WriteEvent("ProcessKill " + process.Id);
  666. process.Kill();
  667. }
  668. catch (InvalidOperationException)
  669. {
  670. // already terminated
  671. }
  672. }
  673. else
  674. {
  675. SignalShutdownPending();
  676. stoparguments += " " + descriptor.Arguments;
  677. Process stopProcess = new Process();
  678. String executable = descriptor.StopExecutable;
  679. if (executable == null)
  680. {
  681. executable = descriptor.Executable;
  682. }
  683. StartProcess(stopProcess, stoparguments, executable);
  684. WriteEvent("WaitForProcessToExit "+process.Id+"+"+stopProcess.Id);
  685. WaitForProcessToExit(process);
  686. WaitForProcessToExit(stopProcess);
  687. SignalShutdownComplete();
  688. }
  689. if (systemShuttingdown && descriptor.BeepOnShutdown)
  690. {
  691. Console.Beep();
  692. }
  693. WriteEvent("Finished " + descriptor.Id);
  694. }
  695. private void WaitForProcessToExit(Process process)
  696. {
  697. SignalShutdownPending();
  698. try
  699. {
  700. // WriteEvent("WaitForProcessToExit [start]");
  701. while (!process.WaitForExit(descriptor.SleepTime))
  702. {
  703. SignalShutdownPending();
  704. // WriteEvent("WaitForProcessToExit [repeat]");
  705. }
  706. }
  707. catch (InvalidOperationException)
  708. {
  709. // already terminated
  710. }
  711. // WriteEvent("WaitForProcessToExit [finished]");
  712. }
  713. private void SignalShutdownPending()
  714. {
  715. IntPtr handle = this.ServiceHandle;
  716. wrapperServiceStatus.checkPoint++;
  717. wrapperServiceStatus.waitHint = descriptor.WaitHint;
  718. // WriteEvent("SignalShutdownPending " + wrapperServiceStatus.checkPoint + ":" + wrapperServiceStatus.waitHint);
  719. wrapperServiceStatus.currentState = (int)State.SERVICE_STOP_PENDING;
  720. SetServiceStatus(handle, ref wrapperServiceStatus);
  721. }
  722. private void SignalShutdownComplete()
  723. {
  724. IntPtr handle = this.ServiceHandle;
  725. wrapperServiceStatus.checkPoint++;
  726. // WriteEvent("SignalShutdownComplete " + wrapperServiceStatus.checkPoint + ":" + wrapperServiceStatus.waitHint);
  727. wrapperServiceStatus.currentState = (int)State.SERVICE_STOPPED;
  728. SetServiceStatus(handle, ref wrapperServiceStatus);
  729. }
  730. private void StartProcess(Process process, string arguments, String executable)
  731. {
  732. var ps = process.StartInfo;
  733. ps.FileName = executable;
  734. ps.Arguments = arguments;
  735. ps.CreateNoWindow = false;
  736. ps.UseShellExecute = false;
  737. ps.RedirectStandardInput = true; // this creates a pipe for stdin to the new process, instead of having it inherit our stdin.
  738. ps.RedirectStandardOutput = true;
  739. ps.RedirectStandardError = true;
  740. foreach (string key in envs.Keys)
  741. System.Environment.SetEnvironmentVariable(key, envs[key]);
  742. // 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)
  743. process.Start();
  744. WriteEvent("Started " + process.Id);
  745. // monitor the completion of the process
  746. new Thread(delegate()
  747. {
  748. string msg = process.Id + " - " + process.StartInfo.FileName + " " + process.StartInfo.Arguments;
  749. process.WaitForExit();
  750. try
  751. {
  752. if (orderlyShutdown)
  753. {
  754. LogEvent("Child process [" + msg + "] terminated with " + process.ExitCode, EventLogEntryType.Information);
  755. }
  756. else
  757. {
  758. LogEvent("Child process [" + msg + "] terminated with " + process.ExitCode, EventLogEntryType.Warning);
  759. Environment.Exit(process.ExitCode);
  760. }
  761. }
  762. catch (InvalidOperationException ioe)
  763. {
  764. LogEvent("WaitForExit " + ioe.Message);
  765. }
  766. try
  767. {
  768. process.Dispose();
  769. }
  770. catch (InvalidOperationException ioe)
  771. {
  772. LogEvent("Dispose " + ioe.Message);
  773. }
  774. }).Start();
  775. }
  776. public static int Main(string[] args)
  777. {
  778. try
  779. {
  780. Run(args);
  781. return 0;
  782. }
  783. catch (WmiException e)
  784. {
  785. Console.Error.WriteLine(e);
  786. return (int)e.ErrorCode;
  787. }
  788. catch (Exception e)
  789. {
  790. Console.Error.WriteLine(e);
  791. return -1;
  792. }
  793. }
  794. private static void ThrowNoSuchService()
  795. {
  796. throw new WmiException(ReturnValue.NoSuchService);
  797. }
  798. public static void Run(string[] args)
  799. {
  800. if (args.Length > 0)
  801. {
  802. var d = new ServiceDescriptor();
  803. Win32Services svc = new WmiRoot().GetCollection<Win32Services>();
  804. Win32Service s = svc.Select(d.Id);
  805. args[0] = args[0].ToLower();
  806. if (args[0] == "install")
  807. {
  808. svc.Create(
  809. d.Id,
  810. d.Caption,
  811. ServiceDescriptor.ExecutablePath,
  812. WMI.ServiceType.OwnProcess,
  813. ErrorControl.UserNotified,
  814. StartMode.Automatic,
  815. d.Interactive,
  816. d.ServiceDependencies);
  817. // update the description
  818. /* Somehow this doesn't work, even though it doesn't report an error
  819. Win32Service s = svc.Select(d.Id);
  820. s.Description = d.Description;
  821. s.Commit();
  822. */
  823. // so using a classic method to set the description. Ugly.
  824. Registry.LocalMachine.OpenSubKey("System").OpenSubKey("CurrentControlSet").OpenSubKey("Services")
  825. .OpenSubKey(d.Id, true).SetValue("Description", d.Description);
  826. }
  827. if (args[0] == "uninstall")
  828. {
  829. if (s == null)
  830. return; // there's no such service, so consider it already uninstalled
  831. try
  832. {
  833. s.Delete();
  834. }
  835. catch (WmiException e)
  836. {
  837. if (e.ErrorCode == ReturnValue.ServiceMarkedForDeletion)
  838. return; // it's already uninstalled, so consider it a success
  839. throw e;
  840. }
  841. }
  842. if (args[0] == "start")
  843. {
  844. if (s == null) ThrowNoSuchService();
  845. s.StartService();
  846. }
  847. if (args[0] == "stop")
  848. {
  849. if (s == null) ThrowNoSuchService();
  850. s.StopService();
  851. }
  852. if (args[0] == "restart")
  853. {
  854. if (s == null)
  855. ThrowNoSuchService();
  856. if(s.Started)
  857. s.StopService();
  858. while (s.Started)
  859. {
  860. Thread.Sleep(1000);
  861. s = svc.Select(d.Id);
  862. }
  863. s.StartService();
  864. }
  865. if (args[0] == "status")
  866. {
  867. if (s == null)
  868. Console.WriteLine("NonExistent");
  869. else if (s.Started)
  870. Console.WriteLine("Started");
  871. else
  872. Console.WriteLine("Stopped");
  873. }
  874. if (args[0] == "test")
  875. {
  876. WrapperService wsvc = new WrapperService();
  877. wsvc.OnStart(args);
  878. Thread.Sleep(1000);
  879. wsvc.OnStop();
  880. }
  881. return;
  882. }
  883. ServiceBase.Run(new WrapperService());
  884. }
  885. }
  886. }