Click here to Skip to main content
15,887,285 members
Articles / Programming Languages / C#

Monitor and Manage Services on Remote Machines

Rate me:
Please Sign up or sign in to vote.
4.57/5 (17 votes)
8 May 2009CPOL2 min read 129.2K   10.9K   93   25
Display status on services on several remote machines; one click start/restart, view logs;
Click to enlarge image

Introduction

This article describes how you can make very good use of the ServiceController objects in .NET and easily create a simple application that monitors and manages Windows services across several machines. It is also an example for how powerful the DagaGridView control just might be.

Background

I've started developing this application strictly for my own purposes as I wanted to monitor several critical services on different servers at once and get alerted when either of these fails. Then I extended it a little bit and I think it can be a very useful tool.

Using the Code

The method ListServices has machineName and serviceName as parameters. It lists all the services on a specified machine with the given name pattern and then displays them in a dataGridView control. Each row is being manually created and shows the machine name, the service name, and the last two columns are buttons used to start or stop the service. 

C#
#region [ ListServices ]
private void ListServices(string machineName, string serviceName)
{
    try
    {
        dataGridView1.Rows.Clear();
        services = ServiceController.GetServices(machineName);
       
         foreach (ServiceController sc in services)
        {
            if (sc.ServiceName.ToLower().Contains(serviceName.ToLower()))
            {
                DataGridViewRow row = new DataGridViewRow();
                 #region [ declare cells ]
                DataGridViewCell cell0 = new DataGridViewTextBoxCell();
                DataGridViewCell cell1 = new DataGridViewTextBoxCell();
                DataGridViewCell cell2 = new DataGridViewImageCell(false);
                DataGridViewCell cell3 = new DataGridViewButtonCell();
                DataGridViewCell cell4 = new DataGridViewButtonCell();
           
                #endregion
                 #region [ cell styles ]
                DataGridViewCellStyle styleServiceName = new DataGridViewCellStyle();
                styleServiceName.Font = 
			new Font(new FontFamily("Verdana"), 10.0F, FontStyle.Bold);
                styleServiceName.ForeColor = Color.Black;
                 DataGridViewCellStyle styleStarted = new DataGridViewCellStyle();
                styleStarted.Font = 
			new Font(new FontFamily("Verdana"), 10.0F, FontStyle.Bold);
                styleStarted.ForeColor = Color.Green;
                 DataGridViewCellStyle styleStopped = new DataGridViewCellStyle();
                styleStopped.Font = 
			new Font(new FontFamily("Verdana"), 10.0F, FontStyle.Bold);
                styleStopped.ForeColor = Color.Red;
                 DataGridViewCellStyle stylePending = new DataGridViewCellStyle();
                stylePending.Font = 
			new Font(new FontFamily("Verdana"), 10.0F, FontStyle.Bold);
                stylePending.ForeColor = Color.Yellow;
                 DataGridViewCellStyle styleStartButton = new DataGridViewCellStyle();
                styleStartButton.Font = 
			new Font(new FontFamily("Verdana"),9.0F, FontStyle.Bold);
                styleStartButton.ForeColor = Color.White;
                styleStartButton.BackColor = Color.Green;
                 DataGridViewCellStyle styleStopButton = new DataGridViewCellStyle();
                styleStopButton.Font = 
			new Font(new FontFamily("Verdana"), 9.0F, FontStyle.Bold);
                styleStopButton.ForeColor = Color.White;
                styleStopButton.BackColor = Color.DarkRed;
                
                #endregion
                 #region [ set cell values ]
                
                 cell0.Value = sc.ServiceName;
                cell0.Style = styleServiceName;
                 cell3.Value = "start";
                cell3.Style = styleStartButton;
                 cell4.Value = "stop";
                cell4.Style = styleStopButton;
               
                if (sc.Status == ServiceControllerStatus.Running)
                {
                    cell1.Value = sc.Status.ToString();
                    cell1.Style = styleStarted;
                     cell2.Value = started;
                }
                else if (sc.Status == ServiceControllerStatus.Stopped)
                {
                    cell1.Value = sc.Status.ToString();
                    cell1.Style = styleStopped;
                     cell2.Value = stopped;
                }
                else if (sc.Status == ServiceControllerStatus.StartPending)
                {
                    cell1.Value = sc.Status.ToString();
                    cell1.Style = stylePending;
                     cell2.Value = pending;
                }
                else if (sc.Status == ServiceControllerStatus.StopPending)
                {
                    cell1.Value = sc.Status.ToString();
                    cell1.Style = stylePending;
                     cell2.Value = pending;
                }
                 #endregion
                 #region [ add cells ]
                 row.Cells.Add(cell0);
                row.Cells.Add(cell1);
                row.Cells.Add(cell2);
                row.Cells.Add(cell3);
                row.Cells.Add(cell4);                       
                 #endregion
                 row.Height = 48;
                 dataGridView1.Rows.Add(row);                       
                 this.Text = "Smc Services Monitor Central 
				[" + machineName.ToUpper() + "]";
            }
            else if (serviceName.Trim().Length == 0)
            {
                DataGridViewRow row = new DataGridViewRow();
                 #region [ declare cells ]
                DataGridViewCell cell0 = new DataGridViewTextBoxCell();
                DataGridViewCell cell1 = new DataGridViewTextBoxCell();
                DataGridViewCell cell2 = new DataGridViewImageCell(false);
                DataGridViewCell cell3 = new DataGridViewButtonCell();
                DataGridViewCell cell4 = new DataGridViewButtonCell();
                 #endregion
                 #region [ cell styles ]
                DataGridViewCellStyle styleServiceName = new DataGridViewCellStyle();
                styleServiceName.Font = 
			new Font(new FontFamily("Verdana"), 10.0F, FontStyle.Bold);
                styleServiceName.ForeColor = Color.Black;
                DataGridViewCellStyle styleStarted = new DataGridViewCellStyle();
                styleStarted.Font = 
			new Font(new FontFamily("Verdana"), 10.0F, FontStyle.Bold);
                styleStarted.ForeColor = Color.Green;
                 DataGridViewCellStyle styleStopped = new DataGridViewCellStyle();
                styleStopped.Font = 
			new Font(new FontFamily("Verdana"), 10.0F, FontStyle.Bold);
                styleStopped.ForeColor = Color.Red;
                 DataGridViewCellStyle stylePending = new DataGridViewCellStyle();
                stylePending.Font = 
			new Font(new FontFamily("Verdana"), 10.0F, FontStyle.Bold);
                stylePending.ForeColor = Color.Yellow;
                 DataGridViewCellStyle styleStartButton = new DataGridViewCellStyle();
                styleStartButton.Font = 
			new Font(new FontFamily("Verdana"), 9.0F, FontStyle.Bold);
                styleStartButton.ForeColor = Color.White;
                styleStartButton.BackColor = Color.Green;
                 DataGridViewCellStyle styleStopButton = new DataGridViewCellStyle();
                styleStopButton.Font = 
			new Font(new FontFamily("Verdana"), 9.0F, FontStyle.Bold);
                styleStopButton.ForeColor = Color.White;
                styleStopButton.BackColor = Color.DarkRed;
                 #endregion
                 #region [ set cell values ]
                 cell0.Value = sc.ServiceName;
                cell0.Style = styleServiceName;
                 cell3.Value = "start";
                cell3.Style = styleStartButton;
                 cell4.Value = "stop";
                cell4.Style = styleStopButton;
                 if (sc.Status == ServiceControllerStatus.Running)
                {
                    cell1.Value = sc.Status.ToString();
                    cell1.Style = styleStarted;
                     cell2.Value = started;
                }
                else if (sc.Status == ServiceControllerStatus.Stopped)
                {
                    cell1.Value = sc.Status.ToString();
                    cell1.Style = styleStopped;
                     cell2.Value = stopped;
                }
                else if (sc.Status == ServiceControllerStatus.StartPending)
                {
                    cell1.Value = sc.Status.ToString();
                    cell1.Style = stylePending;
                     cell2.Value = pending;
                }
                else if (sc.Status == ServiceControllerStatus.StopPending)
                {
                    cell1.Value = sc.Status.ToString();
                    cell1.Style = stylePending;
                     cell2.Value = pending;
                }
                 #endregion
                 #region [ add cells ]
                 row.Cells.Add(cell0);
                row.Cells.Add(cell1);
                row.Cells.Add(cell2);
                row.Cells.Add(cell3);
                row.Cells.Add(cell4);
                 #endregion
                 row.Height = 48;
                 dataGridView1.Rows.Add(row);
            }
        }                
    }
    catch (Exception ex)
    {
        toolStripStatusLabel1.Text = ex.Message;
        Log("ListServices(): ", ex);
    }
}
#endregion 

In the application, we basically have three dataGridView controls. The one filled with the ListServices method displays the filtered services on a machine. When a user double-clicks a row, the chosen service is being added to the second gridView, which holds the monitored services. It can contain services from various remote machines. If a service stops, it is then shown in the third gridView. You also get asked if you want to receive an email if a service stops execution and you can set an ‘auto’ option which if on automatically restarts the service.

As mentioned above, we store the monitored services in a KeyValuePair<ServiceController, bool> and then the FillGrid2() method displays them in the second gridView. This is done using the CellDoubleClick event of the dataGridView1.

C#
#region [ dataGridView1_CellDoubleClick ]
private void dataGridView1_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
{
    try
    {
        // we say we can monitor up to 10 services 
        // (for interface issues; else window gets too big)
        if (index < 10)
        {
             // we check if the service already exists in our list
            foreach (KeyValuePair<ServiceController, bool> kv in monitoredServs)
            {
                if (kv.Key != null)
                {
                    if (kv.Key.ServiceName == dataGridView1.Rows
			[e.RowIndex].Cells[0].Value.ToString() && 
			kv.Key.MachineName == textBoxMachineName.Text)
                    {
                        toolStripStatusLabel1.Text = "Service already in list!";
                        return;
                    }
                }
            }
            // we create a new ServiceController with the 
	   // passed information from the clicked row
            ServiceController s = new ServiceController(dataGridView1.Rows
        		[e.RowIndex].Cells[0].Value.ToString(), textBoxMachineName.Text);
            // we create a new KeyValuePair<ServiceController, bool> 
	   // with the created ServiceController and a Boolean variable controlling 
	   // whether we receive an email when the service stops
            KeyValuePair<ServiceController, bool> kv0 = 
			new KeyValuePair<ServiceController, bool>();
             // we ask if we want to receive an email when the service stops
            DialogResult result = MessageBox.Show("Do you want to receive an E-mail 
			notifying you if the monitored service has 
			stopped execution?", "Notification confirmation", 
			MessageBoxButtons.YesNo, MessageBoxIcon.Question);
            if (result == DialogResult.Yes)
            {
                kv0 = new KeyValuePair<ServiceController, bool>(s, true);
            }
            else
            {
                kv0 = new KeyValuePair<ServiceController, bool>(s, false);
            }
              // we add this service to the monitored services list of 
	     // type KeyValuePair<ServiceController, bool>
            monitoredServs.Add(kv0);
             // we fill the second grid
            FillGrid2();
            index++;
        }
        else
        {
            toolStripStatusLabel1.Text = "You can monitor up to 10 services!";
        }
    }
    catch (Exception ex)
    {
        Log("dataGridView1_CellDoubleClick", ex);
    }
}
#endregion

I use a timer to check if one of the monitored services has changed its status. Please refer to the comments in the following method:

C#
#region [ timer2_Tick ]

void timer2_Tick(object sender, EventArgs e)
{
    try
    {                                
        foreach (KeyValuePair<ServiceController, bool> kv in monitoredServs)
        {
            exists = false;
            if (kv.Key != null)
            {
                kv.Key.Refresh();
                 // we check if the service has already been marked as stopped, 
	        // then we can't and don't want to add it again
                foreach (DataGridViewRow row in dataGridView3.Rows)
                {
                    if (row.Cells[0].Value.ToString() == kv.Key.ServiceName && 
			row.Cells[1].Value.ToString() == kv.Key.MachineName)
                    {
                        exists = true;
                    }
                }
                // if the service is stopped and hasn't been added still, 
	       // we added to the GridView3
                if (!exists)
                {                            
                    if (kv.Key.Status == ServiceControllerStatus.Stopped)
                    {
                        FillGrid3(kv);
                    }                          
                }
            }
        }
        // we check if the service has restarted and if its status 
        // is different from stopped, then we take it out 
        // of the list with stopped services and remove it as a row 
        // from the GridView3 control displaying them
        foreach (DataGridViewRow row in dataGridView3.Rows)
        {
            ServiceController sc = new ServiceController
		(row.Cells[0].Value.ToString(),row.Cells[1].Value.ToString());
            sc.Refresh();
            if (sc.Status != ServiceControllerStatus.Stopped)
            {
                dataGridView3.Rows.RemoveAt(row.Index);
            }
        }
        
        new System.Threading.Thread(FillGrid2).Start();               
    }
    catch (Exception ex)
    {
        Log("timer2_Tick", ex);
    }
}
#endregion 

Another Timer refreshes the list of services displayed in the main DataGridView. It operates on a different Thread.

C#
#region [ timer1_Tick ]
void timer1_Tick(object sender, EventArgs e)
{
    try
    {
        if (textBoxMachineName.Text.Trim().Length == 0)
        {
            textBoxMachineName.Text = System.Environment.MachineName;
        }                
        new System.Threading.Thread(ListServicesAsync).Start();
                                        
    }
    catch (Exception ex)
    {
        Log("timer1_Tick", ex);
    }
}
#endregion 

I find the following very useful. When you click on a row in the gridView displaying the services being monitored, a .BAT file with parameters is being executed, in this case displaying the event viewer of the machine the service is running on:

C#
#region [ dataGridView3_CellContentClick ]
private void dataGridView3_CellContentClick_1(object sender, DataGridViewCellEventArgs e)
{
    try
    {
        if (e.ColumnIndex == 3)// start service button
        {
            ServiceController sc = new ServiceController
		(dataGridView3.Rows[e.RowIndex].Cells[0].Value.ToString(), 
		dataGridView3.Rows[e.RowIndex].Cells[1].Value.ToString());
            sc.Start();
            dataGridView3.Rows.RemoveAt(e.RowIndex);
        }
        else
        {
            Process p = new Process();
             //string executable = Environment.ExpandEnvironmentVariables
				(@"%SystemRoot%\system32\eventvwr.msc");
              p.StartInfo.FileName = "EVENTVIEWER.BAT";
            p.StartInfo.Arguments = Convert.ToString
			(dataGridView3.Rows[e.RowIndex].Cells[1].Value);
              p.Start(); 
        }
    }
    catch (Exception ex)
    {
        Log("dataGridView3_CellContentClick_1", ex);
        toolStripStatusLabel1.Text = ex.Message;
    }
}
#endregion 

You can start the application with administrator privileges (as long you have those). Therefore I use some classes I've created, which I won't describe in this article. The passwords are then stored securely in the configuration file and you don't need to enter administrator username and password each time.

Another useful feature is that all the monitored services, while the application was running get stored in the application config file in the FormClosing EventHandler

C#
#region [ Smc_FormClosing ]
private void Smc_FormClosing(object sender, FormClosingEventArgs e)
{
    try
    {
        for (int i = 0; i < 10; i++)
        {
            conf.SaveToAppConfig("service" + i.ToString() + "Name", 
		(i >= monitoredServs.Count ? "" : 
			monitoredServs[i].Key.ServiceName));
            conf.SaveToAppConfig("service" + i.ToString() + "Machine", 
		(i >= monitoredServs.Count ? "" : 
			monitoredServs[i].Key.MachineName.ToUpper()));
            conf.SaveToAppConfig("service" + i.ToString() + "mail", 
		(i >= monitoredServs.Count ? "" : 
			monitoredServs[i].Value.ToString()));
        }
    }
    catch (Exception ex)
    {
        Log("Smc_FormClosing", ex);
    }
}
#endregion 

This is also done with classes I've created separately and won't be described here. I just wanted to point out the use of generics, dataGridView and the ServiceController object. 

History

  • 8th May, 2009: Initial version 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Junior)
Bulgaria Bulgaria
I am a 26 year old programmer with 5 years of experience in .NET programming. I am interested in developing ASP.NET AJAX enabled applications utilizing SSRS, MSSQL Server etc.

Comments and Discussions

 
QuestionNOT WORKING AT ALL >>> Pin
Member 139947653-Oct-18 5:58
Member 139947653-Oct-18 5:58 
QuestionVersion VS Pin
Alejandroabeijon25-Apr-18 7:40
Alejandroabeijon25-Apr-18 7:40 
QuestionDLL missing Pin
Member 126771875-Sep-16 22:19
Member 126771875-Sep-16 22:19 
QuestionAwesome job Pin
Member 1178343722-Jan-16 7:25
Member 1178343722-Jan-16 7:25 
QuestionDoes not work on a different network machine Pin
pulak_mj22-Jun-15 1:24
pulak_mj22-Jun-15 1:24 
AnswerRe: Does not work on a different network machine Pin
stixoffire7-Jul-15 9:14
stixoffire7-Jul-15 9:14 
QuestionSolution is empty Pin
Member 108954579-Jul-14 10:58
Member 108954579-Jul-14 10:58 
Questioncannot open service control manager on computer "". this operation might require other privileges. Pin
dzidzai16-Feb-12 5:21
dzidzai16-Feb-12 5:21 
AnswerRe: cannot open service control manager on computer "". this operation might require other privileges. Pin
kunaladesai2-Feb-13 0:09
kunaladesai2-Feb-13 0:09 
Questionhelp Pin
keith89898910-Oct-11 9:53
keith89898910-Oct-11 9:53 
QuestionWebExtreme dlls PinPopular
Matthew Verbrugge31-Jan-11 11:08
Matthew Verbrugge31-Jan-11 11:08 
GeneralSource code is missing.. Pin
Holysoul19-May-09 9:20
Holysoul19-May-09 9:20 
GeneralRe: Source code is missing.. Pin
rusevd19-May-09 20:20
rusevd19-May-09 20:20 
GeneralRe: Source code is missing.. [modified] Pin
Holysoul20-May-09 3:37
Holysoul20-May-09 3:37 
GeneralRe: Source code is missing.. Pin
rusevd20-May-09 3:42
rusevd20-May-09 3:42 
GeneralRe: Source code is missing.. Pin
Holysoul20-May-09 3:58
Holysoul20-May-09 3:58 
GeneralRe: Source code is missing.. Pin
Holysoul20-May-09 5:00
Holysoul20-May-09 5:00 
GeneralNice utility but could improve with small usability extras Pin
msorens12-May-09 4:54
msorens12-May-09 4:54 
GeneralRe: Nice utility but could improve with small usability extras Pin
rusevd12-May-09 20:14
rusevd12-May-09 20:14 
GeneralRe: Nice utility but could improve with small usability extras Pin
dzidzai16-Feb-12 5:27
dzidzai16-Feb-12 5:27 
QuestionReferences? Pin
Andy Crawford12-May-09 2:05
professionalAndy Crawford12-May-09 2:05 
AnswerRe: References? Pin
rusevd12-May-09 2:21
rusevd12-May-09 2:21 
QuestionWhere are the Referenced Assemblies Pin
AllenR12-May-09 2:05
professionalAllenR12-May-09 2:05 
In the project there are reference to WebExtreme assemblies. Where can these be found?? Frown | :(

Seem like an interesting project otherwise.

Ciao,
AllenR
AnswerRe: Where are the Referenced Assemblies Pin
rusevd12-May-09 2:21
rusevd12-May-09 2:21 
GeneralRe: Where are the Referenced Assemblies Pin
AllenR12-May-09 4:49
professionalAllenR12-May-09 4:49 

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.