Setting recovery options on windows services
I've recently written a small class to help me change recovery options on a windows service (most of code I found online somewhere). The code creates a FailureAction for the first, second, and subsequent failures. Each Failure object contains a type (None, Restart, Reboot, RunCommand), and a Delay (int) in milliseconds. These objects are packaged inside a struct and passed into ChangeServiceConfig2 (WinAPI P/Invoke). However, when I actually right-click on a service on the console and go to the Recovery tab, you can only set the delay ("Restart server after" field) once for all failures (first, second and subsequent). When I set this programmatically, it takes the delay from the first FailureAction and ignores all others. Does anyone know why this is the case? Why do we have to pass in a delay value for all FailureAction objects when only the first one gets used? Am I misunderstanding something?
Also, setting dwResetPeriod/"Reset fail count after" doesn't seem to have any effect.
Code:
public class ServiceConfigurator
{
private const int SERVICE_ALL_ACCESS = 0xF01FF;
private const int SC_MANAGER_ALL_ACCESS = 0xF003F;
private const int SERVICE_CONFIG_DESCRIPTION = 0x1;
private const int SERVICE_CONFIG_FAILURE_ACTIONS = 0x2;
private const int SERVICE_NO_CHANGE = -1;
private const int ERROR_ACCESS_DENIED = 5;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct SERVICE_FAILURE_ACTIONS
{
public int dwResetPeriod;
[MarshalAs(UnmanagedType.LPWStr)]
public string lpRebootMsg;
[MarshalAs(UnmanagedType.LPWStr)]
public string lpCommand;
public int cActions;
public IntPtr lpsaActions;
}
[DllImport("advapi32.dll", EntryPoint = "ChangeServiceConfig2")]
private static extern bool ChangeServiceFailureActions(IntPtr hService, int dwInfoLevel, [MarshalAs(UnmanagedType.Struct)] ref SERVICE_FAILURE_ACTIONS lpInfo);
[DllImport("advapi32.dll", EntryPoint = "ChangeServiceConfig2")]
private static extern bool ChangeServiceDescription(IntPtr hService, int dwInfoLevel, [MarshalAs(UnmanagedType.Struct)] ref SERVICE_DESCRIPTION lpInfo);
[DllImport("kernel32.dll")]
private static extern int GetLastError();
private IntPtr _ServiceHandle;
public IntPtr ServiceHa开发者_运维技巧ndle { get { return _ServiceHandle; } }
public ServiceConfigurator(ServiceController svcController)
{
this._ServiceHandle = svcController.ServiceHandle.DangerousGetHandle();
}
public void SetRecoveryOptions(FailureAction pFirstFailure, FailureAction pSecondFailure, FailureAction pSubsequentFailures, int pDaysToResetFailureCount = 0)
{
int NUM_ACTIONS = 3;
int[] arrActions = new int[NUM_ACTIONS * 2];
int index = 0;
arrActions[index++] = (int)pFirstFailure.Type;
arrActions[index++] = pFirstFailure.Delay;
arrActions[index++] = (int)pSecondFailure.Type;
arrActions[index++] = pSecondFailure.Delay;
arrActions[index++] = (int)pSubsequentFailures.Type;
arrActions[index++] = pSubsequentFailures.Delay;
IntPtr tmpBuff = Marshal.AllocHGlobal(NUM_ACTIONS * 8);
try
{
Marshal.Copy(arrActions, 0, tmpBuff, NUM_ACTIONS * 2);
SERVICE_FAILURE_ACTIONS sfa = new SERVICE_FAILURE_ACTIONS();
sfa.cActions = 3;
sfa.dwResetPeriod = pDaysToResetFailureCount;
sfa.lpCommand = null;
sfa.lpRebootMsg = null;
sfa.lpsaActions = new IntPtr(tmpBuff.ToInt32());
bool success = ChangeServiceFailureActions(_ServiceHandle, SERVICE_CONFIG_FAILURE_ACTIONS, ref sfa);
if(!success)
{
if(GetLastError() == ERROR_ACCESS_DENIED)
throw new Exception("Access denied while setting failure actions.");
else
throw new Exception("Unknown error while setting failure actions.");
}
}
finally
{
Marshal.FreeHGlobal(tmpBuff);
tmpBuff = IntPtr.Zero;
}
}
}
Trevor
The sc command provides a great way for automating service management. To call from code just do a Process.Start("sc", "args")
and redirect the output if you want to get the result.
This one line tells the service to restart twice after waiting 1 min. on failure. After one day goes by it resets the failure count. You can also set it up to run programs, etc. on subsequent failures.
sc failure myservice reset= 86400 actions= restart/60000/restart/60000//
http://technet.microsoft.com/en-us/library/cc742019(v=ws.10).aspx
I have discovered that all flavors of win 7 have no ability to restart some services even when their restart upon failure is set to yes.
I ended up writing a service to monitor start and stop status as well as (heuristics derived) responsiveness (eg hung not hung). Would you like that services code posted here?
Edit: Added the code
Ok, the following is the service code I referred to. Bear in mind:
- This code is designed to point to "C:\appMon" for logging, and cpu util. threshold values.
- This code is ugly in that it was cranked out while I was slammed with other priorities - as a result, it could be rewritten to handle any number of services, user defined log and cfg paths, etc.
- The service it was written for is the notorious windows spooler (spoolsv.exe).
To install the service, use installutil.exe from MS. Make sure the service runs as a "local system account" or it will not be able to start/stop services.
appMon.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.ServiceProcess;
using System.Text;
using System.Timers;
using System.Windows.Forms;
using System.IO;
using System.Net.Mail;
using System.Threading;
using System.Management;
namespace appMon
{
public class appMon : ServiceBase
{
public const string serviceName = "appMon";
public appMon()
{
InitializeComponent();
}
private void InitializeComponent()
{
this.ServiceName = serviceName;
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose(bool disposing)
{
// free instantiated object resources not handled by garbage collector
base.Dispose(disposing);
}
public static string getCurrUser()
{// gets the owner of explorer.exe/UI to determine current logged in user
String User = String.Empty;
String Domain = String.Empty;
String OwnerSID = String.Empty;
string processname = String.Empty;
int PID = Process.GetProcessesByName("explorer")[0].Id;
ObjectQuery sq = new ObjectQuery
("Select * from Win32_Process Where ProcessID = '" + PID + "'");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(sq);
foreach (ManagementObject oReturn in searcher.Get())
{
string[] o = new String[2];
oReturn.InvokeMethod("GetOwner", (object[])o);
User = o[0];
System.IO.StreamWriter sr = new System.IO.StreamWriter(@"C:\user.txt");
sr.WriteLine("\\" + o[2] + "\\" + o[1] + "\\" + o[0]);
return User;
}
return User;
}
public static int readConfigFile()
{
int cputime = 5; // 5 min dflt
try
{
string readcfg;
readcfg = File.ReadAllText(@"c:\appMon\cpuUtilization.txt");
cputime = Convert.ToInt16(readcfg);
return cputime;
}
catch (Exception e)
{
MessageBox.Show(e.ToString());
return cputime; // 5 min dflt
}
}
public static void logEvents(bool spoolerHang, bool appHang, string msg)
{
try
{
StreamWriter sw;
sw = File.AppendText(@"c:\appMon\appMonLog.txt");
sw.WriteLine(@"appMon spoolsv.exe event: " + "," + System.Environment.MachineName + "," + System.DateTime.Now + "," + msg);
sw.Close();
}
catch (Exception e)
{
MessageBox.Show(e.ToString());
}
}
/// <summary>
/// Start this service.
/// </summary>
protected override void OnStart(string[] args)
{// upon appMon load, a polling interval is set (in milliseconds 1000 ms = 1 s)
System.Timers.Timer pollTimer = new System.Timers.Timer();
pollTimer.Elapsed += new ElapsedEventHandler(pollTimer_Elapsed);
pollTimer.Interval = 20000; // 20 sec
pollTimer.Enabled = true;
}
public static void StartService(string serviceName, int timeoutMilliseconds)
{
ServiceController service = new ServiceController(serviceName);
try
{
TimeSpan timeout = TimeSpan.FromMilliseconds(timeoutMilliseconds);
if (service.Status == ServiceControllerStatus.Stopped) // if service is not running...
{
service.Start(); // ...start the service
}
}
catch(Exception e)
{
MessageBox.Show(e.ToString());
}
}
public static void StopService(string serviceName, int timeoutMilliseconds)
{
ServiceController service = new ServiceController(serviceName);
try
{
TimeSpan timeout = TimeSpan.FromMilliseconds(timeoutMilliseconds);
if (service.Status == ServiceControllerStatus.Running) // if service is running...
{
service.Stop(); //...stop the service
}
service.WaitForStatus(ServiceControllerStatus.Stopped, timeout);
}
catch (Exception e)
{
MessageBox.Show(e.ToString());
}
}
public static void delFiles(string path)
{
string[] filePaths = Directory.GetFiles(path);
foreach (string filePath in filePaths)
{
try
{
File.Delete(filePath);
}
catch (Exception e)
{
// TODO: !log to file instead!
MessageBox.Show("error deleting files: " + e.ToString());
}
}
}
public static void getServiceInfo(string serviceName)
{
ServiceController service = new ServiceController(serviceName);
ServiceController[] depServices = service.ServicesDependedOn;
List<string> depServicesList = new List<string>();
foreach (ServiceController sc in depServices)
{
depServicesList.Add(sc.ServicesDependedOn.ToString());
logEvents(false, false, sc.ServicesDependedOn.ToString());
}
}
void pollTimer_Elapsed(object sender, ElapsedEventArgs e)
{// polling interval has elapsed
getServiceInfo("spooler");
ServiceController serviceSpooler = new ServiceController("spooler");
if (serviceSpooler.Status == ServiceControllerStatus.Stopped)
{
logEvents(true, false, "Print Spooler is: " + serviceSpooler.Status.ToString());
serviceSpooler.Refresh();
serviceSpooler.Start();
logEvents(true, false, "Print Spooler is: " + serviceSpooler.Status.ToString());
}
int cputime = readConfigFile();
// get active processes (exe's, including services)
Process[] processlist = Process.GetProcesses();
// iterate through process list
foreach (Process theprocess in processlist)
{
// assign local variable to iterator - cures the foreach "gotcha"
Process p = theprocess;
if (p.ProcessName == "spoolsv") // "spoolsv" = windows name for spoolsv.exe aka "spooler"
{
if (p.TotalProcessorTime.Minutes > cputime) // has current spooler thread occupied >= cputime # mins of CPU time?
{
logEvents(true, false, "spoolsv.exe CPU time (mins): " + p.TotalProcessorTime.Minutes.ToString());
p.Refresh();
StopService("spooler", 0);
StartService("spooler", 0);
}
}
}
}
/// <summary>
/// Stop this service.
/// </summary>
///
protected override void OnStop()
{
}
}
}
精彩评论