Build a Windows process tree in C#

[process tree]

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 Example   Follow me on Twitter   RSS feed   Donate




About RodStephens

Rod Stephens is a software consultant and author who has written more than 30 books and 250 magazine articles covering C#, Visual Basic, Visual Basic for Applications, Delphi, and Java.
This entry was posted in algorithms, system and tagged , , , , , , , , , . Bookmark the permalink.

5 Responses to Build a Windows process tree in C#

  1. John Doe says:

    Working great! Please do more system programming tutorials like this one in the future. Anti-malware techniques as well.

    Thanks.

    • RodStephens says:

      Unfortunately I don’t do this sort of thing very often so I don’t have many examples. If you request a specific example (either email me or in the comments here), I can look into it and see if I can figure out how to do it. But I may not the best person for some of this stuff.

      • John Doe says:

        I understand.

        I’d like to see ways to monitor what the system is doing in the background. Alerts for unexpected/unauthorized changes (registry, group policy, winsock, DNS, suspicious Event Viewer entries, TCP/UDP connections etc.) in real-time. Windows Hardening.

        More specific request; prevent .exe-files and services from running without permission from the Administrator. Popup-alert, whitelisting, details on what the process tried to do etc.

        Another example: on this tutorial (“Build a Windows process tree in C#”) is there a way to find out what “Thread” is actually doing?

        It’s a broad subject so I’d appreciate any tutorial you could do on this.

        Thank you for the reply.

        • RodStephens says:

          Sorry but I think that’s way outside of my skill set. I haven’t seen any programming tools for monitoring most of that. You can monitor resource usage like how much memory a process is using, but the Task Manager can already do that. You could probably poll the system periodically to see when a new process started and warn the user.

          This really needs someone with more experience at working with the heart of the system.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.