Freigeben über


Using .NET Code to Set a Windows Service to Automatically Restart on Failure

This one popped up this week as I was investigating why a particular Windows service written in .NET 1.1 was terminating. After a quick MSN search, I only came up with a few fragments of C#, but not a complete solution. In attempting my own implementation, a few things came up that may be of interest to others.

 

Firstly, the relevant Win32 API function is “ChangeServiceConfig2”. It’s a “jack of all trades” function, whose flexibility makes it a bit of a pain to call from .NET. The difficulty is that we need to pass it a structure that itself contains an array of structures.

 

Before getting to “ChangeServiceConfig2”, we first need to get a handle to the service via a call to the “OpenService” Win32 API function. It’s not mentioned in any documentation I’ve seen, but you need to request both SERVICE_CHANGE_CONFIG and SERVICE_START access rights, otherwise you’ll receive an “access denied” error from “ChangeServiceConfig2”.

 

“OpenService” and “OpenSCManager” (which is also needed in the solution) return Windows handles. A common .NET misconception is that Windows handles should be treated as 32-bit integers (int, Int32 etc). In “unmanaged” land, the HANDLE type is actually a pointer to void (void*). This means IntPtr should be used to deal with these.

 

The key structures for this task are SC_ACTION  and SERVICE_FAILURE_ACTIONS. The SC_ACTION structure describes what Windows should do in the event of a service failure. It is possible to provide serveral of these so that different actions can be taken on subsequent service failures. These all need to be placed into an array, which itself is then placed into SERVICE_FAILURE_ACTIONS (this is the pain mentioned above).

 

struct SC_ACTION
{
[MarshalAs(UnmanagedType.U4)]
public SC_ACTION_TYPE Type;
[MarshalAs(UnmanagedType.U4)]
public UInt32 Delay;

}

struct SERVICE_FAILURE_ACTIONS
{
[MarshalAs(UnmanagedType.U4)]
public UInt32 dwResetPeriod;
[MarshalAs(UnmanagedType.LPStr)]
public String lpRebootMsg;
[MarshalAs(UnmanagedType.LPStr)]
public String lpCommand;
[MarshalAs(UnmanagedType.U4)]
public UInt32 cActions;
public IntPtr lpsaActions;
}

 

In preparing the structures to pass to “ChangeServiceConfig2”, there is a requirement to perform pointer arithmetic in order to construct the array of failure actions. An arbitrary cast to a 32-bit integer would make the solution incompatible with 64-bit Windows. The safest option is to use Int64 for pointer arithmetic.

 

Marshal.StructureToPtr(action2, (IntPtr)((Int64)actionPtr + Marshal.SizeOf(typeof(SC_ACTION))), false);

 

After a bit of marshalling, we’re actually ready to call “ChangeServiceConfig2”. The good news is that this is straight forward. The only thing remaining is to handle any errors the .NET way.

 

if (changeResult == 0)

{

throw new Win32Exception(Marshal.GetLastWin32Error(), "Unable to change the Service configuration.");

}

 

Finally, since we're working with unmanaged resources, we need to make sure that things are cleaned up properly. The best pattern for this is to use a try-finally block.

 

I’ve attached my first attempt at a solution, but it still needs testing on 64-bit platforms (fingers crossed). Note that under .NET 2.0, we would probably do a few things differently (such as make use of the new SafeHandle class). That said, the solution runs under .NET 2.0.

 

BTW, for the security conscious, you should actually add a Link Demand to methods that that use the Marshal class. I’ll leave the details for another day, but your method will be exposed to certain types of security issues if you don’t.

 

[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]

 

Enjoy!

ServiceControlManager.cs

Comments

  • Anonymous
    July 30, 2006
    Great article.  If you aren't familiar with it, there is a great Wiki out there for stuff like this called <a href="http://www.pinvoke.net/">PInvoke.Net</a>.  You might contribute this to Pinvoke.  It's lacking an article on ChangeServiceConfig2.  It only has ChangeServiceConfig, located here (with a link to a future article on ChangeServiceConfig2):

    http://www.pinvoke.net/default.aspx/advapi32/ChangeServiceConfig.html

  • Anonymous
    July 30, 2006
    You've been kicked (a good thing) - Trackback from DotNetKicks.com
  • Anonymous
    January 16, 2008
    VERY GOOD/USEFULL POST DUDE ;)
  • Anonymous
    May 23, 2011
    Excellent code sample. Helped me a lot. Thanks for the contribution