Setting a description in the ServiceInstaller of a .NET Service

Posted by

If you’re lucky enough to be using .NET 2.0 already, then you don’t need to worry. Version 2.0 of the ServiceInstaller class has a Description property which you can use to set the description of your service.

If you’re still on .NET 1.x, then no such luck and you’ve got to resort to a bit of hackery. I’ve seen code which goes in and modifies the registry directly, but nothing really that does it “properly” (I would define “properly” here as “going through the SCM API”)

So here’s a little bit of P/Invoke trickery to get a Description property working. The code is fairly simple, first of all you just need to set up the P/Invoke signatures:


[DllImport("advapi32.dll", CharSet=CharSet.Unicode, SetLastError=true)]
public static extern bool ChangeServiceConfig2(
    IntPtr hService, InfoLevel dwInfoLevel, 
    [MarshalAs(UnmanagedType.Struct)] ref SERVICE_DESCRIPTION lpInfo);

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

[DllImport("advapi32.dll", CharSet=CharSet.Unicode, SetLastError=true)]
public static extern IntPtr OpenSCManager(
    string lpMachineName, string lpDatabaseName,
    ServiceControlManagerType dwDesiredAccess);

[DllImport("advapi32.dll", SetLastError=true)]
public static extern bool CloseServiceHandle(
    IntPtr hSCObject);

public enum ServiceControlManagerType : int
{
    SC_MANAGER_CONNECT = 0x1,
    SC_MANAGER_CREATE_SERVICE = 0x2,
    SC_MANAGER_ENUMERATE_SERVICE = 0x4,
    SC_MANAGER_LOCK = 0x8,
    SC_MANAGER_QUERY_LOCK_STATUS = 0x10,
    SC_MANAGER_MODIFY_BOOT_CONFIG = 0x20,
    SC_MANAGER_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED |
                            SC_MANAGER_CONNECT |
                            SC_MANAGER_CREATE_SERVICE |
                            SC_MANAGER_ENUMERATE_SERVICE |
                            SC_MANAGER_LOCK |
                            SC_MANAGER_QUERY_LOCK_STATUS |
                            SC_MANAGER_MODIFY_BOOT_CONFIG
}

public enum ACCESS_TYPE : int
{
    SERVICE_QUERY_CONFIG = 0x1,
    SERVICE_CHANGE_CONFIG = 0x2,
    SERVICE_QUERY_STATUS = 0x4,
    SERVICE_ENUMERATE_DEPENDENTS = 0x8,
    SERVICE_START = 0x10,
    SERVICE_STOP = 0x20,
    SERVICE_PAUSE_CONTINUE = 0x40,
    SERVICE_INTERROGATE = 0x80,
    SERVICE_USER_DEFINED_CONTROL = 0x100,
    SERVICE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED |
                         SERVICE_QUERY_CONFIG |
                         SERVICE_CHANGE_CONFIG |
                         SERVICE_QUERY_STATUS |
                         SERVICE_ENUMERATE_DEPENDENTS |
                         SERVICE_START |
                         SERVICE_STOP |
                         SERVICE_PAUSE_CONTINUE |
                         SERVICE_INTERROGATE |
                         SERVICE_USER_DEFINED_CONTROL
}

public enum InfoLevel : int
{
    SERVICE_CONFIG_DESCRIPTION = 1,
    SERVICE_CONFIG_FAILURE_ACTIONS = 2
}

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
public struct SERVICE_DESCRIPTION
{
    public string lpDescription;
}

And then I just created a sub-class of ServiceInstaller which has a Description property. Pretty neat!


[RunInstaller(false)]
public class ServiceInstallerEx : ServiceInstaller
{
    private string description;

    public string Description
    {
        get { return description; }
        set { description = value; }
    }

    protected override void OnAfterInstall(IDictionary savedState)
    {
        // First, open the SCM
        IntPtr scm = ServiceApi.OpenSCManager(null, null,
            ServiceApi.ServiceControlManagerType.SC_MANAGER_ALL_ACCESS);

        // Then open the actual service
        IntPtr service = ServiceApi.OpenService(scm, ServiceName,
                             ServiceApi.ACCESS_TYPE.SERVICE_ALL_ACCESS);
        try
        {
            // Finally, set the description!!
            ServiceApi.SERVICE_DESCRIPTION desc =
                new ServiceApi.SERVICE_DESCRIPTION();
            desc.lpDescription = description;
            ServiceApi.ChangeServiceConfig2(service,
                ServiceApi.InfoLevel.SERVICE_CONFIG_DESCRIPTION, ref desc);
        }
        finally
        {
            ServiceApi.CloseServiceHandle(service);
        }

        base.OnAfterInstall (savedState);
    }
}

You’ll notice that all I do is update the config to set the description in the OnAfterInstall method. I don’t bother removing the description, because the regular Uninstall/Rollback would remove the service – description and all.

Usage of this class is just like the regular ServiceInstaller class, but now you get a Description property as well. You could also add more properties (for example, to affect recovery options, etc) if you like.

blog comments powered by Disqus