Main.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937
  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. /// Process the file copy instructions, so that we can replace files that are always in use while
  428. /// the service runs.
  429. /// </summary>
  430. private void HandleFileCopies()
  431. {
  432. var file = descriptor.BasePath + ".copies";
  433. if (!File.Exists(file))
  434. return; // nothing to handle
  435. try
  436. {
  437. using (var tr = new StreamReader(file,Encoding.UTF8))
  438. {
  439. string line;
  440. while ((line = tr.ReadLine()) != null)
  441. {
  442. LogEvent("Handling copy: " + line);
  443. string[] tokens = line.Split('>');
  444. if (tokens.Length > 2)
  445. {
  446. LogEvent("Too many delimiters in " + line);
  447. continue;
  448. }
  449. CopyFile(tokens[0], tokens[1]);
  450. }
  451. }
  452. }
  453. finally
  454. {
  455. File.Delete(file);
  456. }
  457. }
  458. private void CopyFile(string sourceFileName, string destFileName)
  459. {
  460. try
  461. {
  462. File.Delete(destFileName);
  463. File.Move(sourceFileName, destFileName);
  464. }
  465. catch (IOException e)
  466. {
  467. LogEvent("Failed to copy :" + sourceFileName + " to " + destFileName + " because " + e.Message);
  468. }
  469. }
  470. /// <summary>
  471. /// Handle the creation of the logfiles based on the optional logmode setting.
  472. /// </summary>
  473. private void HandleLogfiles()
  474. {
  475. string logDirectory = descriptor.LogDirectory;
  476. if (!Directory.Exists(logDirectory))
  477. {
  478. Directory.CreateDirectory(logDirectory);
  479. }
  480. string baseName = descriptor.BaseName;
  481. string errorLogfilename = Path.Combine(logDirectory, baseName + ".err.log");
  482. string outputLogfilename = Path.Combine(logDirectory, baseName + ".out.log");
  483. System.IO.FileMode fileMode = FileMode.Append;
  484. if (descriptor.Logmode == "reset")
  485. {
  486. fileMode = FileMode.Create;
  487. }
  488. else if (descriptor.Logmode == "roll")
  489. {
  490. CopyFile(outputLogfilename, outputLogfilename + ".old");
  491. CopyFile(errorLogfilename, errorLogfilename + ".old");
  492. }
  493. new Thread(delegate() { CopyStream(process.StandardOutput, new StreamWriter(new FileStream(outputLogfilename, fileMode))); }).Start();
  494. new Thread(delegate() { CopyStream(process.StandardError, new StreamWriter(new FileStream(errorLogfilename, fileMode))); }).Start();
  495. }
  496. private void LogEvent(String message)
  497. {
  498. if (systemShuttingdown)
  499. {
  500. /* NOP - cannot call EventLog because of shutdown. */
  501. }
  502. else
  503. {
  504. EventLog.WriteEntry(message);
  505. }
  506. }
  507. private void LogEvent(String message, EventLogEntryType type)
  508. {
  509. if (systemShuttingdown)
  510. {
  511. /* NOP - cannot call EventLog because of shutdown. */
  512. }
  513. else
  514. {
  515. EventLog.WriteEntry(message, type);
  516. }
  517. }
  518. private void WriteEvent(String message, Exception exception)
  519. {
  520. WriteEvent(message + "\nMessage:" + exception.Message + "\nStacktrace:" + exception.StackTrace);
  521. }
  522. private void WriteEvent(String message)
  523. {
  524. string logfilename = Path.Combine(descriptor.LogDirectory, descriptor.BaseName + ".wrapper.log");
  525. StreamWriter log = new StreamWriter(logfilename, true);
  526. log.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " - " + message);
  527. log.Flush();
  528. log.Close();
  529. }
  530. protected override void OnStart(string[] args)
  531. {
  532. envs = descriptor.EnvironmentVariables;
  533. foreach (string key in envs.Keys)
  534. {
  535. LogEvent("envar " + key + '=' + envs[key]);
  536. }
  537. HandleFileCopies();
  538. // handle downloads
  539. foreach (Download d in descriptor.Downloads)
  540. {
  541. LogEvent("Downloading: " + d.From+ " to "+d.To);
  542. try
  543. {
  544. d.Perform();
  545. }
  546. catch (Exception e)
  547. {
  548. LogEvent("Failed to download " + d.From + " to " + d.To + "\n" + e.Message);
  549. WriteEvent("Failed to download " + d.From +" to "+d.To, e);
  550. // but just keep going
  551. }
  552. }
  553. string startarguments = descriptor.Startarguments;
  554. if (startarguments == null)
  555. {
  556. startarguments = descriptor.Arguments;
  557. }
  558. else
  559. {
  560. startarguments += " " + descriptor.Arguments;
  561. }
  562. LogEvent("Starting " + descriptor.Executable + ' ' + startarguments);
  563. WriteEvent("Starting " + descriptor.Executable + ' ' + startarguments);
  564. StartProcess(process, startarguments, descriptor.Executable);
  565. // send stdout and stderr to its respective output file.
  566. HandleLogfiles();
  567. process.StandardInput.Close(); // nothing for you to read!
  568. }
  569. protected override void OnShutdown()
  570. {
  571. // WriteEvent("OnShutdown");
  572. try
  573. {
  574. this.systemShuttingdown = true;
  575. StopIt();
  576. }
  577. catch (Exception ex)
  578. {
  579. WriteEvent("Shutdown exception", ex);
  580. }
  581. }
  582. protected override void OnStop()
  583. {
  584. // WriteEvent("OnStop");
  585. try
  586. {
  587. StopIt();
  588. }
  589. catch (Exception ex)
  590. {
  591. WriteEvent("Stop exception", ex);
  592. }
  593. }
  594. private void StopIt()
  595. {
  596. string stoparguments = descriptor.Stoparguments;
  597. LogEvent("Stopping " + descriptor.Id);
  598. WriteEvent("Stopping " + descriptor.Id);
  599. orderlyShutdown = true;
  600. if (stoparguments == null)
  601. {
  602. try
  603. {
  604. WriteEvent("ProcessKill " + process.Id);
  605. process.Kill();
  606. }
  607. catch (InvalidOperationException)
  608. {
  609. // already terminated
  610. }
  611. }
  612. else
  613. {
  614. SignalShutdownPending();
  615. stoparguments += " " + descriptor.Arguments;
  616. Process stopProcess = new Process();
  617. String executable = descriptor.StopExecutable;
  618. if (executable == null)
  619. {
  620. executable = descriptor.Executable;
  621. }
  622. StartProcess(stopProcess, stoparguments, executable);
  623. WriteEvent("WaitForProcessToExit "+process.Id+"+"+stopProcess.Id);
  624. WaitForProcessToExit(process);
  625. WaitForProcessToExit(stopProcess);
  626. SignalShutdownComplete();
  627. }
  628. if (systemShuttingdown && descriptor.BeepOnShutdown)
  629. {
  630. Console.Beep();
  631. }
  632. WriteEvent("Finished " + descriptor.Id);
  633. }
  634. private void WaitForProcessToExit(Process process)
  635. {
  636. SignalShutdownPending();
  637. try
  638. {
  639. // WriteEvent("WaitForProcessToExit [start]");
  640. while (!process.WaitForExit(descriptor.SleepTime))
  641. {
  642. SignalShutdownPending();
  643. // WriteEvent("WaitForProcessToExit [repeat]");
  644. }
  645. }
  646. catch (InvalidOperationException)
  647. {
  648. // already terminated
  649. }
  650. // WriteEvent("WaitForProcessToExit [finished]");
  651. }
  652. private void SignalShutdownPending()
  653. {
  654. IntPtr handle = this.ServiceHandle;
  655. wrapperServiceStatus.checkPoint++;
  656. wrapperServiceStatus.waitHint = descriptor.WaitHint;
  657. // WriteEvent("SignalShutdownPending " + wrapperServiceStatus.checkPoint + ":" + wrapperServiceStatus.waitHint);
  658. wrapperServiceStatus.currentState = (int)State.SERVICE_STOP_PENDING;
  659. SetServiceStatus(handle, ref wrapperServiceStatus);
  660. }
  661. private void SignalShutdownComplete()
  662. {
  663. IntPtr handle = this.ServiceHandle;
  664. wrapperServiceStatus.checkPoint++;
  665. // WriteEvent("SignalShutdownComplete " + wrapperServiceStatus.checkPoint + ":" + wrapperServiceStatus.waitHint);
  666. wrapperServiceStatus.currentState = (int)State.SERVICE_STOPPED;
  667. SetServiceStatus(handle, ref wrapperServiceStatus);
  668. }
  669. private void StartProcess(Process process, string arguments, String executable)
  670. {
  671. var ps = process.StartInfo;
  672. ps.FileName = executable;
  673. ps.Arguments = arguments;
  674. ps.CreateNoWindow = false;
  675. ps.UseShellExecute = false;
  676. ps.RedirectStandardInput = true; // this creates a pipe for stdin to the new process, instead of having it inherit our stdin.
  677. ps.RedirectStandardOutput = true;
  678. ps.RedirectStandardError = true;
  679. foreach (string key in envs.Keys)
  680. System.Environment.SetEnvironmentVariable(key, envs[key]);
  681. // 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)
  682. process.Start();
  683. WriteEvent("Started " + process.Id);
  684. // monitor the completion of the process
  685. new Thread(delegate()
  686. {
  687. string msg = process.Id + " - " + process.StartInfo.FileName + " " + process.StartInfo.Arguments;
  688. process.WaitForExit();
  689. try
  690. {
  691. if (orderlyShutdown)
  692. {
  693. LogEvent("Child process [" + msg + "] terminated with " + process.ExitCode, EventLogEntryType.Information);
  694. }
  695. else
  696. {
  697. LogEvent("Child process [" + msg + "] terminated with " + process.ExitCode, EventLogEntryType.Warning);
  698. Environment.Exit(process.ExitCode);
  699. }
  700. }
  701. catch (InvalidOperationException ioe)
  702. {
  703. LogEvent("WaitForExit " + ioe.Message);
  704. }
  705. try
  706. {
  707. process.Dispose();
  708. }
  709. catch (InvalidOperationException ioe)
  710. {
  711. LogEvent("Dispose " + ioe.Message);
  712. }
  713. }).Start();
  714. }
  715. public static int Main(string[] args)
  716. {
  717. try
  718. {
  719. Run(args);
  720. return 0;
  721. }
  722. catch (WmiException e)
  723. {
  724. Console.Error.WriteLine(e);
  725. return (int)e.ErrorCode;
  726. }
  727. catch (Exception e)
  728. {
  729. Console.Error.WriteLine(e);
  730. return -1;
  731. }
  732. }
  733. private static void ThrowNoSuchService()
  734. {
  735. throw new WmiException(ReturnValue.NoSuchService);
  736. }
  737. public static void Run(string[] args)
  738. {
  739. if (args.Length > 0)
  740. {
  741. var d = new ServiceDescriptor();
  742. Win32Services svc = new WmiRoot().GetCollection<Win32Services>();
  743. Win32Service s = svc.Select(d.Id);
  744. args[0] = args[0].ToLower();
  745. if (args[0] == "install")
  746. {
  747. svc.Create(
  748. d.Id,
  749. d.Caption,
  750. ServiceDescriptor.ExecutablePath,
  751. WMI.ServiceType.OwnProcess,
  752. ErrorControl.UserNotified,
  753. StartMode.Automatic,
  754. d.Interactive,
  755. d.ServiceDependencies);
  756. // update the description
  757. /* Somehow this doesn't work, even though it doesn't report an error
  758. Win32Service s = svc.Select(d.Id);
  759. s.Description = d.Description;
  760. s.Commit();
  761. */
  762. // so using a classic method to set the description. Ugly.
  763. Registry.LocalMachine.OpenSubKey("System").OpenSubKey("CurrentControlSet").OpenSubKey("Services")
  764. .OpenSubKey(d.Id, true).SetValue("Description", d.Description);
  765. }
  766. if (args[0] == "uninstall")
  767. {
  768. if (s == null)
  769. return; // there's no such service, so consider it already uninstalled
  770. try
  771. {
  772. s.Delete();
  773. }
  774. catch (WmiException e)
  775. {
  776. if (e.ErrorCode == ReturnValue.ServiceMarkedForDeletion)
  777. return; // it's already uninstalled, so consider it a success
  778. throw e;
  779. }
  780. }
  781. if (args[0] == "start")
  782. {
  783. if (s == null) ThrowNoSuchService();
  784. s.StartService();
  785. }
  786. if (args[0] == "stop")
  787. {
  788. if (s == null) ThrowNoSuchService();
  789. s.StopService();
  790. }
  791. if (args[0] == "restart")
  792. {
  793. if (s == null)
  794. ThrowNoSuchService();
  795. if(s.Started)
  796. s.StopService();
  797. while (s.Started)
  798. {
  799. Thread.Sleep(1000);
  800. s = svc.Select(d.Id);
  801. }
  802. s.StartService();
  803. }
  804. if (args[0] == "status")
  805. {
  806. if (s == null)
  807. Console.WriteLine("NonExistent");
  808. else if (s.Started)
  809. Console.WriteLine("Started");
  810. else
  811. Console.WriteLine("Stopped");
  812. }
  813. if (args[0] == "test")
  814. {
  815. WrapperService wsvc = new WrapperService();
  816. wsvc.OnStart(args);
  817. Thread.Sleep(1000);
  818. wsvc.OnStop();
  819. }
  820. return;
  821. }
  822. ServiceBase.Run(new WrapperService());
  823. }
  824. }
  825. }