Title: Build a Windows process tree in C#
A process tree is a tree that shows the processes running on the computer arranged hierarchically so you can see which processes started other processes. My first attempts at this used techniques that I found scattered around the internet. Unfortunately those techniques generally worked by finding the parent process for individual processes. Repeating those techniques for each of the hundreds of processes on my system took a couple of minutes.
This example uses a different approach that only takes about half a second. It gets information on all of the process at once and then uses the information to build the tree.
ProcessInfo
The program stores information about a process in the following ProcessInfo class.
class ProcessInfo : IComparable<ProcessInfo>
{
public Process TheProcess;
public ProcessInfo Parent;
public List<ProcessInfo> Children = new List<ProcessInfo>();
public ProcessInfo(Process the_process)
{
TheProcess = the_process;
}
public override string ToString()
{
return string.Format("{0} [{1}]",
TheProcess.ProcessName, TheProcess.Id);
}
public int CompareTo(ProcessInfo other)
{
return TheProcess.ProcessName.CompareTo(
other.TheProcess.ProcessName);
}
}
The class stores a reference to the process that it represents in the variable TheProcess. Note that the class does not keep itself up to date so, for example, if the process ends, the ProcessInfo object does not know that.
As you can probably guess, the class stores the process's parent process in variable Parent. The Children list holds references to this process's child processes.
The class's constructor simply saves a reference to the process. The ToString method returns the process's name and ID.
Finally the CompareTo method compares this object's ProcessName to another object's ProcessName. That method implements the IComparable interface, which allows the program to sort ProcessInfo objects alphabetically.
Form1_Load
When the program starts, the following Load event handler does most of the interesting work.
private int NumProcesses, NumThreads;
private void Form1_Load(object sender, EventArgs e)
{
Stopwatch watch = new Stopwatch();
watch.Start();
Dictionary<int, ProcessInfo> process_dict =
new Dictionary<int, ProcessInfo>();
// Get the processes.
foreach (Process process in Process.GetProcesses())
{
process_dict.Add(process.Id, new ProcessInfo(process));
}
// Get the parent/child info.
ManagementObjectSearcher searcher = new ManagementObjectSearcher(
"SELECT ProcessId, ParentProcessId FROM Win32_Process");
ManagementObjectCollection collection = searcher.Get();
// Create the child lists.
foreach (var item in collection)
{
// Find the parent and child in the dictionary.
int child_id = Convert.ToInt32(item["ProcessId"]);
int parent_id = Convert.ToInt32(item["ParentProcessId"]);
ProcessInfo child_info = null;
ProcessInfo parent_info = null;
if (process_dict.ContainsKey(child_id))
child_info = process_dict[child_id];
if (process_dict.ContainsKey(parent_id))
parent_info = process_dict[parent_id];
if (child_info == null)
Console.WriteLine(
"Cannot find child " + child_id.ToString() +
" for parent " + parent_id.ToString());
if (parent_info == null)
Console.WriteLine(
"Cannot find parent " + parent_id.ToString() +
" for child " + child_id.ToString());
if ((child_info != null) && (parent_info != null))
{
parent_info.Children.Add(child_info);
child_info.Parent = parent_info;
}
}
// Convert the dictionary into a list.
List<ProcessInfo> infos = process_dict.Values.ToList();
// Sort the list.
infos.Sort();
// Populate the TreeView.
NumProcesses = 0;
NumThreads = 0;
foreach (ProcessInfo info in infos)
{
// Start with root processes.
if (info.Parent != null) continue;
// Add this process to the TreeView.
AddInfoToTree(trvProcesses.Nodes, info);
}
lblCounts.Text =
"# Processes: " +
NumProcesses.ToString() + ", " +
"# Threads : " +
NumThreads.ToString();
watch.Stop();
Console.WriteLine(string.Format("{0:0.00} seconds",
watch.Elapsed.TotalSeconds));
}
After starting a stopwatch, the code creates a dictionary that uses an integer (process ID) for the keys and ProcessInfo objects for the values.
Next the code loops through the processes returned by the Process.GetProcesses. For each returned process, the program creates a new ProcessInfo object and adds that object to the dictionary.
After this step, the program has a list of the system's processes, but it doesn't have any parent/child information. To get that information, the program uses a ManagementObjectSearcher. (To use that class, add a reference to the System.Management library and add the statement using System.Management at the top of the file.)
The program initializes the searcher with the query SELECT ProcessId, ParentProcessId FROM Win32_Process. For every process that is running, that fetches the process's ID and its parent ID. The program calls the object's Get method to execute the query and return a collection holding the results.
Now the program loops through the results. For each result, the program gets the child and parent process IDs and looks them up in the dictionary to find the corresponding ProcessInfo objects. If it finds both ProcessInfo objects, the code sets the child's Parent field and adds the child to the parent's Children list.
After the program has finished looping through the searcher's results, the objects in the dictionary have links to their parents and children. To make using the objects easier, the program gets the dictionary's value collection, converts it into a list, and sorts the result. (Remember that the ProcessInfo class implements IComparable so the list can sort the objects.)
Next the program uses the list to populate the program's TreeView control. To do that, it loops through the list. If a process has a parent, then it will be placed in the tree when its parent is placed in the tree. If a process does not have a parent, then the code adds it as a top-level node in the tree by calling the AddInfoToTree method described shortly.
After it finishes building the tree, the program displays the number of processes and threads that it found, and the elapsed time.
AddInfoToTree
The following AddInfoToTree method adds a ProcessInfo to the TreeView.
// Add a ProcessInfo, its children, and its threads to the tree.
private void AddInfoToTree(TreeNodeCollection nodes, ProcessInfo info)
{
// Add the process's node.
TreeNode process_node = nodes.Add(info.ToString());
process_node.Tag = info;
NumProcesses++;
// Add the node's threads.
if (info.TheProcess.Threads.Count > 0)
{
TreeNode thread_node = process_node.Nodes.Add("Threads");
foreach (ProcessThread thread in info.TheProcess.Threads)
{
thread_node.Nodes.Add(string.Format(
"Thread {0}", thread.Id));
NumThreads++;
}
}
// Sort the children.
info.Children.Sort();
// Add child processes.
foreach (ProcessInfo child_info in info.Children)
{
AddInfoToTree(process_node.Nodes, child_info);
}
// Expand the main process node.
if (info.Children.Count > 0)
process_node.Expand();
}
The method takes as its first parameter the TreeNodeCollection where the process should be added.
The method adds a new node to the nodes collection and saves the ProcessInfo object in the new node's Tag property. This example does not use that value, but another program might want to be able to find the ProcessInfo object for a tree node. It could then use that object to get the process object and all of the properties that it contains.
The code increments NumProcesses to indicate that it added another process to the tree.
Next, if the process has threads, the method adds them to the tree. It creates a child node labeled Threads and then loops through the threads, adding them to the thread node.
Before the method adds the process's children to the tree, it sorts the children. (Again, this is possible because the ProcessInfo class implements IComparable.) The code then loops through the children and recursively calls the AddInfoToTree method to add the child's information below the current process's node.
The method finishes by expanding the process's node if the node has children. The result is that TreeView nodes that have children are expanded so you can see all of the processes, but Threads nodes are not expanded. (There are so many of them that it make the tree hard to read.)
Conclusion
Download the example to experiment with it. If you like, you can modify the example to manipulate the processes. For example, you could click on tree node and then call the corresponding process's Kill method. (There's some commented out code in the example that does this.)
Download the example to experiment with it and to see additional details.
|