PageRenderTime 57ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/Reactor/Hosting/Internal/ServiceControlHelper.cs

https://github.com/akilhoffer/Reactor
C# | 441 lines | 358 code | 69 blank | 14 comment | 37 complexity | f14218212e0285c3a5b5b9881987c7b5 MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Diagnostics;
  5. using System.Runtime.InteropServices;
  6. using System.Security.Permissions;
  7. using System.ServiceProcess;
  8. using Microsoft.Win32;
  9. using log4net;
  10. namespace Reactor.Hosting.Internal
  11. {
  12. internal static class ServiceControlHelper
  13. {
  14. private static readonly ILog Log = LogManager.GetLogger(typeof (ServiceControlHelper));
  15. #region "SERVICE RECOVERY INTEROP"
  16. // ReSharper disable InconsistentNaming
  17. enum SC_ACTION_TYPE
  18. {
  19. None = 0,
  20. RestartService = 1,
  21. RebootComputer = 2,
  22. RunCommand = 3
  23. }
  24. [StructLayout(LayoutKind.Sequential)]
  25. struct SC_ACTION
  26. {
  27. public SC_ACTION_TYPE Type;
  28. public uint Delay;
  29. }
  30. [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
  31. struct SERVICE_FAILURE_ACTIONS
  32. {
  33. public int dwResetPeriod;
  34. [MarshalAs(UnmanagedType.LPWStr)]
  35. public string lpRebootMsg;
  36. [MarshalAs(UnmanagedType.LPWStr)]
  37. public string lpCommand;
  38. public int cActions;
  39. public IntPtr lpsaActions;
  40. }
  41. private const int SERVICE_CONFIG_FAILURE_ACTIONS = 2;
  42. [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
  43. [return: MarshalAs(UnmanagedType.Bool)]
  44. private static extern bool ChangeServiceConfig2(
  45. IntPtr hService,
  46. int dwInfoLevel,
  47. IntPtr lpInfo);
  48. [DllImport("advapi32.dll", EntryPoint = "QueryServiceConfig2", CharSet = CharSet.Unicode, SetLastError = true)]
  49. private static extern int QueryServiceConfig2(
  50. IntPtr hService,
  51. int dwInfoLevel,
  52. IntPtr lpBuffer,
  53. uint cbBufSize,
  54. out uint pcbBytesNeeded);
  55. // ReSharper restore InconsistentNaming
  56. #endregion
  57. #region "GRANT SHUTDOWN INTEROP"
  58. // ReSharper disable InconsistentNaming
  59. [StructLayout(LayoutKind.Sequential)]
  60. struct LUID_AND_ATTRIBUTES
  61. {
  62. public long Luid;
  63. public UInt32 Attributes;
  64. }
  65. [StructLayout(LayoutKind.Sequential, Pack = 1)]
  66. struct TOKEN_PRIVILEGES
  67. {
  68. public int PrivilegeCount;
  69. public LUID_AND_ATTRIBUTES Privileges;
  70. }
  71. [DllImport("advapi32.dll")]
  72. private static extern bool
  73. AdjustTokenPrivileges(IntPtr TokenHandle, bool DisableAllPrivileges,
  74. [MarshalAs(UnmanagedType.Struct)] ref TOKEN_PRIVILEGES NewState, int BufferLength,
  75. IntPtr PreviousState, ref int ReturnLength);
  76. [DllImport("advapi32.dll")]
  77. private static extern bool
  78. LookupPrivilegeValue(string lpSystemName, string lpName, ref long lpLuid);
  79. [DllImport("advapi32.dll")]
  80. private static extern bool
  81. OpenProcessToken(IntPtr ProcessHandle, int DesiredAccess, ref IntPtr TokenHandle);
  82. private const int TOKEN_ADJUST_PRIVILEGES = 32;
  83. private const int TOKEN_QUERY = 8;
  84. private const string SE_SHUTDOWN_NAME = "SeShutdownPrivilege";
  85. private const int SE_PRIVILEGE_ENABLED = 2;
  86. // ReSharper restore InconsistentNaming
  87. #endregion
  88. [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
  89. public static void SetServiceRecoveryOptions(
  90. string serviceName,
  91. ServiceRecoveryOptions recoveryOptions)
  92. {
  93. Exceptions.ThrowHelper.ThrowArgumentNullIfNull(serviceName, "serviceName");
  94. Exceptions.ThrowHelper.ThrowArgumentOutOfRangeIfEmpty(serviceName, "serviceName");
  95. Log.Debug("Setting service recovery options...");
  96. bool requiresShutdownPriveleges =
  97. recoveryOptions.FirstFailureAction == ServiceRecoveryAction.RestartTheComputer ||
  98. recoveryOptions.SecondFailureAction == ServiceRecoveryAction.RestartTheComputer ||
  99. recoveryOptions.SubsequentFailureActions == ServiceRecoveryAction.RestartTheComputer;
  100. if (requiresShutdownPriveleges)
  101. {
  102. GrantShutdownPrivileges();
  103. }
  104. const int actionCount = 3;
  105. var restartServiceAfter = (uint)TimeSpan.FromMinutes(
  106. recoveryOptions.MinutesToRestartService).TotalMilliseconds;
  107. IntPtr failureActionsPointer = IntPtr.Zero;
  108. IntPtr actionPointer = IntPtr.Zero;
  109. ServiceController controller = null;
  110. try
  111. {
  112. // Open the service
  113. controller = new ServiceController(serviceName);
  114. // Set up the failure actions
  115. var failureActions = new SERVICE_FAILURE_ACTIONS
  116. {
  117. dwResetPeriod = (int)TimeSpan.FromDays(recoveryOptions.DaysToResetFailAcount).TotalSeconds,
  118. cActions = actionCount,
  119. lpRebootMsg = recoveryOptions.RebootMessage
  120. };
  121. // allocate memory for the individual actions
  122. actionPointer = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(SC_ACTION)) * actionCount);
  123. ServiceRecoveryAction[] actions = { recoveryOptions.FirstFailureAction,
  124. recoveryOptions.SecondFailureAction,
  125. recoveryOptions.SubsequentFailureActions };
  126. for (int i = 0; i < actions.Length; i++)
  127. {
  128. ServiceRecoveryAction action = actions[i];
  129. var scAction = GetScAction(action, restartServiceAfter);
  130. Marshal.StructureToPtr(scAction, (IntPtr)((Int64)actionPointer + (Marshal.SizeOf(typeof(SC_ACTION))) * i), false);
  131. }
  132. failureActions.lpsaActions = actionPointer;
  133. string command = recoveryOptions.CommandToLaunchOnFailure;
  134. if (command != null)
  135. {
  136. failureActions.lpCommand = command;
  137. }
  138. failureActionsPointer = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(SERVICE_FAILURE_ACTIONS)));
  139. Marshal.StructureToPtr(failureActions, failureActionsPointer, false);
  140. // Make the change
  141. bool success = ChangeServiceConfig2(
  142. controller.ServiceHandle.DangerousGetHandle(),
  143. SERVICE_CONFIG_FAILURE_ACTIONS,
  144. failureActionsPointer);
  145. // Check that the change occurred
  146. if (!success)
  147. {
  148. throw new Win32Exception(Marshal.GetLastWin32Error(), "Unable to change the Service configuration.");
  149. }
  150. }
  151. finally
  152. {
  153. if (failureActionsPointer != IntPtr.Zero)
  154. {
  155. Marshal.FreeHGlobal(failureActionsPointer);
  156. }
  157. if (actionPointer != IntPtr.Zero)
  158. {
  159. Marshal.FreeHGlobal(actionPointer);
  160. }
  161. if (controller != null)
  162. {
  163. controller.Close();
  164. }
  165. Log.Debug("Done setting service recovery options.");
  166. }
  167. }
  168. [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
  169. public static ServiceRecoveryOptions GetServiceRecoveryOptions(string serviceName)
  170. {
  171. Exceptions.ThrowHelper.ThrowArgumentNullIfNull(serviceName, "serviceName");
  172. Exceptions.ThrowHelper.ThrowArgumentOutOfRangeIfEmpty(serviceName, "serviceName");
  173. Log.Debug("Getting service recovery options...");
  174. // 8KB is the largest buffer supported by QueryServiceConfig2
  175. const int bufferSize = 1024 * 8;
  176. IntPtr bufferPtr = IntPtr.Zero;
  177. ServiceRecoveryOptions recoveryOptions;
  178. ServiceController controller = null;
  179. try
  180. {
  181. // Open the service
  182. controller = new ServiceController(serviceName);
  183. uint dwBytesNeeded;
  184. // Allocate memory for struct
  185. bufferPtr = Marshal.AllocHGlobal(bufferSize);
  186. int queryResult = QueryServiceConfig2(
  187. controller.ServiceHandle.DangerousGetHandle(),
  188. SERVICE_CONFIG_FAILURE_ACTIONS,
  189. bufferPtr,
  190. bufferSize,
  191. out dwBytesNeeded);
  192. if (queryResult == 0)
  193. {
  194. throw new Win32Exception(Marshal.GetLastWin32Error(), "Unable to query the Service configuration.");
  195. }
  196. // Cast the buffer to a QUERY_SERVICE_CONFIG struct
  197. SERVICE_FAILURE_ACTIONS config =
  198. (SERVICE_FAILURE_ACTIONS)Marshal.PtrToStructure(bufferPtr, typeof(SERVICE_FAILURE_ACTIONS));
  199. recoveryOptions = new ServiceRecoveryOptions
  200. {
  201. DaysToResetFailAcount = (int) TimeSpan.FromSeconds(config.dwResetPeriod).TotalDays,
  202. RebootMessage = config.lpRebootMsg,
  203. CommandToLaunchOnFailure = config.lpCommand
  204. };
  205. int actionCount = config.cActions;
  206. if (actionCount != 0)
  207. {
  208. uint millisecondsToRestartService = 0;
  209. SC_ACTION[] actions = new SC_ACTION[actionCount];
  210. for (int i = 0; i < config.cActions; i++)
  211. {
  212. SC_ACTION action = (SC_ACTION)Marshal.PtrToStructure(
  213. (IntPtr)(config.lpsaActions.ToInt32() + (Marshal.SizeOf(typeof(SC_ACTION)) * i)),
  214. typeof(SC_ACTION));
  215. actions[i] = action;
  216. millisecondsToRestartService = action.Delay;
  217. }
  218. recoveryOptions.FirstFailureAction = GetServiceRecoveryAction(actions[0]);
  219. recoveryOptions.SecondFailureAction = GetServiceRecoveryAction(actions[1]);
  220. recoveryOptions.SubsequentFailureActions = GetServiceRecoveryAction(actions[2]);
  221. recoveryOptions.MinutesToRestartService =
  222. (int)TimeSpan.FromMilliseconds(millisecondsToRestartService).TotalMinutes;
  223. }
  224. }
  225. finally
  226. {
  227. // Clean up
  228. if (bufferPtr != IntPtr.Zero)
  229. {
  230. Marshal.FreeHGlobal(bufferPtr);
  231. }
  232. if (controller != null)
  233. {
  234. controller.Close();
  235. }
  236. Log.Debug("Done getting service recovery options.");
  237. }
  238. return recoveryOptions;
  239. }
  240. public static bool IsServiceInstalled(string serviceName)
  241. {
  242. if(string.IsNullOrEmpty(serviceName))
  243. throw new ArgumentNullException("serviceName");
  244. Log.DebugFormat("Checking if service '{0}' is already installed...", serviceName);
  245. bool returnValue = GetInstalledServices().Contains(serviceName);
  246. Log.DebugFormat("Service '{0}' is {1} installed.", serviceName, returnValue ? "already" : "not yet");
  247. return returnValue;
  248. }
  249. public static void AllowServiceToInteractWithDesktop(string serviceName)
  250. {
  251. if (string.IsNullOrEmpty(serviceName))
  252. throw new ArgumentNullException("serviceName");
  253. Log.DebugFormat("Allowing service '{0}' to interact with desktop...", serviceName);
  254. RegistryKey registryKey = null;
  255. try
  256. {
  257. registryKey = Registry.LocalMachine.OpenSubKey(
  258. string.Format(@"SYSTEM\CurrentControlSet\Services\{0}", serviceName), true);
  259. if (registryKey != null)
  260. {
  261. object value = registryKey.GetValue("Type");
  262. if (value != null)
  263. {
  264. registryKey.SetValue("Type", ((int)value | 256));
  265. }
  266. }
  267. }
  268. finally
  269. {
  270. if (registryKey != null)
  271. {
  272. registryKey.Close();
  273. }
  274. }
  275. Log.DebugFormat("Done allowing service '{0}' to interact with desktop.", serviceName);
  276. }
  277. public static List<string> GetInstalledServices()
  278. {
  279. return GetInstalledServices(s => true);
  280. }
  281. public static List<string> GetInstalledServices(Predicate<ServiceController> filter)
  282. {
  283. Log.Debug("Getting list of installed services...");
  284. var serviceControllers = Array.ConvertAll(
  285. ServiceController.GetServices(), s => s);
  286. var returnValue = new List<string>(
  287. Array.ConvertAll(
  288. Array.FindAll(serviceControllers, filter),
  289. s => s.ServiceName));
  290. Log.DebugFormat("Done getting list of installed services. Found '{0}' services.", returnValue.Count);
  291. return returnValue;
  292. }
  293. private static SC_ACTION GetScAction(ServiceRecoveryAction action, uint restartServiceAfter)
  294. {
  295. var scAction = new SC_ACTION();
  296. SC_ACTION_TYPE actionType = default(SC_ACTION_TYPE);
  297. switch (action)
  298. {
  299. case ServiceRecoveryAction.TakeNoAction:
  300. actionType = SC_ACTION_TYPE.None;
  301. break;
  302. case ServiceRecoveryAction.RestartTheService:
  303. actionType = SC_ACTION_TYPE.RestartService;
  304. break;
  305. case ServiceRecoveryAction.RestartTheComputer:
  306. actionType = SC_ACTION_TYPE.RebootComputer;
  307. break;
  308. case ServiceRecoveryAction.RunAProgram:
  309. actionType = SC_ACTION_TYPE.RunCommand;
  310. break;
  311. }
  312. scAction.Type = actionType;
  313. scAction.Delay = restartServiceAfter;
  314. return scAction;
  315. }
  316. private static ServiceRecoveryAction GetServiceRecoveryAction(SC_ACTION action)
  317. {
  318. ServiceRecoveryAction serviceRecoveryAction = default(ServiceRecoveryAction);
  319. switch (action.Type)
  320. {
  321. case SC_ACTION_TYPE.None:
  322. serviceRecoveryAction = ServiceRecoveryAction.TakeNoAction;
  323. break;
  324. case SC_ACTION_TYPE.RestartService:
  325. serviceRecoveryAction = ServiceRecoveryAction.RestartTheService;
  326. break;
  327. case SC_ACTION_TYPE.RebootComputer:
  328. serviceRecoveryAction = ServiceRecoveryAction.RestartTheComputer;
  329. break;
  330. case SC_ACTION_TYPE.RunCommand:
  331. serviceRecoveryAction = ServiceRecoveryAction.RunAProgram;
  332. break;
  333. }
  334. return serviceRecoveryAction;
  335. }
  336. [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
  337. private static void GrantShutdownPrivileges()
  338. {
  339. Log.Debug("Granting shutdown privileges to process user...");
  340. IntPtr tokenHandle = IntPtr.Zero;
  341. TOKEN_PRIVILEGES tkp = new TOKEN_PRIVILEGES();
  342. long luid = 0;
  343. int retLen = 0;
  344. try
  345. {
  346. IntPtr processHandle = Process.GetCurrentProcess().Handle;
  347. bool success = OpenProcessToken(processHandle, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref tokenHandle);
  348. if (!success)
  349. {
  350. throw new Win32Exception(Marshal.GetLastWin32Error(), "Unable to open process token.");
  351. }
  352. LookupPrivilegeValue(null, SE_SHUTDOWN_NAME, ref luid);
  353. tkp.PrivilegeCount = 1;
  354. tkp.Privileges.Luid = luid;
  355. tkp.Privileges.Attributes = SE_PRIVILEGE_ENABLED;
  356. success = AdjustTokenPrivileges(tokenHandle, false, ref tkp, 0, IntPtr.Zero, ref retLen);
  357. if (!success)
  358. {
  359. throw new Win32Exception(Marshal.GetLastWin32Error(), "Unable to shutdown priveleges.");
  360. }
  361. }
  362. finally
  363. {
  364. if (tokenHandle != IntPtr.Zero)
  365. {
  366. Marshal.FreeHGlobal(tokenHandle);
  367. }
  368. Log.Debug("Done granting shutdown privileges to process user.");
  369. }
  370. }
  371. }
  372. }