OhmCli.cs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. using Microsoft.Extensions.Hosting.WindowsServices;
  2. using Microsoft.Extensions.Hosting;
  3. using NLog;
  4. using System;
  5. using System.CommandLine;
  6. using System.Configuration;
  7. using System.Diagnostics;
  8. using System.IO;
  9. using System.Threading.Tasks;
  10. using System.ServiceProcess;
  11. using Microsoft.Extensions.DependencyInjection;
  12. namespace OhmGraphite
  13. {
  14. public static class OhmCli
  15. {
  16. private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
  17. public static async Task Execute(string[] args)
  18. {
  19. var serviceName = "OhmGraphite";
  20. var description = "Expose hardware sensor data to Graphite / InfluxDB / Prometheus / Postgres / Timescaledb";
  21. var configOption = new Option<FileInfo>(name: "--config", description: "OhmGraphite configuration file");
  22. var rootCommand = new RootCommand(description)
  23. {
  24. // To maintain compabitility with old topshelf installations, we should ignore
  25. // command flags like " -displayname"
  26. TreatUnmatchedTokensAsErrors = false
  27. };
  28. rootCommand.AddOption(configOption);
  29. rootCommand.SetHandler(async (context) => await OhmCommand(context, configOption));
  30. var runCommand = new Command("run", "Runs the service from the command line (default)");
  31. runCommand.AddOption(configOption);
  32. runCommand.SetHandler(async (context) => await OhmCommand(context, configOption));
  33. rootCommand.AddCommand(runCommand);
  34. var statusCommand = new Command("status", "Queries the status of the service");
  35. statusCommand.SetHandler((context) =>
  36. {
  37. if (OperatingSystem.IsWindows())
  38. {
  39. var service = new ServiceController(serviceName);
  40. Console.WriteLine(service.Status);
  41. }
  42. });
  43. rootCommand.AddCommand(statusCommand);
  44. var stopCommand = new Command("stop", "Stops the service if it is running");
  45. stopCommand.SetHandler((context) =>
  46. {
  47. if (OperatingSystem.IsWindows())
  48. {
  49. var service = new ServiceController(serviceName);
  50. service.Stop();
  51. service.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(10));
  52. if (service.Status != ServiceControllerStatus.Stopped)
  53. {
  54. Console.Error.WriteLine($"Unable to stop {serviceName}");
  55. }
  56. else
  57. {
  58. Console.WriteLine($"{serviceName} stopped");
  59. }
  60. }
  61. });
  62. rootCommand.AddCommand(stopCommand);
  63. var startCommand = new Command("start", "Starts the service if it is not already running");
  64. startCommand.SetHandler((context) =>
  65. {
  66. if (OperatingSystem.IsWindows())
  67. {
  68. var service = new ServiceController(serviceName);
  69. service.Start();
  70. service.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(10));
  71. if (service.Status != ServiceControllerStatus.Running)
  72. {
  73. Console.Error.WriteLine($"Unable to start {serviceName}");
  74. }
  75. else
  76. {
  77. Console.WriteLine($"{serviceName} started");
  78. }
  79. }
  80. });
  81. rootCommand.AddCommand(startCommand);
  82. var installCommand = new Command("install", "Installs the service");
  83. installCommand.AddOption(configOption);
  84. installCommand.SetHandler((context) =>
  85. {
  86. {
  87. var procInfo = new ProcessStartInfo();
  88. procInfo.ArgumentList.Add("create");
  89. procInfo.ArgumentList.Add(serviceName);
  90. var configFile = context.ParseResult.GetValueForOption(configOption);
  91. var configService = configFile == null ? "" : $@" --config ""{configFile.FullName}""";
  92. procInfo.ArgumentList.Add($@"binpath=""{Environment.ProcessPath}"" {configService}");
  93. procInfo.ArgumentList.Add(@"DisplayName=Ohm Graphite");
  94. procInfo.ArgumentList.Add("start=auto");
  95. ScCommand(procInfo);
  96. }
  97. {
  98. var procInfo = new ProcessStartInfo();
  99. procInfo.ArgumentList.Add("description");
  100. procInfo.ArgumentList.Add(serviceName);
  101. procInfo.ArgumentList.Add(description);
  102. ScCommand(procInfo);
  103. }
  104. });
  105. rootCommand.AddCommand(installCommand);
  106. var uninstallCommand = new Command("uninstall", "Uninstalls the service");
  107. uninstallCommand.SetHandler((context) =>
  108. {
  109. var procInfo = new ProcessStartInfo
  110. {
  111. Arguments = $"delete {serviceName}",
  112. };
  113. ScCommand(procInfo);
  114. });
  115. rootCommand.AddCommand(uninstallCommand);
  116. await rootCommand.InvokeAsync(args);
  117. }
  118. static async Task OhmCommand(System.CommandLine.Invocation.InvocationContext context, Option<FileInfo> configOption)
  119. {
  120. var configFile = context.ParseResult.GetValueForOption(configOption);
  121. var configPath = configFile == null ? string.Empty : configFile.FullName;
  122. var configDisplay = configFile == null ? "default" : configFile.Name;
  123. var config = Logger.LogFunction($"parse config {configDisplay}", () => MetricConfig.ParseAppSettings(CreateConfiguration(configPath)));
  124. if (WindowsServiceHelpers.IsWindowsService())
  125. {
  126. var builder = Host.CreateDefaultBuilder()
  127. .UseWindowsService(options =>
  128. {
  129. options.ServiceName = "OhmGraphite";
  130. })
  131. .ConfigureServices((hostContext, services) =>
  132. {
  133. services.AddSingleton(config);
  134. services.AddHostedService<Worker>();
  135. });
  136. builder.Build().Run();
  137. }
  138. else
  139. {
  140. var token = context.GetCancellationToken();
  141. var worker = new Worker(config);
  142. await worker.StartAsync(token);
  143. await worker.ExecuteTask;
  144. }
  145. }
  146. private static int ScCommand(ProcessStartInfo procInfo)
  147. {
  148. procInfo.FileName = "sc.exe";
  149. procInfo.RedirectStandardOutput = true;
  150. procInfo.RedirectStandardError = true;
  151. procInfo.CreateNoWindow = true;
  152. procInfo.UseShellExecute = false;
  153. using var process = new Process() { StartInfo = procInfo };
  154. process.Start();
  155. process.WaitForExit();
  156. var error = process.StandardError.ReadToEnd();
  157. var stdout = process.StandardOutput.ReadToEnd();
  158. if (!string.IsNullOrEmpty(error))
  159. {
  160. Console.WriteLine(error);
  161. }
  162. if (!string.IsNullOrEmpty(stdout))
  163. {
  164. Console.WriteLine(stdout);
  165. }
  166. return process.ExitCode;
  167. }
  168. private static IAppConfig CreateConfiguration(string configPath)
  169. {
  170. if (string.IsNullOrEmpty(configPath))
  171. {
  172. var fn = Path.Join(Path.GetDirectoryName(Environment.ProcessPath), "OhmGraphite.exe.config");
  173. var configMap1 = new ExeConfigurationFileMap { ExeConfigFilename = fn };
  174. var config1 = ConfigurationManager.OpenMappedExeConfiguration(configMap1, ConfigurationUserLevel.None);
  175. return new CustomConfig(config1);
  176. }
  177. if (!File.Exists(configPath))
  178. {
  179. throw new ApplicationException($"unable to detect config: ${configPath}");
  180. }
  181. var configMap = new ExeConfigurationFileMap { ExeConfigFilename = configPath };
  182. var config = ConfigurationManager.OpenMappedExeConfiguration(configMap, ConfigurationUserLevel.None);
  183. return new CustomConfig(config);
  184. }
  185. }
  186. }