BATCHman Writes a PowerShell Script to Automate Handle

Doctor Scripto

Summary: Windows PowerShell superhero BATCHman writes a script to automate the Sysinternals Handle tool.

Microsoft Scripting Guy Ed Wilson here. Today, we continue the BATCHman series as the titular hero battles Tapeworm.

 

 

When the digital crash
In a blink and a splash
A gleam in the night
To make all wrongs right
Our heroes fly out
And there is no doubt
That evil will fall true
At the sight of the blue

The one and only .NET Duo—BATCHman and Cmdlet!

 

When last we saw our heroes, Tapeworm had taken control of the files in the Redmond Pocket Protector Recycling Plant, paralyzing the backup system. BATCHman and Cmdlet were working on a solution to automate Handle.exe. They were close to a solution as Cmdlet had discovered the output of Handle.exe was an object that could be manipulated in Windows PowerShell, an object that Select-String could work with.

No sooner were they close to working on a solution than the alarm system was blaring on the WinMobile.

Tearing down the stairs and bursting through the front door were BATCHman and Cmdlet. The sight that met their eyes was too much.

The WinMobile was getting a parking ticket. Cmdlet looked up.

“I thought you were taking your 40-speed carbon fiber, midnight blue special to the crime scene. ‘We’re a Green company,’ you said.”

BATCHman coughed. “Well, I did. From the top of the stairs to the WinMobile. I do after all have to arrive in style! I am…BATCHMAN!”

Cmdlet snickered. “Style apparently didn’t include the two dollars in quarters for the parking meter.”

BATCHman quickly dumped his change into the meter and collected his “prize,” a $140 ticket for an unpaid meter and for taking up two parking spots. “Must look into running compression on the WinMobile,” he mused.

Running back up the stairs, they headed back to the task: automating Handle.exe.

“So, Cmdlet, let’s see what we have now. With the following sequence in Windows PowerShell, we can grab the output from Handle.exe when searching for open DOCX files.”

$ScreenOutput=.\HANDLE.EXE DOCX

“And then with the following Select-String statements, identify the parts of the output that contain the text with ProcessID and FileHandle.”

$ProcessIDResults=$ScreenOutput | SELECT-STRING –pattern ‘pid: [\w]*’
$FileHandleResults=$ScreenOutput | SELECT-STRING –pattern ‘File [s\S]*?:’

“With this output within Select-String, we found there was also an index and length of the output to work with for each match.”

Cmdlet thought. “So what we need now is to find a way to pull the information out of these matches. So maybe we should look at what data we now have. We know where pid: and type: File exist because of the properties in Matches.

BATCHman could see the little hamster running in the wheel in Cmdlet’s brain. “Continue…”

“So we can use substring() and pull out based upon the index and length of that content’s position in the string.” Cmdlet quickly typed a line in Windows PowerShell.

$ProcessIDResults[0].tostring().substring(19,9)

They looked at the screen as a result appeared.

pid: 8520

“Cmdlet, excellent job. Now we need to this via the properties instead, and for both values.” BATCHman quickly took over.

$ProcessIDIndex=$ProcessIDResults[0].matches[0].Index
$ProcessIDLength=$ProcessIDResults[0].matches[0].Length

$ProcessIDResults[0].tostring().substring($ProcessIDIndex,$ProcessIDLength)

$FileHandleIndex=$FileHandleResults[0].matches[0].Index
$FileHandleLength=$FileHandleResults[0].matches[0].Length

$FileHandleResults[0].tostring().substring($FileHandleIndex,$FileHandleLength)

BATCHman looked at the output on the screen.

pid: 8520

File 164:

“Now we need to clean this up,” mumbled BATCHman as he scratched his chin. “We’ll need to skip the first four characters of the pid: by adjusting the starting point in the substring and the length appropriately. We will skip File because it’s only four characters long. But for the FileHandle, we’ll drop an extra character to lose that colon on the end.”

$ProcessIDResults[0].tostring().substring($ProcessIDIndex+4,$ProcessIDLength-4)
$FileHandleResults[0].tostring().substring($FileHandleIndex+4,$FileHandleLength-5)

8520

164

“Now we’ve just got to store it as a variable and tack on a trim() method to remove any extraneous spaces leading or trailing.”

$ProcessID=$ProcessIDResults[0].tostring().substring($ProcessIDIndex+4,$ProcessIDLength-4).trim()
$FileHandle=$FileHandleResults[0].tostring().substring($FileHandleIndex+4,$FileHandleLength-5).trim()

Cmdlet looked and realized that and this point they could automate the application. “So, BATCHman, we could just simply at this point step through the results with a Foreach statement and call up Handle.exe!”

BATCHman quickly typed a line to verify this would work on the DOCX files.

$TotalResults=$ProcessIdResults.count

For ($Counter=0; $Counter –lt $TotalResults; $Counter++)

{

$ProcessIDIndex=$ProcessIDResults[$Counter].matches[0].Index
$ProcessIDLength=$ProcessIDResults[$Counter].matches[0].Length

$FileHandleIndex=$FileHandleResults[$Counter].matches[0].Index
$FileHandleLength=$FileHandleResults[$Counter].matches[0].Length

$ProcessID=$ProcessIDResults[$Counter].tostring().substring($ProcessIDIndex+4,$ProcessIDLength-4).trim()
$FileHandle=$FileHandleResults[$Counter].tostring().substring($FileHandleIndex+4,$FileHandleLength-5).trim()

(& ‘.\Handle Program\handle.exe’ -c $FileHandle -p $ProcessID -y)

}

Smiling, BATCHman noted as files closed on the screen. He pressed a Ctrl+C to stop the process.

“Now, Cmdlet, all we need to do to get this automated and running is turn this into a function so that Jane can run her backups properly and these old pocket protectors can get back to being recycled.”

BATCHman quickly rewrote it into a single Windows PowerShell script with a function to specify types of files to close that Jane could run automatically on the file server.

function global:close-file ($name) {
$ScreenOutput=(& ‘.\Handle\handle.exe’ $name)
$ProcessIDResults=$ScreenOutput | SELECT-STRING –pattern ‘pid: [\w]*’
$FileHandleResults=$ScreenOutput | SELECT-STRING –pattern ‘File [s\S]*?:’

$TotalResults=$ProcessIdResults.count

For ($Counter=0; $Counter –lt $TotalResults; $Counter++)

{

$ProcessIDIndex=$ProcessIDResults[$Counter].matches[0].Index
$ProcessIDLength=$ProcessIDResults[$Counter].matches[0].Length

$FileHandleIndex=$FileHandleResults[$Counter].matches[0].Index
$FileHandleLength=$FileHandleResults[$Counter].matches[0].Length

$ProcessID=$ProcessIDResults[$Counter].tostring().substring($ProcessIDIndex+4,$ProcessIDLength-4).trim()
$FileHandle=$FileHandleResults[$Counter].tostring().substring($FileHandleIndex+4,$FileHandleLength-5).trim()

(& ‘.\Handle Program\handle.exe’ -c $FileHandle -p $ProcessID -y)

}

CLOSE-HANDLE DOCX
CLOSE-HANDLE XLSX
CLOSE-HANDLE ACCDB
CLOSE-HANDLE PPTX

BATCHman handed the script to Jane and scheduled it to run every 10 minutes to thwart Tapeworm’s efforts. In moments, there was a shriek from the basement! “AIAGHIAGHI!!!! Curse you! Curse you!”

“Jane, quickly, let’s trigger that backup now!” commanded BATCHman.

While the backup ran, BATCHman and Cmdlet followed the shrieking sounds of Tapeworm to his hideaway, a forgotten IT storage room nicknamed the Pit of Eternal Sorrow.

“A-ha! We have you now Tapeworm! What have you got to say for yourself?”

“Bah! Curses! I would have gotten away with it if hadn’t been for you meddling kids and Windows PowerShell! BAAAAAHAHH!!!” he cursed as BATCHman hauled him off. Scooby Doo was nowhere to be found.

 

I want to thank Sean for another exciting episode of BATCHman. Join us tomorrow for the exciting conclusion to the BATCHman series.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

 

 

1 comment

Discussion is closed. Login to edit/delete existing comments.

  • Steve LoGrande 0

    OK, Am i the only guy to try this or did something change with systinternals handle.exe

    I had to change line $FileHandleResults=$ScreenOutput | SELECT-STRING –pattern ‘File [s\S]*?:’

    to $FileHandleResults=$ScreenOutput | SELECT-STRING –pattern ‘File [\s\S]*?:’

    (missing slash before the lower case s (regex whitespace character).

    My $ScreenOutput looked like this, with multiple spaces between “File” and the actual Handle Number (ex 1164).

    Nthandle v4.1 – Handle viewer Copyright (C) 1997-2016 Mark Russinovich Sysinternals – http://www.sysinternals.com java.exe pid: 14272 type: File       1164:
    D:\Workspaces\workspace\Ch5_Data_Dictionary\.PolicyCenter\.svn\svn.65d854d7-63d0-40dd-9271-36a3712e8f7c.tmp

Feedback usabilla icon