BATCHman Writes a PowerShell Script to Automate Handle

BATCHman Writes a PowerShell Script to Automate Handle

  • Comments 2
  • Likes

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

 

 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • BATCHman is back and saved us again!

    GREAT to know that he and his regex helped to "handle" this case!

    Well done, BATCHman!!

    As you know, I like regular expressions which is bad luck because I will add something else here :-)

    So .... there is onother approach to solve this case which might be a little more "straight" .. !?

    If we extract the lines conataining the interesting information and use the replace operator to get

    rid of all other information except the pid and handle,

    we can reduce the calculations of substrings and have a shorter form like this:

    $commands = ((c:\tools\handle total) -match 'pid') -replace  '^.*pid:\s*([0-9]+).*File\s*([0-9A-F]+).*$', 'c:\tools\handle.exe -c $2 -p $1 -y'

    This returns an array of commands that look like that

    c:\tools\handle.exe -c 210 -p 6380 -y

    If we pass them to Invoke-Expression, the handles are closed!

    The first part: ((c:\tools\handle total) -match 'pid') reduces handle's output to the interesting lines,

    those that contain the word 'pid'.

    The result is passed to replace, which skips the beginning '^.*' and end '.*$' of the line,

    looks for 'pid:' followed by whitespace and captures the part in round braces: '([0-9]+)' which is the pid number.

    We skip '.*' anything up to 'File' followed by whitespace and capture the hexadecimal handle number '([0-9A-F]+)'

    The replace operator is nice enough to store the captures in automatic variables $1 and $2.

    So we have the pid in $1 and the handle number in $2.

    Having replaced all other characters of each matching line, we can build a new line from scratch with the

    path to the handle in first place and add the parameters for -p = pid ( $1 ) and -c = handlenumber ( $2 )

    to the commandline. "-y" is just used to avoid confirmation questions.

    Regexps are GREAT!

    Take a look at them!!

    Klaus

  • ... another interesting variant is this here:

    (.\handle\handle.exe $name) -match 'pid:' |

    % {

       $processId,$fileHandle = ($_ -split '[:\s]+')[2,5]

       .\handle\handle.exe -c $fileHandle -p $processId -y

    }

    We call handle, look for lines containing the keyword 'pid:' and pipe them to the split operator using the regexp '[:\s]' to split each line at a colon or whitespace.

    If we rely ( and we have to! ) the fact that the pid is located in part 2 and the handle in part5 of the splitted line, you can use this information to close the handles!

    Klaus.