Showing posts with label pinvoke. Show all posts
Showing posts with label pinvoke. Show all posts

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