Click here to Skip to main content
15,867,330 members
Articles / Programming Languages / C#
Article

A ServiceController Class that Contains the Path to the Executable

Rate me:
Please Sign up or sign in to vote.
5.00/5 (11 votes)
31 May 2008CPOL2 min read 60.7K   508   25   5
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):

C#
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:

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Israel Israel
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 5 Pin
H0tHead23-Dec-14 21:34
H0tHead23-Dec-14 21:34 
QuestionMy vote of 5 Pin
dl4gbe30-Jan-14 22:43
dl4gbe30-Jan-14 22:43 
GeneralAn easier way to do this Pin
katrash3-Jun-08 3:42
katrash3-Jun-08 3:42 
GeneralRe: An easier way to do this Pin
DanielWehrle12-Nov-08 3:09
DanielWehrle12-Nov-08 3:09 
GeneralRe: An easier way to do this Pin
dl4gbe30-Jan-14 22:42
dl4gbe30-Jan-14 22:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.