Title:  Web Method To Get NT Service Info
Author:      Greg Dubinovskiy 
Email:       [email protected]
Environment: C#, VB.NET
Keywords:    Web Service, NT Service Info, Manage NT Service, Impersonate Web User
Level:       Intermediate
Description: Create a web service method to get NT service information
Section      Miscellaneous
SubSection   General

Introduction

Recently, I created a mobile application-Siccolo that allows me to manage SQL Servers by using web services hosted on public domain (see more information here about how to develop a mobile management tool)
As a part of management tool, I needed to show some information about selected NT Service, such as path to service executable. For example, services.msc shows it like this:

In my mobile management tool, I needed to display in similar manner:


The code presented retrieves information about Path to Executable for selected NT Service.

Background (optional, but needed)

My "managing" web service is hosted under SSL with "Integrated Windows" authentication being set. Therefore, mobile application required to pass network credentials. And this is needed to be able remote access to get information from the registry on the remote machine.
So, as we can see Web Services is quickly finding its way into major system integration development efforts. In back office and legacy systems alike, Web Servicess has become a main focus in providing mechanisms to make data move more freely between and across long-closed boundaries. An entire industry has joined forces and pushes forward in overcoming both technical hurdles such as security and market specific hurdles such as the need of standardized message formats. While these efforts continue, an entirely different market is rapidly maturing: the mobile devices, software and services market. Very interesting solution options arise in the cross section, where Web Services meets mobility. The Microsoft® Windows® .NET Compact Framework opens up the world of Web Services to Pocket PCs, and the wireless nature of Pocket PCs opens up the world of mobility to Web Services. This means that new fuel is added to the expansion of the XML-based Web Services provider market. In this article, we will see a sample Pocket PC project that uses Web Services to manage Windows (NT) service from Windows Mobile device!

Using the code

.NET provides just the tool for this task - System.ServiceProcess.ServiceController class. To create an instance of System.ServiceProcess.ServiceController:
	// C# //
	...
	System.ServiceProcess.ServiceController Service;
	if (this.m_MachineName!="")
		{Service = new ServiceController(this.m_ServiceName, this.m_MachineName ) ;}
	else
	{Service = new ServiceController(this.m_ServiceName ) ;}
	...
And the fact that authentication (Integrated Windows authentication or Basic) is in place on IIS, actually helps here. In order to be able to access service(s) on the different machine than web service host, web service needs to "assume" an identity of authenticated user. Normally, web service is running under ASP.NET user with minimum privileges and I needed to impersonate authenticated user with the web service.
On the server side, to retrieve authenticated user, we need to use System.Web.Services.WebService.User and then impersonate: and code does just that - "Impersonates the user represented by the WindowsIdentity object."
	// C# //
	...
	
	System.Security.Principal.WindowsImpersonationContext impersonationContext;
			impersonationContext = 
				((System.Security.Principal.WindowsIdentity)User.Identity).Impersonate();
	...	
To retrieve path to executable, we can look under HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services, find the selected service, and get "ImagePath"




To read value of a registry key (on the remote machine, or on the local machine):
	private string ReadRegestryKey(string RegistryKey, out string ErrorInfo)
	{
		try
		{
			string Value="";
			ErrorInfo ="";
			
			RegistryKey Key;
			RegistryKey KeyHKLM = Registry.LocalMachine;
			try
			{
				if (this.m_MachineName !="" )	//open on remote machine
					Key = Microsoft.Win32.RegistryKey.OpenRemoteBaseKey(
						RegistryHive.LocalMachine, this.m_MachineName
							).OpenSubKey(RegistryKey);
				else
					Key = KeyHKLM.OpenSubKey(RegistryKey);
               
				Value = Key.GetValue("ImagePath").ToString();
				Key.Close();
			}
			catch (Exception ex_open_key)
			{
				ErrorInfo = "Error Accessing Registry [" + ex_open_key.ToString() + "]";
				return "";
			}
			return Value;
		}
		catch (Exception ex_read_registry)
		{
			ErrorInfo =   ex_read_registry.Message;
			return "";
		}
	}
Once Path to executable is extracted, then we need to check, if path needs to be "extracted" from something like "%SystemRoot%\system32\..." to the actual path.
Value of %SystemRoot% can be found in the registry:


	private string ExpandEnvironmentString (string Path)
	{
		string SystemRootKey = "Software\\Microsoft\\Windows NT\\CurrentVersion\\";
		RegistryKey Key;

		if (this.m_MachineName !="" )
			Key = Microsoft.Win32.RegistryKey.OpenRemoteBaseKey(
				RegistryHive.LocalMachine, this.m_MachineName
					).OpenSubKey(SystemRootKey);
		else
			Key = Registry.LocalMachine.OpenSubKey(SystemRootKey);
    
		string ExpandedSystemRoot ="";
		ExpandedSystemRoot = Key.GetValue("SystemRoot").ToString();
		Key.Close();

		Path = Path.Replace ("%SystemRoot%", ExpandedSystemRoot);
		
		return Path;
	}
And finally:
	public string PathToExecutable(WindowsPrincipal User)
	{
		//HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services.
		string RegistryKey = "SYSTEM\\CurrentControlSet\\Services\\" + this.m_ServiceName;

		string ErrorInfo="";
		System.Security.Principal.WindowsImpersonationContext impersonationContext;
		impersonationContext = 
				((System.Security.Principal.WindowsIdentity)User.Identity).Impersonate();

		string Path= this.ReadRegestryKey(RegistryKey, out ErrorInfo);
		if ( Path.IndexOf("%")>0)
		{
			Path = ExpandEnvironmentString(Path);
		}
		impersonationContext.Undo();
		return Path;
	}
Notice, how call to ReadRegestryKey() and call to ExpandEnvironmentString() "wrapped in"
	impersonationContext = 
				((System.Security.Principal.WindowsIdentity)User.Identity).Impersonate();
	...
	...
	impersonationContext.Undo();
Then, the actual web method:
	[WebMethod]
	public bool GetNTServiceInfo(string RemoteServerAddress , 
					string NTServiceName, 
					out string ServiceInfo_XML , 
					out string ErrorInfo )
	{
		try
		{
			string ToDebugSetting = System.Configuration.ConfigurationSettings.AppSettings.Get("DebugMode");
			bool ToDebug = (ToDebugSetting!="");

			ErrorInfo="";
			ServiceInfo_XML ="";
			System.ServiceProcess.ServiceController Service;
			if (RemoteServerAddress!="")
			{Service = new ServiceController(NTServiceName, RemoteServerAddress ) ;}
			else
			{Service = new ServiceController(NTServiceName ) ;}
									
			DataSet objDataSet = new DataSet("QueryResults");
			objDataSet.Tables.Add("ServiceInfo");
			objDataSet.Tables[0].Columns.Add("service_display_name", System.Type.GetType("System.String"));
			objDataSet.Tables[0].Columns.Add("status", System.Type.GetType("System.String"));
			objDataSet.Tables[0].Columns.Add("service_name", System.Type.GetType("System.String"));
			objDataSet.Tables[0].Columns.Add("path_to_executable", System.Type.GetType("System.String"));
			objDataSet.Tables[0].Columns.Add("can_stop", System.Type.GetType("System.Boolean"));
			objDataSet.Tables[0].Columns.Add("can_pause_and_continue",  System.Type.GetType("System.Boolean"));
			objDataSet.Tables[0].Columns.Add("services_depend_on", System.Type.GetType("System.String"));
			objDataSet.Tables[0].Columns.Add("dependent_services", System.Type.GetType("System.String"));
				
			NTServiceInfo si  = new NTServiceInfo(NTServiceName, RemoteServerAddress);

			Object[] r	= new Object[8] {Service.DisplayName, 
							Service.Status.ToString(), 
							Service.ServiceName,
							si.PathToExecutable((WindowsPrincipal) this.User), 
							Service.CanStop.ToString(), 
							Service.CanPauseAndContinue.ToString(), 
							si.ServiceDependOnStringList(Service.ServicesDependedOn),
							si.DependentServicesStringList(Service.DependentServices)
							};
				
			objDataSet.Tables[0].Rows.Add(r);
			
			Service.Close();

			System.IO.StringWriter objStringWriter =new System.IO.StringWriter();
			objDataSet.WriteXml(objStringWriter, XmlWriteMode.WriteSchema);

			ServiceInfo_XML = "" + objStringWriter.ToString();

			ErrorInfo = "";
			return true;
		}
		catch (Exception ex_get_service_info)
		{
			ServiceInfo_XML ="";
			ErrorInfo = ex_get_service_info.Message;
			return false;
		}
	}
In this method, GetNTServiceInfo(), I'm using Dataset class to create XML string using WriteXML() method:
		...
	DataSet objDataSet = new DataSet("QueryResults");
		...	
		...
	System.IO.StringWriter objStringWriter = new System.IO.StringWriter();
	objDataSet.WriteXml(objStringWriter, XmlWriteMode.WriteSchema);
		...
where WriteXml() Writes the current data, and the schema, for the DataSet using the specified System.IO.Stream and XmlWriteMode. XmlWriteMode.WriteSchema tells application to Write the current contents of the DataSet as XML data with the relational structure as inline XSD schema.
where ServiceDependOnStringList() and DependentServicesStringList():
                public string ServiceDependOnStringList(System.ServiceProcess.ServiceController[] Services)
		{
			string ServicesList="";
			foreach (System.ServiceProcess.ServiceController service in Services)
			{
				if (ServicesList!=""){ ServicesList +=",";}
				ServicesList += service.DisplayName;
			}

			return ServicesList;
		}

		public string DependentServicesStringList(System.ServiceProcess.ServiceController[] Services)
		{
			string ServicesList="";
			foreach (System.ServiceProcess.ServiceController service in Services)
			{
				if (ServicesList!=""){ ServicesList +=",";}
				ServicesList += service.DisplayName;
			}

			return ServicesList;
		}

To add/create columns in Dataset:
	...
	objDataSet.Tables[0].Columns.Add("service_display_name", System.Type.GetType("System.String"));
	...
Columns.Add() - Creates and adds a DataColumn object to the DataColumnCollection. Here, Add() creates a DataColumn object with the specified name and type to the DataColumnCollection. To let Add() know type of column, we need to pass a parameter of type DataColumn.DataType. To do that, we can use GetType() method which gets Type with the specified name - System.Type.GetType("System.String")

Points of Interest

If you would like to read more on this story - please take a look at Siccolo - Free Mobile Management Tool For SQL Server and full article at How to Develop Mobile Management Tool

History

No improvements so far. Nearly perfect.


Article keywords: NT Service, Windows Service, Integrated Windows authentication, IIS authentication, System.ServiceProcess.ServiceController, System.Security.Principal.WindowsImpersonationContext, System.Security.Principal.WindowsIdentity, User.Identity.Impersonate(), impersonation context, RegistryKey, Registry.LocalMachine, Microsoft.Win32.RegistryKey.OpenRemoteBaseKey(), RegistryHive.LocalMachine, Microsoft.Win32.OpenSubKey(), KeyHKLM.OpenSubKey(), Key.GetValue(), %SystemRoot%, Software\\Microsoft\\Windows NT\\CurrentVersion\\, SYSTEM\\CurrentControlSet\\Services\\, new DataSet(), DataSet, ServiceName, PathToExecutable, CanStop, CanPauseAndContinue, ServicesDependedOn, DependentServices, System.IO.StringWriter, XML, GetType(), WriteXML(), System.IO.StringWriter, XmlWriteMode


Back To Articles Page

Free Mobile Management For SQL Server(s!) - Siccolo - SQL Management ToolQuestions? Suggestions? Concerns? - email me to [email protected]    Greg Dubinovsky © 2006
or share your thoughts at Siccolo Blog

web sponsor - siccolo.com. well being sponsor - Enabling clinical and operational value across the continuum of care.