Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Monitor and Manage Services on Remote Machines

0.00/5 (No votes)
8 May 2009 1  
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. 

#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.

#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:

#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.

#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:

#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

#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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here