Hey, Scripting Guy! Question

Hey, Scripting Guy! If I have a bunch of duplicate processes, how can I delete all of them except for the oldest one?

-- IR

SpacerHey, Scripting Guy! AnswerScript Center

Hey, IR. We apologize for the delay in answering this question. Originally we thought it would be cute if we could get Peter Costantini, the oldest living Scripting Guy, to answer this; after all, we figured Peter would get a kick out of a scenario in which everything except the oldest thing was targeted for elimination. And we did ask Peter if he knew how to do carry out this task, but instead of answering the question he began telling us stories about the Spanish-American War and the day he got his first Model T. After a half hour or so he insisted that we go to Denny’s in time for the early-bird special; as soon as we got back to the office, he turned on Wheel of Fortune and fell asleep. When he woke up, he resumed telling us stories about the Spanish-American War. (Apparently he and Teddy Roosevelt were bitter rivals.)

It was about that time when we decided we’d better go ahead and answer this question ourselves:

strComputer = "."

Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

Set colItems = objWMIService.ExecQuery _
    ("Select * From Win32_Process Where Name = 'Notepad.exe'")

If colItems.Count < 2 Then
    Wscript.Quit
End If

dtmTarget = Now

For Each objItem in colItems
    dtmDateHolder = objItem.CreationDate
    
    dtmDateHolder = CDate(Mid(dtmDateHolder, 5, 2) & "/" & _
        Mid(dtmDateHolder, 7, 2) & "/" & Left(dtmDateHolder, 4) _
            & " " & Mid (dtmDateHolder, 9, 2) & ":" & _
                Mid(dtmDateHolder, 11, 2) & ":" & Mid(dtmDateHolder, 13, 2))

    If dtmDateHolder < dtmTarget Then
         intProcessID = objItem.ProcessID
         dtmTarget = dtmDateHolder
    End If
Next

Set colItems = objWMIService.ExecQuery _
    ("Select * From Win32_Process Where Name = 'Notepad.exe' " & _
        "AND ProcessID <> " & intProcessID)

For Each objItem in colItems
    objItem.Terminate
Next

You say that it’s a bit complicated looking? Well, yeah, a little. But don’t worry; we’ll explain how it all works, step-by-step.

Note. Peter would like us to inform everyone that back in his day he didn’t need fancy stuff like Windows and Notepad just to terminate a few processes. Give him a punch card and the good old Univac I and in a few days he’d get rid of those pesky old processes. And he’d do it after walking 10 miles through waist-deep snow just to get to the office in the first place.

As you can see, our script starts out in pretty straightforward fashion, binding to the WMI service on the local computer. (Although this script works equally well against remote machines; all you have to do is assign the name of the remote computer to the variable strComputer.) For this example, we’ve decided that we want to get rid of any “extra” copies of Notepad that might be running. With that in mind, we go ahead execute this query to get back a collection of all the processes that have a Name equal to Notepad.exe:

Set colItems = objWMIService.ExecQuery _
    ("Select * From Win32_Process Where Name = 'Notepad.exe'")

At this point we take a minor detour. Suppose it turns out that we have only one instance of Notepad running; in other words, suppose we don’t have any duplicate processes running after all. If that’s the case we’re assuming that we don’t want to terminate our one and only instance of Notepad. That’s why we stuck in this block of code:

If colItems.Count < 2 Then
    Wscript.Quit
End If

What we’re doing here is checking the value of the Count property to see how many instances of Notepad really are up and running. If the Count is less than 2 that means we have, at most, one instance of Notepad running. In that case our work is done: there aren’t any duplicate processes to be terminated. Therefore, we go ahead and call the Wscript.Quit method and terminate the script.

Let’s assume, however, that we do have multiple instances of Notepad running. In that case we need to figure out which instance is the oldest (that is, which instance was created first), then go ahead and delete all the other instances of Notepad. There are a number of different ways to do this, but we chose the following approach.

The first thing we do is assign the current date and time (using the Now function) to a variable named dtmTarget. Why? Well, we’re going to check the creation date and time of each instance of Notepad against this target date and time. Suppose we check the creation time for Notepad instance 1 and find out that it’s older than the value in dtmTarget (which it will be). In that case, Notepad instance 1 becomes our oldest instance of Notepad, and we’ll assign its creation date and time to dtmTarget. We’ll then check the creation date and time of Notepad instance 2 against the new value of dtmTarget. What if Notepad instance 2 is “younger” than dtmTarget? No problem; in that case we just loop around and repeat the process with Notepad instance 3.

If it turns out that Notepad instance 2 is older than dtmTarget then we assign its creation date and time to dtmTarget. That means that, for the moment at least, Notepad instance 2 is our oldest instance.

Make sense? If not, bear with us; we’ll show you exactly how this all takes place.

After retrieving all the instances of Notepad we set up a For Each loop to walk through each item in the collection. The first thing we do inside that loop is assign the value of the process CreationDate property to a variable named dtmDateHolder:

dtmDateHolder = objItem.CreationDate

This is where things get a little messy. The value of the CreationDate property – like most WMI date-time values – is stored using the Universal Time Coordinate (UTC) format; that means that dtmDateHolder is going to be equal to something like this:

20070504085221.620161-420

Needless to say, there’s not a lot we can do with a UTC value, at least not in that particular format. Instead, we need to convert this to a “real” date-time value. That’s what this block of code is for:

dtmDateHolder = CDate(Mid(dtmDateHolder, 5, 2) & "/" & _
    Mid(dtmDateHolder, 7, 2) & "/" & Left(dtmDateHolder, 4) _
        & " " & Mid (dtmDateHolder, 9, 2) & ":" & _
            Mid(dtmDateHolder, 11, 2) & ":" & Mid(dtmDateHolder, 13, 2))

We won’t discuss the ins and outs of converting a UTC value to a regular date-time value; for detailed information on that take a look at the Microsoft Windows 2000 Scripting Guide. About all we will say is that the preceding block of code turns a value like 20070504085221.620161-420 into a value like this:

5/4/2007 8:52:21 AM

Obviously that’s a value that is much easier for us to work with. More important, it’s a value that’s much easier for VBScript to work with.

OK. Let’s next assume that the value of dtmTarget is equal to this:

5/4/2007 8:58:13 AM

In our next line of code we check to see if our first process (dtmDateHolder) is older than this target date:

If dtmHolder < dtmTarget Then

In this case, of course, that’s going to be true: our process is older than the target date. Therefore we do two things: 1) we assign the value of the ProcessID property to a variable named intProcessID, and 2) we assign the value of dtmDateHolder to our target variable (dtmTarget). That’s what these two lines of code are for:

intProcessID = objItem.ProcessID
dtmTarget = dtmDateHolder

What’s the point of all that? Well, now we know the creation date and time for the oldest process (or at least the oldest process so far). In addition, we also know the process ID for the oldest process. That’s important, because when it comes time to start deleting processes we need an easy way to ensure that we don’t delete the oldest process. Because process IDs are unique this seemed like a pretty good way to separate the oldest process from the rest of the herd.

We then loop around and do this all over again with the next item in the collection. When we’ve finished looping through the entire collection the variable intProcessID will contain the process ID for the oldest instance of Notepad running on the computer.

Still with us? Good. We’re almost done. Promise.

It’s now time to get rid of all those young whippersnapper instances of Notepad. How are we going to do that? To begin with, we’re going to retrieve a brand-new collection of processes, using the following query:

Set colItems = objWMIService.ExecQuery _
    ("Select * From Win32_Process Where Name = 'Notepad.exe' " & _
        "AND ProcessID <> " & intProcessID)

With this query we’re still retrieving all the members of the Win32_Process class that have a Name equal to Notepad.exe. However, we’ve now added a second stipulation: we only want those instances of Notepad where the ProcessID is not equal to intProcessID. That’s what this clause is for:

AND ProcessID <> " & intProcessID

What will that do? That will retrieve all the instances of Notepad except for the instance that has a process ID equal to intProcessID. And because intProcessID just happens to have the same process ID as our oldest instance of Notepad that means that our collection will contain all the instances of Notepad except for the oldest one.

That also means that we can now delete all the instances of Notepad (except, of course, for the oldest instance) by using this little block of code:

For Each objItem in colItems
    objItem.Terminate
Next

And there you have it. If only Peter were still alive to see this.

Note. Peter has just informed us that he is still alive. We believe him. However, we’d still like to get a second opinion.

Incidentally, a lot of people ask us just how old Peter really is. As we noted in one of our webcasts, he’s only 27; he’s actually old only in comparison to the other Scripting Guys, all of whom are barely out of their teens.

Note. What’s that? If the other Scripting Guys are barely out of their teens then how could the Scripting Son be 17 years old? You know, that’s a really good question, and we’d love to answer it. However, it’s time for the early-bird special at Denny’s. See you tomorrow!