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
{
if (index < 10)
{
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;
}
}
}
ServiceController s = new ServiceController(dataGridView1.Rows
[e.RowIndex].Cells[0].Value.ToString(), textBoxMachineName.Text);
KeyValuePair<ServiceController, bool> kv0 =
new KeyValuePair<ServiceController, bool>();
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);
}
monitoredServs.Add(kv0);
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();
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 (!exists)
{
if (kv.Key.Status == ServiceControllerStatus.Stopped)
{
FillGrid3(kv);
}
}
}
}
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)
{
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();
(@"%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