Title:       Web Method To Restart NT Service
Author:      Greg Dubinovskiy 
Email:       [email protected]
Environment: C#, VB.NET
Keywords:    Web Service, Restart NT Service, Manage NT Service, Impersonate Web User
Level:       Intermediate
Description: Create a web service method to manage a NT service
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)
Among other problems, I had to be able to restart SQL Server service on machine(s) other than web service host from within web method. And I wanted not just to stop and start MSSQLSERVER service, but also restart all the services that are dependent on MSSQLSERVER service, e.g. SQL Server Agent, Crystal Reports Engine, CRM Security Service etc.

The code presented allows to restart (or just stop, or start) NT Service - Windows Services in .NET: Controlling Your Service from Another Application.

If you’ve ever used SQL Server, you know it comes with a Service Manager control program that allows you to start and stop the SQL Server service and SQL Server Agent service.
Microsoft provides a special tool called SQL Server Service Manager. This tool does almost the same thing - allow you to Start, Pause or Stop the SQL Server services. The SQL Service Control Manager can manage SQL Servers installed on a computer in the network.
Wouldn’t it be cool if you could write a small program to do the same with your service? Well you can, and this article shows how.

In .NET, Starting and stopping a Service is easy, all we have to do is create an instance of a service controller object, and then call it’s Start or Stop method.

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 SQL Server machine to control MSSQLSERVER service.
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

Let's see how to restart SQL Server service. .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 enumerate dependent services - System.ServiceProcess.ServiceController has DependentServices property:
	// C# //
	...
	foreach (System.ServiceProcess.ServiceController dependent_service 
						in Service.DependentServices)
	{	
		...
	}
	...	
To handle reqursive stopping and starting, I added small "wrapper" NTServiceInfo class:
	// NTServiceInfo :
	public bool RestartServervice(WindowsPrincipal User, 
				bool ToDebug, 
				out string ErrorInfo)
	{
		try
		{
			ErrorInfo ="";
		
			if (this.StopService(User, ToDebug, out ErrorInfo))
			{
				if (this.StartService(User, ToDebug, out ErrorInfo))
				{return true;}
				else{return false;}
			}
			else
			{return false;}

		}
		catch (Exception ex_restart_service)
		{
			ErrorInfo = "Restart Service [" + this.m_ServiceName + "] [" + ex_restart_service.Message + "]";
			return false;
		}
	}
Where StopService() and StartService() methods are:
// NTServiceInfo :
public bool StopService(WindowsPrincipal User, 
				bool ToDebug, 
				out string ErrorInfo)
	{
		try
		{
			System.Security.Principal.WindowsImpersonationContext impersonationContext;
				impersonationContext = 
					((System.Security.Principal.WindowsIdentity)User.Identity).Impersonate();

			System.ServiceProcess.ServiceController Service;
			if (this.m_MachineName!="")
			{Service = new ServiceController(this.m_ServiceName, this.m_MachineName ) ;}
			else
			{Service = new ServiceController(this.m_ServiceName ) ;}

			//stop all the Dependent services...
			foreach (System.ServiceProcess.ServiceController dependent_service 
							in Service.DependentServices)
			{					
				switch ( dependent_service.Status)
				{
					case ServiceControllerStatus.Stopped:
						//already stopped...nothing to do
						break;

					case ServiceControllerStatus.StopPending:
						dependent_service.WaitForStatus(ServiceControllerStatus.Stopped);
						break;

					default:
						Service.Stop();
						Service.WaitForStatus(ServiceControllerStatus.Stopped);
						break;
				}
			}

			//stop main service...
			switch ( Service.Status)
			{
				case ServiceControllerStatus.Stopped:
					//already stopped...nothing to do
					break;

				case ServiceControllerStatus.StopPending:
					Service.WaitForStatus(ServiceControllerStatus.Stopped);
					break;

				default:
					// *-*************************************************************************
					//check all dependent services?
					foreach (System.ServiceProcess.ServiceController dependent_service 
										in Service.DependentServices)
					{					
						switch ( dependent_service.Status)
						{
							case ServiceControllerStatus.Stopped:
								//already stopped...nothing to do
								break;

							case ServiceControllerStatus.StopPending:
							dependent_service.WaitForStatus(ServiceControllerStatus.Stopped);
								break;

							default:
								Service.Stop();
								Service.WaitForStatus(ServiceControllerStatus.Stopped);
								break;
						}
							

						}

						if ( !Service.CanStop )
						{
							throw new Exception ("Cannot stop service [" + 
							        this.m_ServiceName + "]");
						}
						Service.Stop();
						Service.WaitForStatus(ServiceControllerStatus.Stopped);
						break;
				}

				Service.Close();

				impersonationContext.Undo();

				ErrorInfo="";
				return true;
			}

		catch (Exception ex_stop_service)
		{
			ErrorInfo = ex_stop_service.Message;
			return false;
		}

	}


	public bool StartService(WindowsPrincipal User, 
				bool ToDebug, 
				out string ErrorInfo)
	{
		try
		{
			System.Security.Principal.WindowsImpersonationContext impersonationContext;
			impersonationContext = 
				((System.Security.Principal.WindowsIdentity)User.Identity).Impersonate();

			System.ServiceProcess.ServiceController Service;
			if (this.m_MachineName!="")
			{Service = new ServiceController(this.m_ServiceName, this.m_MachineName ) ;}
			else
			{Service = new ServiceController(this.m_ServiceName ) ;}
			
			switch ( Service.Status)
			{
				case ServiceControllerStatus.Stopped:
					Service.Start();
					Service.WaitForStatus(ServiceControllerStatus.Running);
					break;

				case ServiceControllerStatus.StopPending:
					//wait for it to stop
					Service.WaitForStatus(ServiceControllerStatus.Stopped);
					//... and then start
					Service.Start();
					Service.WaitForStatus(ServiceControllerStatus.Running);
					break;

				case ServiceControllerStatus.StartPending:
					//nothing to do...just wait
					Service.WaitForStatus(ServiceControllerStatus.Running);
					break;

				case ServiceControllerStatus.Running:
					//nothing to do.already running...
					break;
				
				default:
					Service.Start();
					Service.WaitForStatus(ServiceControllerStatus.Running);
					break;
			}

			//start all the Dependent services...
			foreach (System.ServiceProcess.ServiceController dependent_service 
							in Service.DependentServices)
			{
				
				switch (dependent_service.Status )
				{
					case ServiceControllerStatus.StartPending:
						//just wait for it to start
						dependent_service.WaitForStatus (ServiceControllerStatus.Running);
						break;

					case ServiceControllerStatus.Running:
						//already running.nothing to do
						break;

					default:
						NTServiceInfo si = 
						        new NTServiceInfo( dependent_service.ServiceName, this.m_MachineName);
						if ( !si.StartService(User, ToDebug, out ErrorInfo))
						{
							throw new Exception ("Failed to start Dependent Service [" +  
							                dependent_service.ServiceName + 
								            "] - Error [" + ErrorInfo + "]") ;
						}
						break;
				}
			}
			
			Service.Close();

			impersonationContext.Undo();

			ErrorInfo="";
			return true;
		}
		catch (Exception ex_start_service)
		{
			ErrorInfo = ex_start_service.Message;
			return false;
		}

	}
So, after all, web method looks like this:
[WebMethod]
	public bool RestartSQLServerService(string RemoteServerAddress,
					out string NewServiceStatus, 
					out string ErrorInfo )
	{
		try
		{
			string ToDebugSetting = 
					System.Configuration.ConfigurationSettings.AppSettings.Get("DebugMode");
			bool ToDebug = (ToDebugSetting!="");

			string NTServiceName = "MSSQLSERVER";

			ErrorInfo="";
			NewServiceStatus ="";

			System.Security.Principal.WindowsImpersonationContext impersonationContext;
				impersonationContext = 
					((System.Security.Principal.WindowsIdentity)User.Identity).Impersonate();
			
			NTServiceInfo si = new NTServiceInfo(NTServiceName, RemoteServerAddress);

			if ( ! si.RestartServervice ((WindowsPrincipal) this.User,ToDebug,  out ErrorInfo))
			{return false;}

			NewServiceStatus =  si.ServiceStatus();

			impersonationContext.Undo();
				
			ErrorInfo = "";
			return true;
		}
		catch (Exception ex_get_service_info)
		{
			NewServiceStatus ="";
			ErrorInfo = ex_get_service_info.Message;
			return false;
		}
	}
	
Or, to make functionality more generic:
[WebMethod]
		public bool RestartNTService(string RemoteServerAddress , 
									string NTServiceName, 
									out string NewServiceStatus  , 
									out bool CanStop, 
									out bool CanPauseAndContinue,
									out string ErrorInfo )
		{
	
			try
			{
		
				string ToDebugSetting = System.Configuration.ConfigurationSettings.AppSettings.Get("DebugMode");
				bool ToDebug = (ToDebugSetting!="");

				ErrorInfo="";
				NewServiceStatus ="";
				CanStop = false;
				CanPauseAndContinue = false;

				System.Security.Principal.WindowsImpersonationContext impersonationContext;
				impersonationContext = 
					((System.Security.Principal.WindowsIdentity)User.Identity).Impersonate();
			
				NTServiceInfo si = new NTServiceInfo(NTServiceName, RemoteServerAddress);

				if ( ! si.RestartServervice ((WindowsPrincipal) this.User,ToDebug,  out ErrorInfo))
				{return false;}

				NewServiceStatus =  si.ServiceStatus();
				CanStop = si.CanStop();
				CanPauseAndContinue = si.CanPauseAndContinue();

				impersonationContext.Undo();

				ErrorInfo = "";
				return true;
			}
			catch (Exception ex_get_service_info)
			{
				NewServiceStatus ="";
				CanStop = false;
				CanPauseAndContinue = false;
				ErrorInfo = ex_get_service_info.Message;
				return false;
			}

		}

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: System.ServiceProcess.ServiceController, Service.Start(), Service.Stop(), Service.WaitForStatus(), System.Security.Principal.WindowsImpersonationContext, impersonationContext, System.Security.Principal.WindowsIdentity, User.Identity, Impersonate(), impersonationContext.Undo(), Service.DependentServices, ServiceControllerStatus.Stopped, ServiceControllerStatus.StopPending, Service.CanStop, ServiceControllerStatus.StartPending, ServiceControllerStatus.Running, Restart MS SQL Server service


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.
Creating a .Net application to start / restart / stop Windows service. Windows Service monitoring.