Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A ServiceController Class that Contains the Path to the Executable

0.00/5 (No votes)
31 May 2008 1  
Extends the System.ServiceProcess.ServiceController class to obtain the path to the process executable

Introduction

While doing some work with Windows Services, I needed the path to the process executable, which led me to an article here on The Code Project by aleksisa called Create A Web Service Method to Get NT Service Information.

The article was great, containing all the information necessary to implement the feature.

Going over aleksisa code, I started thinking that this is the kind of feature that should be implemented using Inheritance, and therefore set about putting this into code.

This article is what I came up with.

Using the Code

Because the code is implemented using Inheritance, you can continue using the ServiceController class as before, just make sure to change the reference to the correct namespace.

One of the most important differences between aleksisa and my code is, that the ServiceController class does not handle security internally. This means that all impersonations etc. need to be handled by the calling code.

This is the class as I implemented it (below I mention a few details):

public class ServiceController : System.ServiceProcess.ServiceController
{
    private string m_ImagePath;
    private ServiceController[] m_DependentServices;
    private ServiceController[] m_ServicesDependedOn;

    public ServiceController()
        : base()
    {
    }

    public ServiceController( string name )
        : base( name )
    {
    }

    public ServiceController( string name, string machineName )
        : base( name, machineName )
    {
    }

    public string ImagePath
    {
        get
        {
            if( m_ImagePath == null )
            {
                m_ImagePath = GetImagePath();
            }
            return m_ImagePath;
        }
    }

    public new ServiceController[] DependentServices
    {
        get
        {
            if( m_DependentServices == null )
            {
                m_DependentServices = 
                     ServiceController.GetServices( base.DependentServices );
            }
            return m_DependentServices;
        }
    }

    public new ServiceController[] ServicesDependedOn
    {
        get
        {
            if( m_ServicesDependedOn == null )
            {
                m_ServicesDependedOn = 
                      ServiceController.GetServices( base.ServicesDependedOn );
            }
            return m_ServicesDependedOn;
        }
    }

    public static new ServiceController[] GetServices()
    {
        return GetServices( "." );
    }

    public static new ServiceController[] GetServices( string machineName )
    {
        return GetServices( System.ServiceProcess.ServiceController.GetServices
            ( machineName ) );
    }

    private string GetImagePath()
    {
        string registryPath = @"SYSTEM\CurrentControlSet\Services\" + ServiceName;
        RegistryKey keyHKLM = Registry.LocalMachine;

        RegistryKey key;
        if( MachineName != "" )
        {
            key = RegistryKey.OpenRemoteBaseKey
              ( RegistryHive.LocalMachine, this.MachineName ).OpenSubKey( registryPath );
        }
        else
        {
            key = keyHKLM.OpenSubKey( registryPath );
        }

        string value = key.GetValue( "ImagePath" ).ToString();
        key.Close();
        return ExpandEnvironmentVariables( value );
        //return value;
    }

    private string ExpandEnvironmentVariables( string path )
    {
        if( MachineName == "" )
        {
            return Environment.ExpandEnvironmentVariables( path );
        }
        else
        {
            string systemRootKey = @"Software\Microsoft\Windows NT\CurrentVersion\";

            RegistryKey key = RegistryKey.OpenRemoteBaseKey
                 ( RegistryHive.LocalMachine, MachineName ).OpenSubKey( systemRootKey );
            string expandedSystemRoot = key.GetValue( "SystemRoot" ).ToString();
            key.Close();

            path = path.Replace( "%SystemRoot%", expandedSystemRoot );
            return path;
        }
    }

    private static ServiceController[] GetServices
         ( System.ServiceProcess.ServiceController[] systemServices )
    {
        List<ServiceController> services = new List<ServiceController>
            ( systemServices.Length );
        foreach( System.ServiceProcess.ServiceController service in systemServices )
        {
            services.Add( new ServiceController
                ( service.ServiceName, service.MachineName ) );
        }
        return services.ToArray();
    }
}

First of all, I added a new property, ImagePath, that is used to retrieve the path to the executable.

I also hid two properties and one static method from the parent System.ServiceProcess.ServiceController. These would usually return an array of type System.ServiceProcess.ServiceController, so I changed them to work with the new ServiceController.

Please note that the new properties only obtain their value(s) at the first call, not at instantiation. While this is slightly more efficient, it also means that if, while obtaining data from a remote machine, the connection is severed, there may be unpredictable results.

While the code mostly uses aleksisa implementation, I did change a few things. For example, for local paths, I used the Environment.ExpandEnvironmentVariables method, mentioned in dchrno's comment.

This is how you would use it:

static void Main( string[] args )
{
    ServiceController[] services = ServiceController.GetServices( "bwdemo" );

    foreach( ServiceController service in services )
    {
        Console.WriteLine("ServiceName: {0}; DisplayName: {1}", 
                service.ServiceName, service.DisplayName );
        Console.WriteLine("Image Path: {0}\n", service.ImagePath );
    }

    Console.ReadLine();
}

Handling Security

Although I tried testing against a remote machine, this was more to test the expansion of environmental variables than for security purposes.

As I mentioned earlier, security impersonation is not handled in the ServiceController class, and must be done in the calling code. I was not able to test this myself, but then, the code that requires it is all copy/paste from aleksisa, so if that worked, so should this.

As an aside, if all you want is a list of services, it is enough to have the same username and password setup on the remote computer.

Points of Interest

One of the interesting things that came up while testing was that I didn't need to expand the environmental variables. For some reason, the string being returned from the Registry already had the full path in expanded form.

Also, I know there are no comments. I did this in half an hour and decided to post it to The Code Project.

History

  • May 31st, 2008: Initial post

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here