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 );
}
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