Thursday, April 21, 2011

Changing Start Mode of a Windows Service

Recently I needed to change the Start Type of a Windows Service from another application. Some PCs were infected with the Conficker worm. One of the things this worm does is shut down some Windows features that might help in discovering/removing the worm. One of the services it affects is BITS - used in downloading Windows Updates - by stopping the service and changing it's start mode to Disabled.

There is a managed option in .NET - the System.ServiceProcess.ServiceController class which exposes some useful Properties and Methods for managing a Service. However, it provides no way to change the start mode of a Service. In looking into this I came across this question on StackOverflow which suggests the best way to achieve this is through the Win32 API using P/Invoke.

The awesome wiki pinvoke.net is a great resource to get started. It has helped me many times in the past and it certainly helped me to write some of this code here.

First of all we need to make calls to functions in the advapi.dll. We need to call ChangeServiceConfig, OpenSCManager and OpenService.

ChangeServiceConfig is where we can actually make the change we want to the Start Mode. But you need to pass this a handle to a service. So we also need to call OpenService. This in turn requires a handle to a SCManager so we need to call
OpenSCManager. Here is the code - feel free to use and improve. It is a static class that provides one method ChangeStartMode

public static class ServiceHelper
{
    [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern Boolean ChangeServiceConfig(
        IntPtr hService,
        UInt32 nServiceType,
        UInt32 nStartType,
        UInt32 nErrorControl,
        String lpBinaryPathName,
        String lpLoadOrderGroup,
        IntPtr lpdwTagId,
        [In] char[] lpDependencies,
        String lpServiceStartName,
        String lpPassword,
        String lpDisplayName);

    [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    static extern IntPtr OpenService(
        IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess);

    [DllImport("advapi32.dll", EntryPoint = "OpenSCManagerW", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern IntPtr OpenSCManager(
        string machineName, string databaseName, uint dwAccess);

    [DllImport("advapi32.dll", EntryPoint = "CloseServiceHandle")]
    public static extern int CloseServiceHandle(IntPtr hSCObject);

    private const uint SERVICE_NO_CHANGE = 0xFFFFFFFF;
    private const uint SERVICE_QUERY_CONFIG = 0x00000001;
    private const uint SERVICE_CHANGE_CONFIG = 0x00000002;
    private const uint SC_MANAGER_ALL_ACCESS = 0x000F003F;

    public static void ChangeStartMode(ServiceController svc, ServiceStartMode mode)
    {
        var scManagerHandle = OpenSCManager(null, null, SC_MANAGER_ALL_ACCESS);
        if (scManagerHandle == IntPtr.Zero)
        {
            throw new ExternalException("Open Service Manager Error");
        }

        var serviceHandle = OpenService(
            scManagerHandle,
            svc.ServiceName,
            SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG);

        if (serviceHandle == IntPtr.Zero)
        {
            throw new ExternalException("Open Service Error");
        }

        var result = ChangeServiceConfig(
            serviceHandle,
            SERVICE_NO_CHANGE,
            (uint)mode,
            SERVICE_NO_CHANGE,
            null,
            null,
            IntPtr.Zero,
            null,
            null,
            null,
            null);

       if (result == false)
       {
           int nError = Marshal.GetLastWin32Error();
           var win32Exception = new Win32Exception(nError);
           throw new ExternalException("Could not change service start type: "
               + win32Exception.Message);
       }

       CloseServiceHandle(serviceHandle);
       CloseServiceHandle(scManagerHandle);
   }

To use this just provide an instance of ServiceController and a ServiceStartMode.

var svc = new ServiceController("BITS");
ServiceHelper.ChangeStartMode(svc, ServiceStartMode.Automatic);

// You can then Start the service if necessary. 
if (svc.Status != ServiceControllerStatus.Running)
{
   svc.Start();
}

// And of course you should close the service when no longer needed
svc.Close();
http://msdn.microsoft.com/en-us/library/system.serviceprocess.servicecontroller.aspx

12 comments:

  1. Hi Peter,
    excellent code it saved me a lot of time, thanks.
    Is there a possibility to get the current service status auto/manual?

    ReplyDelete
  2. Thank you Peter. You saved us a lot of time.

    ReplyDelete
  3. @nathon You can use the ServiceController.Status Property to get the status of a service. If you want to get the current StartUp mode then you would need to modify the code above to do so using different win32 API calls. Alternatively you could use WMI - just so a search for "C# Windows Service StartUp Mode WMI"

    ReplyDelete
  4. Nice code:

    I got Access Denide when running on WS 2008 R2. So I added the following


    private const uint SC_MANAGER_CONNECT = 0x0001;
    private const uint SC_MANAGER_ENUMERATE_SERVICE = 0x0004;


    var scManagerHandle = OpenSCManager(null, null, SC_MANAGER_CONNECT + SC_MANAGER_ENUMERATE_SERVICE);

    Thanks

    ReplyDelete
  5. Here's the same code without the memory leak:

    [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern Boolean ChangeServiceConfig(
    IntPtr hService,
    UInt32 nServiceType,
    UInt32 nStartType,
    UInt32 nErrorControl,
    String lpBinaryPathName,
    String lpLoadOrderGroup,
    IntPtr lpdwTagId,
    [In] char[] lpDependencies,
    String lpServiceStartName,
    String lpPassword,
    String lpDisplayName);

    private const uint SERVICE_NO_CHANGE = 0xFFFFFFFF;
    private const uint SERVICE_QUERY_CONFIG = 0x00000001;
    private const uint SERVICE_CHANGE_CONFIG = 0x00000002;
    private const uint SC_MANAGER_ALL_ACCESS = 0x000F003F;

    /// <summary>
    /// Set the <see cref="ServiceStartMode"/> of the <paramref name="service"/>.
    /// </summary>
    /// <param name="service">Service to set the start mode of.</param>
    /// <param name="mode">Start mode of the <paramref name="service"/>.</param>
    public static void SetStartMode(this ServiceController service, ServiceStartMode mode)
    {
    using (var serviceHandle = service.ServiceHandle)
    {
    if (!
    ChangeServiceConfig(
    serviceHandle.DangerousGetHandle(),
    SERVICE_NO_CHANGE,
    (UInt32)mode,
    SERVICE_NO_CHANGE,
    null,
    null,
    IntPtr.Zero,
    null,
    null,
    null,
    null)
    )
    {
    throw new ExternalException("Could not change service start type", new Win32Exception(Marshal.GetLastWin32Error()));
    }
    }
    }

    ReplyDelete
  6. About "There is a managed option in .NET - the System.ServiceProcess.ServiceController class which exposes some useful Properties and Methods for managing a Service. However, it provides no way to change the start mode of a Service." Why is it so? I mean why was this functionality left out? Also can this be invoked on remote machine like .NET's ServiceController takes MachineName?

    ReplyDelete
  7. I think you should always close the handles. After the native calls I'd call:

    [DllImport("advapi32.dll", EntryPoint = "CloseServiceHandle")]
    public static extern int CloseServiceHandle(IntPtr hSCObject);

    and for the managed ServiceController:

    var svc = new ServiceController("BITS");
    svc.Close();

    If you don't do that you might end up having a "Disabled" service at some point which might need a full system reboot to get rid off. This actually sucks if your application e.g. is supposed to run on a server environment for several months where there must not be any downtime at all.

    ReplyDelete
    Replies
    1. Absolutely - thanks. I've updated the post.

      Delete
  8. hiii

    nice blog and good conten

    Final Year Project Help :- Qualityassignmenthelp is best company that offers to free final year project help, thesis writing help and all types programming project.


    Headquater USA

    100 Mainstreet Blvd. (South) 44th Floor, Highland Tower Miami, Florida 33148
    +16059567291
    service@qualityassignmenthelp.com
    www.qualityassignmenthelp.com
    Business Hours
    Availability: 24 hrs

    ReplyDelete
  9. Hi Kelly am getting Error in my tool while using the code, it say service helper does not exists ??? am I missing any references but I have added all references which you mentioned...

    ReplyDelete
  10. So i've translated this over to Vb.net and it works.

    Only question i have is how can i set a startup type to "Automatic (Delayed Start)"

    I've read through ServiceStartMode Enumeration on Msdn and it makes no mention of this.
    I'm sure i could just edit the registry directly, but then that defeats the purpose of this entire class.


    I'm guessing this is just a limitation in general, even getting the StartMode using WMI doesn't distinguish between the two. WMI just reports back with Auto.

    Just let me know what your thoughts on this are.

    ReplyDelete
  11. Hi,

    Thank you for the great code:
    I created a nuget package with this as extension methods:
    https://www.nuget.org/packages/WindowsServiceExtensions/
    I hope you don't mind... I added you as the original author.

    I also added a method to set/reset the delayed autostart

    ReplyDelete