Dealing with an overabundance of chromedriver.exe
Tags: csharp
Published: 2019-11-01 08:42:11

The code below is in the context of having memory fill up with lots of chromedriver.exe. This was due to programmer (yeah, me) error.

However, fixing the error would necessitate killing off some stuff we wanted to leave running until it was finished. So I opted for the band-aid approach until everything was done and I had time to fix stuff. 

So the idea is this: Get all the chromedriver.exe processes. With each of the processes, see if any have descendants. Those without descendants, that is, chromedriver.exes that haven't started any chrome.exes, can be terminated.

Whether this has applicability to other tasks I don't know. I will eventually get the code up onto Github. Until then, it here. (And now [there](https://github.com/axtens/ChromeDriversWithNoChromes) too.)
```csharp
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Management;

namespace ChromeDriversWithNoChromes
{
    class Program
    {
        static readonly Dictionary<string, object> Settings = new Dictionary<string, object>();
        static readonly List<string> Args = new List<string>();

        static readonly List<Process> FoundProcesses = new List<Process>();
```
There are three data structures there. One is to hold the processes we find. The others are for command-line argument handling. Slashed items go into `Settings` and everything else into `Args`.
```csharp 
        static void Main(string[] args)
        {
            ParseCommandLineToSettingsAndArgs(args);
            var count = StoreProcesses(new string[] { "chromedriver" });
            if (count > 0)
            {
                Console.WriteLine($"{count} chromedrivers found.");

                foreach (var proc in FoundProcesses)
                {
                    var descendants = GetChildren(proc);
                    string tag = string.Empty;

                    if (descendants.Count() == 0)
                    {
                        if (Settings.ContainsKey("/K"))
                        {
                            try
                            {
                                proc.Kill();
                                tag = "TERMINATED.";
                            }
                            catch
                            {
                                tag = "COULD NOT TERMINATE";
                            }
                        }
                        else
                        {
                            tag = "COULD BE TERMINATED";
                        }
                    }
                    else
                    {
                        tag = "IGNORED";
                    }

                    Console.WriteLine($"{tag} {proc.ProcessName}[{proc.Id}]({GetCommandLine(proc)}), {descendants.Count()} descendant/s");

                }
            }
        }
```
There's the Main which is fairly self explanatory. The try/catch is because a process could cease to exist between running the program and processing actually getting as far as the Kill.
```csharp
        static void ParseCommandLineToSettingsAndArgs(string[] args)
        {
            foreach (string arg in args)
            {
                if (arg.StartsWith("/"))
                {
                    var colonPos = arg.IndexOf(":");
                    if (colonPos > -1)
                    {
                        Settings[arg.Substring(0, colonPos)] = arg.Substring(colonPos + 1);
                    }
                    else
                    {
                        Settings[arg] = true;
                    }
                }
                else
                {
                    Args.Add(arg);
                }
            }
        }
```
That's my standard command-line parser. 
```csharp
        static int StoreProcesses(string[] processNames)
        {
            int count = 0;
            foreach (string name in processNames)
            {
                var processes = Process.GetProcessesByName(name);
                if (processes.Count() > 0)
                {
                    FoundProcesses.AddRange(processes);
                    count += processes.Count();
                }
            }
            return count;
        }
```
That gets all the processes that match the names in the processNames list. Maybe having the list of processes as a global is a bad idea. Someone may fork the project (when it gets githubbed) and demonstrate a better way.
```csharp
        static Process[] GetChildren(Process process)
        {
            try
            {
                using (var query = new ManagementObjectSearcher(
                    $"SELECT * FROM Win32_Process WHERE ParentProcessId={process.Id}"))
                {
                    return query
                        .Get()
                        .OfType<ManagementObject>()
                        .Select(p => Process.GetProcessById((int)(uint)p["ProcessId"]))
                        .ToArray();
                }
            }
            catch (ArgumentException ae)
            {
                Console.WriteLine(ae.Message);
                return null;
            }
        }
```
Apparently there's a P/Invoke way of doing that that is faster than the System.Management approach. Essentially one gets all the processes that have a ParentProcessId that's the same as the Id of the process parameter.
```csharp
        static string GetCommandLine(Process proc)
        {
            string commandLine = string.Empty;
            try
            {
                ManagementObjectSearcher commandLineSearcher = new ManagementObjectSearcher(
                    "SELECT CommandLine FROM Win32_Process WHERE ProcessId = " + proc.Id);

                foreach (ManagementObject commandLineObject in commandLineSearcher.Get())
                {
                    commandLine += (String)commandLineObject["CommandLine"];
                }
            }
            catch
            {
                commandLine = $"Command-line data not available";
            }

            return commandLine;
        }
    }
}
```
Another candidate for the P/Invoke approach (if one exists) but again, speed of processing wasn't the primary goal: speed of writing the code was an this routine is from another project written some years ago.

I hope that helps someone. It was (almost) fun to write. Might have been more fun if it weren't for the hundred or more chromedriver.exes filling up available RAM and processor.