3

Overall Goal

I'm attempting to kill all of the processes by a certain name (notepad.exe below) that I currently own. Generally speaking, it's along the lines of:

  • Get all of the applications with a certain name that I'm the owner of
    • In this case, "I" will usually be a service account
  • Kill all of them.

Questions

  • How likely is it that from the time I grab a PID to the time I kill it, another application could have spawned that uses that PID? If I grab a PID of ID 123, how likely is it that it could have closed and a different application now owns PID 123?
  • What is the best way I can reasonably pull this off while limiting the potential that I kill off the wrong PID?

What I have so Far

The below code is based on another SO answer and uses WMI to get all the processes by a certain name and list the users.

What's next: The next step is to kill the processes that are owned by me; however, how can I tell that the PIDs I have here will be the same PIDs I'm trying to kill?

static void Main(string[] args)
    {
        const string PROCESS_NAME = "notepad.exe";
        var queryString = string.Format("Name = '{0}'", PROCESS_NAME);

        var propertiesToSelect = new[] { "Handle", "ProcessId" };
        var processQuery = new SelectQuery("Win32_Process", queryString, propertiesToSelect);

        using (var searcher = new ManagementObjectSearcher(processQuery))
        {
            using (var processes = searcher.Get())
                foreach (var aProcess in processes)
                {
                    var process = (ManagementObject)aProcess;
                    var outParameters = new object[2];
                    var result = (uint)process.InvokeMethod("GetOwner", outParameters);

                    if (result == 0)
                    {
                        var user = (string)outParameters[0];
                        var domain = (string)outParameters[1];
                        var processId = (uint)process["ProcessId"];

                        Console.WriteLine("PID: {0} | User: {1}\\{2}", processId, domain, user);
                        // TODO: Use process data...
                    }
                    else
                    {
                        // TODO: Handle GetOwner() failure...
                    }
                }

        }

        Console.ReadLine();
    }
SeanKilleen
  • 8,809
  • 17
  • 80
  • 133
  • How long do you intend to have between the retrieval and the killing? – Patrick Hofman Jan 29 '15 at 21:27
  • Also see [this](http://superuser.com/a/636508). So there is a risk of reusing. – Patrick Hofman Jan 29 '15 at 21:30
  • @PatrickHofman a second, maybe more. The idea is to immediately get all the procs by that name that I own and kill them. We have a service that needs to kill off a console app and are experimenting with running duplicate services on the same machine as different users. Don't want them each killing the others' console apps. – SeanKilleen Jan 30 '15 at 02:48

4 Answers4

4

Yes, there is a risk of killing the wrong process. The reuse of PIDs probably is a history accident that has caused a lot of grief over the years.

Do it like this:

  1. Find the PIDs you want to kill.
  2. Obtain handles to those processes to stabilize the PIDs. Note, that this might obtain handles to wrong processes.
  3. Re-find the PIDs you want to kill.
  4. Kill those processes that you have stabilized and that are in the second find result set.

By inserting this lock-and-validate step you can be sure.

usr
  • 168,620
  • 35
  • 240
  • 369
  • Thanks; I was initially thinking something along these lines. This seems like the answer that would eliminate the (admittedly already slim) chance of the problem occurring. – SeanKilleen Jan 30 '15 at 02:56
  • A quick question on your #2 point above -- in C#, if I haven't called the process myself, via `Process.Start()`, how would I obtain the handle to it? My understanding is that only processes called via `.Start()` create the `Process.Handle` information. – SeanKilleen Jan 30 '15 at 19:42
  • Process.GetById or so. You might need to use Reflector to see what causes a handle to be taken. Or, if you want to be sure, use PInvoke. It's rather easy in this case. – usr Jan 30 '15 at 19:52
3

How likely is it that from the time I grab a PID to the time I kill it, another application could have spawned that uses that PID?

Another application wouldn't be assigned the same PID if it was spawned whilst the other one was alive. So this condition wouldn't happen as Windows' PIDs are unique decimal numbers to that specific process.

If I grab a PID of ID 123, how likely is it that it could have closed and a different application now owns PID 123?

This is technically feasible that the process could be closed between the time you gain your handle on the process and when you want to kill it. However, that would depend entirely on the lifespan of the process handling within your code. I guess there will always be edge cases where the application could be closed just as you're about to hook onto it, but if you're talking milliseconds/a couple of seconds I imagine it would be few and far between. As for Windows assigning the same PID immediately afterwards, I don't know for sure but they seem pretty random and now allocated again immediately after use, but they eventually would do.

What is the best way I can reasonably pull this off while limiting the potential that I kill off the wrong PID?

There is the Management Event Watcher class which appears to allow you to monitor the starting and stopping of processes. Maybe this could be used to capture events whenever they are closed for your given process name, so this way you know that it no longer exists?

Another answer discussing Management Event Watcher

MSDN ManagementEventWatcher class with example usage

Community
  • 1
  • 1
Michael
  • 451
  • 2
  • 5
  • The event watcher is necessarily asynchronous with regards to the actual state of the process list. – usr Jan 30 '15 at 19:51
1

Consider opposite approach - adjust permissions on service account so it can't kill processes of other users.

I believe such permissions are very close to default for non-admin accounts (or just default) - so unless you run service as box admin/system you may be fine with no-code solution.

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
  • That's an interesting approach and I'll think about it. Our services currently do have some increase permissions but if locked down correctly, that could be an easier solution. I still think I'd like to lean toward my code being responsible for its own actions though since with the wrong permissions it could cause some bad repercussions. – SeanKilleen Jan 30 '15 at 02:54
1

A process id is guaranteed to stay the same as long as the process continues to run. Once the process exits... there is no guarantee.

When a new process starts, Windows will pick a random process ID and assign it to the new process. Its unlikely, but possible that the id chosen was associated with a process that recently exited.

Have you looked at System.Diagnostics.Process?

They have a GetProcessesByName method that will return a list of Process objects.

Process [] localByName = Process.GetProcessesByName("notepad");

Then you can simply iterate through the Processes and kill them. Since the Process object has a handle to the process... an attempt to kill it will generate a useful exception, which you can catch.

foreach (Process p in localByName)
{
   try
   {
        p.Kill();
   }
   catch (Exception e)
   {
       // process either couldn't be terminated or was no longer running
   }
}
Gordon True
  • 963
  • 7
  • 11
  • 1
    Thanks for jumping in! That would be the simplest except in this case I have two services with the same code on that server, each running as a separate user. So I have app.exe running as user1 and app.exe running as user2. I need to kill off only the app.exe processes that are running as user1. – SeanKilleen Jan 30 '15 at 02:52
  • Is it really guaranteed that those Process objects have an open handle? Last time I looked there was some lazy initialization code. As far as I can tell GetProcessesByName does not obtain handles. – usr Jan 30 '15 at 10:19
  • 1
    @usr Good catch... from the documentation... "Only processes started through a call to Start set the Handle property of the corresponding Process instances." I need to do some investigation and figure out exactly what that means... I'll update the answer when I understand it fully. – Gordon True Jan 30 '15 at 12:33