1
0

ServiceDescriptor.cs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.IO;
  5. using System.ServiceProcess;
  6. using System.Text;
  7. using System.Xml;
  8. using WinSW.Configuration;
  9. using WinSW.Native;
  10. using WinSW.Util;
  11. using Names = WinSW.Configuration.SettingNames;
  12. namespace WinSW
  13. {
  14. /// <summary>
  15. /// In-memory representation of the configuration file.
  16. /// </summary>
  17. public class ServiceDescriptor : IWinSWConfiguration
  18. {
  19. protected readonly XmlDocument dom = new XmlDocument();
  20. private readonly Dictionary<string, string> environmentVariables;
  21. internal static ServiceDescriptor? TestDescriptor;
  22. public static DefaultWinSWSettings Defaults { get; } = new DefaultWinSWSettings();
  23. /// <summary>
  24. /// Where did we find the configuration file?
  25. ///
  26. /// This string is "c:\abc\def\ghi" when the configuration XML is "c:\abc\def\ghi.xml"
  27. /// </summary>
  28. public string BasePath { get; set; }
  29. /// <summary>
  30. /// The file name portion of the configuration file.
  31. ///
  32. /// In the above example, this would be "ghi".
  33. /// </summary>
  34. public string BaseName { get; set; }
  35. // Currently there is no opportunity to alter the executable path
  36. public virtual string ExecutablePath => Defaults.ExecutablePath;
  37. public ServiceDescriptor()
  38. {
  39. string path = this.ExecutablePath;
  40. string baseName = Path.GetFileNameWithoutExtension(path);
  41. string baseDir = Path.GetDirectoryName(path)!;
  42. if (!File.Exists(Path.Combine(baseDir, baseName + ".xml")))
  43. {
  44. throw new FileNotFoundException("Unable to locate " + baseName + ".xml file within executable directory");
  45. }
  46. this.BaseName = baseName;
  47. this.BasePath = Path.Combine(baseDir, baseName);
  48. try
  49. {
  50. this.dom.Load(this.BasePath + ".xml");
  51. }
  52. catch (XmlException e)
  53. {
  54. throw new InvalidDataException(e.Message, e);
  55. }
  56. // register the base directory as environment variable so that future expansions can refer to this.
  57. Environment.SetEnvironmentVariable("BASE", baseDir);
  58. // ditto for ID
  59. Environment.SetEnvironmentVariable("SERVICE_ID", this.Id);
  60. // New name
  61. Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameExecutablePath, this.ExecutablePath);
  62. // Also inject system environment variables
  63. Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameServiceId, this.Id);
  64. this.environmentVariables = this.LoadEnvironmentVariables();
  65. }
  66. /// <exception cref="FileNotFoundException" />
  67. public ServiceDescriptor(string path)
  68. {
  69. if (!File.Exists(path))
  70. {
  71. throw new FileNotFoundException(null, path);
  72. }
  73. string baseName = Path.GetFileNameWithoutExtension(path);
  74. string baseDir = Path.GetDirectoryName(Path.GetFullPath(path))!;
  75. this.BaseName = baseName;
  76. this.BasePath = Path.Combine(baseDir, baseName);
  77. try
  78. {
  79. this.dom.Load(path);
  80. }
  81. catch (XmlException e)
  82. {
  83. throw new InvalidDataException(e.Message, e);
  84. }
  85. // register the base directory as environment variable so that future expansions can refer to this.
  86. Environment.SetEnvironmentVariable("BASE", baseDir);
  87. // ditto for ID
  88. Environment.SetEnvironmentVariable("SERVICE_ID", this.Id);
  89. // New name
  90. Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameExecutablePath, this.ExecutablePath);
  91. // Also inject system environment variables
  92. Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameServiceId, this.Id);
  93. this.environmentVariables = this.LoadEnvironmentVariables();
  94. }
  95. /// <summary>
  96. /// Loads descriptor from existing DOM
  97. /// </summary>
  98. #pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
  99. public ServiceDescriptor(XmlDocument dom)
  100. #pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
  101. {
  102. this.dom = dom;
  103. this.environmentVariables = this.LoadEnvironmentVariables();
  104. }
  105. internal static ServiceDescriptor Create(string? path)
  106. {
  107. return path != null ? new ServiceDescriptor(path) : TestDescriptor ?? new ServiceDescriptor();
  108. }
  109. public static ServiceDescriptor FromXml(string xml)
  110. {
  111. var dom = new XmlDocument();
  112. dom.LoadXml(xml);
  113. return new ServiceDescriptor(dom);
  114. }
  115. private string SingleElement(string tagName)
  116. {
  117. return this.SingleElement(tagName, false)!;
  118. }
  119. private string? SingleElement(string tagName, bool optional)
  120. {
  121. XmlNode? n = this.dom.SelectSingleNode("//" + tagName);
  122. if (n is null && !optional)
  123. {
  124. throw new InvalidDataException("<" + tagName + "> is missing in configuration XML");
  125. }
  126. return n is null ? null : Environment.ExpandEnvironmentVariables(n.InnerText);
  127. }
  128. private bool SingleBoolElement(string tagName, bool defaultValue)
  129. {
  130. XmlNode? e = this.dom.SelectSingleNode("//" + tagName);
  131. return e is null ? defaultValue : bool.Parse(e.InnerText);
  132. }
  133. private int SingleIntElement(XmlNode parent, string tagName, int defaultValue)
  134. {
  135. XmlNode? e = parent.SelectSingleNode(tagName);
  136. return e is null ? defaultValue : int.Parse(e.InnerText);
  137. }
  138. private TimeSpan SingleTimeSpanElement(XmlNode parent, string tagName, TimeSpan defaultValue)
  139. {
  140. string? value = this.SingleElement(tagName, true);
  141. return value is null ? defaultValue : this.ParseTimeSpan(value);
  142. }
  143. private TimeSpan ParseTimeSpan(string v)
  144. {
  145. v = v.Trim();
  146. foreach (var s in Suffix)
  147. {
  148. if (v.EndsWith(s.Key))
  149. {
  150. return TimeSpan.FromMilliseconds(int.Parse(v.Substring(0, v.Length - s.Key.Length).Trim()) * s.Value);
  151. }
  152. }
  153. return TimeSpan.FromMilliseconds(int.Parse(v));
  154. }
  155. private static readonly Dictionary<string, long> Suffix = new Dictionary<string, long>
  156. {
  157. { "ms", 1 },
  158. { "sec", 1000L },
  159. { "secs", 1000L },
  160. { "min", 1000L * 60L },
  161. { "mins", 1000L * 60L },
  162. { "hr", 1000L * 60L * 60L },
  163. { "hrs", 1000L * 60L * 60L },
  164. { "hour", 1000L * 60L * 60L },
  165. { "hours", 1000L * 60L * 60L },
  166. { "day", 1000L * 60L * 60L * 24L },
  167. { "days", 1000L * 60L * 60L * 24L }
  168. };
  169. /// <summary>
  170. /// Path to the executable.
  171. /// </summary>
  172. public string Executable => this.SingleElement("executable");
  173. public bool HideWindow => this.SingleBoolElement("hidewindow", Defaults.HideWindow);
  174. /// <summary>
  175. /// Optionally specify a different Path to an executable to shutdown the service.
  176. /// </summary>
  177. public string? StopExecutable => this.SingleElement("stopexecutable", true);
  178. /// <summary>
  179. /// <c>arguments</c> or multiple optional <c>argument</c> elements which overrule the arguments element.
  180. /// </summary>
  181. public string Arguments
  182. {
  183. get
  184. {
  185. string? arguments = this.AppendTags("argument", null);
  186. if (!(arguments is null))
  187. {
  188. return arguments;
  189. }
  190. XmlNode? argumentsNode = this.dom.SelectSingleNode("//arguments");
  191. return argumentsNode is null ? Defaults.Arguments : Environment.ExpandEnvironmentVariables(argumentsNode.InnerText);
  192. }
  193. }
  194. /// <summary>
  195. /// <c>startarguments</c> or multiple optional <c>startargument</c> elements.
  196. /// </summary>
  197. public string? StartArguments
  198. {
  199. get
  200. {
  201. string? startArguments = this.AppendTags("startargument", null);
  202. if (!(startArguments is null))
  203. {
  204. return startArguments;
  205. }
  206. XmlNode? startArgumentsNode = this.dom.SelectSingleNode("//startarguments");
  207. return startArgumentsNode is null ? null : Environment.ExpandEnvironmentVariables(startArgumentsNode.InnerText);
  208. }
  209. }
  210. /// <summary>
  211. /// <c>stoparguments</c> or multiple optional <c>stopargument</c> elements.
  212. /// </summary>
  213. public string? StopArguments
  214. {
  215. get
  216. {
  217. string? stopArguments = this.AppendTags("stopargument", null);
  218. if (!(stopArguments is null))
  219. {
  220. return stopArguments;
  221. }
  222. XmlNode? stopArgumentsNode = this.dom.SelectSingleNode("//stoparguments");
  223. return stopArgumentsNode is null ? null : Environment.ExpandEnvironmentVariables(stopArgumentsNode.InnerText);
  224. }
  225. }
  226. public string? PrestartExecutable => this.GetExecutable(Names.Prestart);
  227. public string? PrestartArguments => this.GetArguments(Names.Prestart);
  228. public string? PoststartExecutable => this.GetExecutable(Names.Poststart);
  229. public string? PoststartArguments => this.GetArguments(Names.Poststart);
  230. public string? PrestopExecutable => this.GetExecutable(Names.Prestop);
  231. public string? PrestopArguments => this.GetArguments(Names.Prestop);
  232. public string? PoststopExecutable => this.GetExecutable(Names.Poststop);
  233. public string? PoststopArguments => this.GetArguments(Names.Poststop);
  234. public string WorkingDirectory
  235. {
  236. get
  237. {
  238. var wd = this.SingleElement("workingdirectory", true);
  239. return string.IsNullOrEmpty(wd) ? Defaults.WorkingDirectory : wd!;
  240. }
  241. }
  242. public List<string> ExtensionIds
  243. {
  244. get
  245. {
  246. XmlNode? argumentNode = this.ExtensionsConfiguration;
  247. XmlNodeList? extensions = argumentNode?.SelectNodes("extension");
  248. if (extensions is null)
  249. {
  250. return new List<string>(0);
  251. }
  252. List<string> result = new List<string>(extensions.Count);
  253. for (int i = 0; i < extensions.Count; i++)
  254. {
  255. result.Add(XmlHelper.SingleAttribute<string>((XmlElement)extensions[i], "id"));
  256. }
  257. return result;
  258. }
  259. }
  260. public XmlNode? ExtensionsConfiguration => this.dom.SelectSingleNode("//extensions");
  261. /// <summary>
  262. /// Combines the contents of all the elements of the given name,
  263. /// or return null if no element exists. Handles whitespace quotation.
  264. /// </summary>
  265. private string? AppendTags(string tagName, string? defaultValue = null)
  266. {
  267. XmlNode? argumentNode = this.dom.SelectSingleNode("//" + tagName);
  268. if (argumentNode is null)
  269. {
  270. return defaultValue;
  271. }
  272. StringBuilder arguments = new StringBuilder();
  273. XmlNodeList argumentNodeList = this.dom.SelectNodes("//" + tagName);
  274. for (int i = 0; i < argumentNodeList.Count; i++)
  275. {
  276. arguments.Append(' ');
  277. string token = Environment.ExpandEnvironmentVariables(argumentNodeList[i].InnerText);
  278. if (token.StartsWith("\"") && token.EndsWith("\""))
  279. {
  280. // for backward compatibility, if the argument is already quoted, leave it as is.
  281. // in earlier versions we didn't handle quotation, so the user might have worked
  282. // around it by themselves
  283. }
  284. else
  285. {
  286. if (token.Contains(" "))
  287. {
  288. arguments.Append('"').Append(token).Append('"');
  289. continue;
  290. }
  291. }
  292. arguments.Append(token);
  293. }
  294. return arguments.ToString();
  295. }
  296. /// <summary>
  297. /// LogDirectory is the service wrapper executable directory or the optionally specified logpath element.
  298. /// </summary>
  299. public string LogDirectory
  300. {
  301. get
  302. {
  303. XmlNode? loggingNode = this.dom.SelectSingleNode("//logpath");
  304. return loggingNode is null
  305. ? Defaults.LogDirectory
  306. : Environment.ExpandEnvironmentVariables(loggingNode.InnerText);
  307. }
  308. }
  309. public string LogMode
  310. {
  311. get
  312. {
  313. string? mode = null;
  314. // first, backward compatibility with older configuration
  315. XmlElement? e = (XmlElement?)this.dom.SelectSingleNode("//logmode");
  316. if (e != null)
  317. {
  318. mode = e.InnerText;
  319. }
  320. else
  321. {
  322. // this is more modern way, to support nested elements as configuration
  323. e = (XmlElement?)this.dom.SelectSingleNode("//log");
  324. if (e != null)
  325. {
  326. mode = e.GetAttribute("mode");
  327. }
  328. }
  329. return mode ?? Defaults.LogMode;
  330. }
  331. }
  332. public string LogName
  333. {
  334. get
  335. {
  336. XmlNode? loggingName = this.dom.SelectSingleNode("//logname");
  337. return loggingName is null ? this.BaseName : Environment.ExpandEnvironmentVariables(loggingName.InnerText);
  338. }
  339. }
  340. public bool OutFileDisabled => this.SingleBoolElement("outfiledisabled", Defaults.OutFileDisabled);
  341. public bool ErrFileDisabled => this.SingleBoolElement("errfiledisabled", Defaults.ErrFileDisabled);
  342. public string OutFilePattern
  343. {
  344. get
  345. {
  346. XmlNode? loggingName = this.dom.SelectSingleNode("//outfilepattern");
  347. return loggingName is null ? Defaults.OutFilePattern : Environment.ExpandEnvironmentVariables(loggingName.InnerText);
  348. }
  349. }
  350. public string ErrFilePattern
  351. {
  352. get
  353. {
  354. XmlNode? loggingName = this.dom.SelectSingleNode("//errfilepattern");
  355. return loggingName is null ? Defaults.ErrFilePattern : Environment.ExpandEnvironmentVariables(loggingName.InnerText);
  356. }
  357. }
  358. public LogHandler LogHandler
  359. {
  360. get
  361. {
  362. XmlElement? e = (XmlElement?)this.dom.SelectSingleNode("//logmode");
  363. // this is more modern way, to support nested elements as configuration
  364. e ??= (XmlElement?)this.dom.SelectSingleNode("//log")!; // WARNING: NRE
  365. int sizeThreshold;
  366. switch (this.LogMode)
  367. {
  368. case "rotate":
  369. return new SizeBasedRollingLogAppender(this.LogDirectory, this.LogName, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern);
  370. case "none":
  371. return new IgnoreLogAppender();
  372. case "reset":
  373. return new ResetLogAppender(this.LogDirectory, this.LogName, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern);
  374. case "roll":
  375. return new RollingLogAppender(this.LogDirectory, this.LogName, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern);
  376. case "roll-by-time":
  377. XmlNode? patternNode = e.SelectSingleNode("pattern");
  378. if (patternNode is null)
  379. {
  380. throw new InvalidDataException("Time Based rolling policy is specified but no pattern can be found in configuration XML.");
  381. }
  382. var pattern = patternNode.InnerText;
  383. int period = this.SingleIntElement(e, "period", 1);
  384. return new TimeBasedRollingLogAppender(this.LogDirectory, this.LogName, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern, pattern, period);
  385. case "roll-by-size":
  386. sizeThreshold = this.SingleIntElement(e, "sizeThreshold", 10 * 1024) * SizeBasedRollingLogAppender.BytesPerKB;
  387. int keepFiles = this.SingleIntElement(e, "keepFiles", SizeBasedRollingLogAppender.DefaultFilesToKeep);
  388. return new SizeBasedRollingLogAppender(this.LogDirectory, this.LogName, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern, sizeThreshold, keepFiles);
  389. case "append":
  390. return new DefaultLogAppender(this.LogDirectory, this.LogName, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern);
  391. case "roll-by-size-time":
  392. sizeThreshold = this.SingleIntElement(e, "sizeThreshold", 10 * 1024) * RollingSizeTimeLogAppender.BytesPerKB;
  393. XmlNode? filePatternNode = e.SelectSingleNode("pattern");
  394. if (filePatternNode is null)
  395. {
  396. throw new InvalidDataException("Roll-Size-Time Based rolling policy is specified but no pattern can be found in configuration XML.");
  397. }
  398. XmlNode? autoRollAtTimeNode = e.SelectSingleNode("autoRollAtTime");
  399. TimeSpan? autoRollAtTime = null;
  400. if (autoRollAtTimeNode != null)
  401. {
  402. // validate it
  403. if (!TimeSpan.TryParse(autoRollAtTimeNode.InnerText, out TimeSpan autoRollAtTimeValue))
  404. {
  405. throw new InvalidDataException("Roll-Size-Time Based rolling policy is specified but autoRollAtTime does not match the TimeSpan format HH:mm:ss found in configuration XML.");
  406. }
  407. autoRollAtTime = autoRollAtTimeValue;
  408. }
  409. XmlNode? zipolderthannumdaysNode = e.SelectSingleNode("zipOlderThanNumDays");
  410. int? zipolderthannumdays = null;
  411. if (zipolderthannumdaysNode != null)
  412. {
  413. // validate it
  414. if (!int.TryParse(zipolderthannumdaysNode.InnerText, out int zipolderthannumdaysValue))
  415. {
  416. throw new InvalidDataException("Roll-Size-Time Based rolling policy is specified but zipOlderThanNumDays does not match the int format found in configuration XML.");
  417. }
  418. zipolderthannumdays = zipolderthannumdaysValue;
  419. }
  420. XmlNode? zipdateformatNode = e.SelectSingleNode("zipDateFormat");
  421. string zipdateformat = zipdateformatNode is null ? "yyyyMM" : zipdateformatNode.InnerText;
  422. return new RollingSizeTimeLogAppender(this.LogDirectory, this.LogName, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern, sizeThreshold, filePatternNode.InnerText, autoRollAtTime, zipolderthannumdays, zipdateformat);
  423. default:
  424. throw new InvalidDataException("Undefined logging mode: " + this.LogMode);
  425. }
  426. }
  427. }
  428. /// <summary>
  429. /// Optionally specified depend services that must start before this service starts.
  430. /// </summary>
  431. public string[] ServiceDependencies
  432. {
  433. get
  434. {
  435. XmlNodeList? nodeList = this.dom.SelectNodes("//depend");
  436. if (nodeList is null)
  437. {
  438. return Defaults.ServiceDependencies;
  439. }
  440. string[] serviceDependencies = new string[nodeList.Count];
  441. for (int i = 0; i < nodeList.Count; i++)
  442. {
  443. serviceDependencies[i] = nodeList[i].InnerText;
  444. }
  445. return serviceDependencies;
  446. }
  447. }
  448. public string Id => this.SingleElement("id");
  449. public string Caption => this.SingleElement("name");
  450. public string Description => this.SingleElement("description");
  451. /// <summary>
  452. /// Start mode of the Service
  453. /// </summary>
  454. public ServiceStartMode StartMode
  455. {
  456. get
  457. {
  458. string? p = this.SingleElement("startmode", true);
  459. if (p is null)
  460. {
  461. return Defaults.StartMode;
  462. }
  463. try
  464. {
  465. return (ServiceStartMode)Enum.Parse(typeof(ServiceStartMode), p, true);
  466. }
  467. catch (ArgumentException e)
  468. {
  469. StringBuilder builder = new StringBuilder();
  470. builder.AppendLine("Start mode in XML must be one of the following:");
  471. foreach (string sm in Enum.GetNames(typeof(ServiceStartMode)))
  472. {
  473. builder.AppendLine(sm);
  474. }
  475. throw new InvalidDataException(builder.ToString(), e);
  476. }
  477. }
  478. }
  479. /// <summary>
  480. /// True if the service should be installed with the DelayedAutoStart flag.
  481. /// This setting will be applyed only during the install command and only when the Automatic start mode is configured.
  482. /// </summary>
  483. public bool DelayedAutoStart => this.dom.SelectSingleNode("//delayedAutoStart") != null;
  484. /// <summary>
  485. /// True if the service should beep when finished on shutdown.
  486. /// This doesn't work on some OSes. See http://msdn.microsoft.com/en-us/library/ms679277%28VS.85%29.aspx
  487. /// </summary>
  488. public bool BeepOnShutdown => this.dom.SelectSingleNode("//beeponshutdown") != null;
  489. /// <summary>
  490. /// True if the service can interact with the desktop.
  491. /// </summary>
  492. public bool Interactive => this.dom.SelectSingleNode("//interactive") != null;
  493. /// <summary>
  494. /// Environment variable overrides
  495. /// </summary>
  496. public Dictionary<string, string> EnvironmentVariables => new Dictionary<string, string>(this.environmentVariables);
  497. /// <summary>
  498. /// List of downloads to be performed by the wrapper before starting
  499. /// a service.
  500. /// </summary>
  501. public List<Download> Downloads
  502. {
  503. get
  504. {
  505. XmlNodeList? nodeList = this.dom.SelectNodes("//download");
  506. if (nodeList is null)
  507. {
  508. return Defaults.Downloads;
  509. }
  510. List<Download> result = new List<Download>(nodeList.Count);
  511. for (int i = 0; i < nodeList.Count; i++)
  512. {
  513. if (nodeList[i] is XmlElement element)
  514. {
  515. result.Add(new Download(element));
  516. }
  517. }
  518. return result;
  519. }
  520. }
  521. public SC_ACTION[] FailureActions
  522. {
  523. get
  524. {
  525. XmlNodeList? childNodes = this.dom.SelectNodes("//onfailure");
  526. if (childNodes is null)
  527. {
  528. return new SC_ACTION[0];
  529. }
  530. SC_ACTION[] result = new SC_ACTION[childNodes.Count];
  531. for (int i = 0; i < childNodes.Count; i++)
  532. {
  533. XmlNode node = childNodes[i];
  534. string action = node.Attributes["action"].Value;
  535. SC_ACTION_TYPE type = action switch
  536. {
  537. "restart" => SC_ACTION_TYPE.SC_ACTION_RESTART,
  538. "none" => SC_ACTION_TYPE.SC_ACTION_NONE,
  539. "reboot" => SC_ACTION_TYPE.SC_ACTION_REBOOT,
  540. _ => throw new Exception("Invalid failure action: " + action)
  541. };
  542. XmlAttribute? delay = node.Attributes["delay"];
  543. result[i] = new SC_ACTION(type, delay != null ? this.ParseTimeSpan(delay.Value) : TimeSpan.Zero);
  544. }
  545. return result;
  546. }
  547. }
  548. public TimeSpan ResetFailureAfter => this.SingleTimeSpanElement(this.dom, "resetfailure", Defaults.ResetFailureAfter);
  549. protected string? GetServiceAccountPart(string subNodeName)
  550. {
  551. XmlNode? node = this.dom.SelectSingleNode("//serviceaccount");
  552. if (node != null)
  553. {
  554. XmlNode? subNode = node.SelectSingleNode(subNodeName);
  555. if (subNode != null)
  556. {
  557. return subNode.InnerText;
  558. }
  559. }
  560. return null;
  561. }
  562. public string? ServiceAccountPrompt => this.GetServiceAccountPart("prompt")?.ToLowerInvariant();
  563. protected string? AllowServiceLogon => this.GetServiceAccountPart("allowservicelogon");
  564. public string? ServiceAccountPassword => this.GetServiceAccountPart("password");
  565. public string? ServiceAccountUserName => this.GetServiceAccountPart("username");
  566. public bool HasServiceAccount()
  567. {
  568. return this.dom.SelectSingleNode("//serviceaccount") != null;
  569. }
  570. public bool AllowServiceAcountLogonRight
  571. {
  572. get
  573. {
  574. if (this.AllowServiceLogon != null)
  575. {
  576. if (bool.TryParse(this.AllowServiceLogon, out bool parsedvalue))
  577. {
  578. return parsedvalue;
  579. }
  580. }
  581. return false;
  582. }
  583. }
  584. /// <summary>
  585. /// Time to wait for the service to gracefully shutdown the executable before we forcibly kill it
  586. /// </summary>
  587. public TimeSpan StopTimeout => this.SingleTimeSpanElement(this.dom, "stoptimeout", Defaults.StopTimeout);
  588. /// <summary>
  589. /// Desired process priority or null if not specified.
  590. /// </summary>
  591. public ProcessPriorityClass Priority
  592. {
  593. get
  594. {
  595. string? p = this.SingleElement("priority", true);
  596. if (p is null)
  597. {
  598. return Defaults.Priority;
  599. }
  600. return (ProcessPriorityClass)Enum.Parse(typeof(ProcessPriorityClass), p, true);
  601. }
  602. }
  603. public string? SecurityDescriptor => this.SingleElement("securityDescriptor", true);
  604. private Dictionary<string, string> LoadEnvironmentVariables()
  605. {
  606. XmlNodeList nodeList = this.dom.SelectNodes("//env");
  607. Dictionary<string, string> environment = new Dictionary<string, string>(nodeList.Count);
  608. for (int i = 0; i < nodeList.Count; i++)
  609. {
  610. XmlNode node = nodeList[i];
  611. string key = node.Attributes["name"].Value;
  612. string value = Environment.ExpandEnvironmentVariables(node.Attributes["value"].Value);
  613. environment[key] = value;
  614. Environment.SetEnvironmentVariable(key, value);
  615. }
  616. return environment;
  617. }
  618. private string? GetExecutable(string name)
  619. {
  620. string? text = this.dom.SelectSingleNode(Names.Service)?.SelectSingleNode(name)?.SelectSingleNode(Names.Executable)?.InnerText;
  621. return text is null ? null : Environment.ExpandEnvironmentVariables(text);
  622. }
  623. private string? GetArguments(string name)
  624. {
  625. string? text = this.dom.SelectSingleNode(Names.Service)?.SelectSingleNode(name)?.SelectSingleNode(Names.Arguments)?.InnerText;
  626. return text is null ? null : Environment.ExpandEnvironmentVariables(text);
  627. }
  628. }
  629. }