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 here allows to restart (or just stop, or start) NT Service.
Background (optional, but needed)
My "managing" web service is hosted under SSL with "Integrated Windows" authentication being set. Therefore, mobile application is required to pass network credentials. And this is needed to be able to remotely access SQL Server machine to control MSSQLSERVER
service.
Using the Code
- serviceprocessor.asmx.cs - Web service interface
- NTServiceInfo.cs - A small "wrapper" to control NT Service
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
:
...
System.ServiceProcess.ServiceController <CODE>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 a 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."
...
System.Security.Principal.WindowsImpersonationContext <CODE>impersonationContext;
impersonationContext =
((System.Security.Principal.WindowsIdentity)User.Identity).Impersonate();
...
To enumerate dependent services - System.ServiceProcess.ServiceController
has DependentServices
property:
...
foreach (System.ServiceProcess.ServiceController <CODE>dependent_service
in Service.DependentServices)
{
...
}
...
To handle recursive stopping and starting, I added small "wrapper" NTServiceInfo
class:
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:
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 ) ;}
foreach (System.ServiceProcess.ServiceController dependent_service
in Service.DependentServices)
{
switch ( dependent_service.Status)
{
case ServiceControllerStatus.Stopped:
break;
case ServiceControllerStatus.StopPending:
dependent_service.WaitForStatus(ServiceControllerStatus.Stopped);
break;
default:
Service.Stop();
Service.WaitForStatus(ServiceControllerStatus.Stopped);
break;
}
}
switch ( Service.Status)
{
case ServiceControllerStatus.Stopped:
break;
case ServiceControllerStatus.StopPending:
Service.WaitForStatus(ServiceControllerStatus.Stopped);
break;
default:
foreach (System.ServiceProcess.ServiceController dependent_service
in Service.DependentServices)
{
switch ( dependent_service.Status)
{
case ServiceControllerStatus.Stopped:
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:
Service.WaitForStatus(ServiceControllerStatus.Stopped);
Service.Start();
Service.WaitForStatus(ServiceControllerStatus.Running);
break;
case ServiceControllerStatus.StartPending:
Service.WaitForStatus(ServiceControllerStatus.Running);
break;
case ServiceControllerStatus.Running:
break;
default:
Service.Start();
Service.WaitForStatus(ServiceControllerStatus.Running);
break;
}
foreach (System.ServiceProcess.ServiceController dependent_service
in Service.DependentServices)
{
switch (dependent_service.Status )
{
case ServiceControllerStatus.StartPending:
dependent_service.WaitForStatus (ServiceControllerStatus.Running);
break;
case ServiceControllerStatus.Running:
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 store - please take a look at Siccolo - Free Mobile Management Tool For SQL Server and the full article at How to Develop Mobile Management Tool.
History
No improvements so far. Nearly perfect.