Combining Powershell and Sysinternals–Closing a File Handle

| | Comments (1) | TrackBacks (0)

powershell

An interesting question came in Friday during the Webcast on Technet Talk Radio.   What I thought was “Huh, yeah you SHOULD be able to pull that off in Windows…”

“Is there a way in Powershell to close a file handle?”

I decided this morning, that was bugging me.   So I started digging into the Developer Land on MSDN to try and learn something.  That made my brain hurt a bit.   After a did a quick search on Bing for the obvious.  Maybe somebody had already built a tool to do this.

Wouldn’t it make sense the coolest bunch of Geeks at Sysinternals would have done this already?  YUP!

The tool is called “HANDLE” and you need to run it as an Administrator (So I imagine running it as SYSTEM should have the same rights if I had to schedule it)

HANDLE works very simply.  Key in HANDLE and the file you’re trying to close, it will yield a pile of info including the Process ID calling it.   It will also output the Hexadecimal ID the File is associated with.

Download it and run it at least once beforehand (To accept the EULA) and you’re good to play

Here’s the catch.  Standard Administrator?  Perfect.   I’m closing a handle, I manually run it.  But today we’d like to automate it. 

So our FIRST challenge is getting the output of HANDLE into Something Powershell will use.  That’s easy, LITERALLY grab the Console output.

$DATA=(& “C:\PathToHandle\Handle.exe” filesomedummyleftopen)

So now we have a Big pile of Data stored in conveniently a variable called $DATA.  Fortunately for us our good friends at Sysinternals formatted the output of HANDLE into CONSISTENT columns.

So the first 5 rows in our array we can ignore.   That’s the header from the output in HANDLE.EXE.   How do you tell? You can step through it Piece by piece like so

$DATA[0]

 

$DATA[1]

Handle 3.45

$DATA[2]

Copyright (C) 1997-2011 Mark Russinovich

$DATA[3]

Sysinternals - www.sysinternals.com

$DATA[4]

 

See? Now our FIFTH line is where the goodies start showing up.  It appears a “Certain ItPro” forgot to close off a Word document.

$DATA[5]

WINWORD.EXE        pid: 7912   type: File           7D0: C:\Users\sean.kearney\Documents\Sample.docx

*cough* oops.

The nice part with this output is we only need TWO pieces of info, the PID (Process ID) and the HEXID for the file handle.    The nicer part is since all of this is CONSISTENT in it’s formatting.

I found the PID Number always starts at column 24 and is never more than 7 bytes long.  

The HEXID could be more than what I see here (3 bytes) but the area IT takes up is ALWAYS the spot before the file name and ALWAYS after the word “File”.   Turns out THAT is starting at Column 41 and never more than 14 Characters.

How did I find this out? I’ll be honest.  I cheated.  I kept doing this and changing numbers

$DATA[5].substring(10)

$DATA[5].substring(15)

Which shows you all information from position 10 down or position 20 down.  After I went in and keyed in

$DATA[5].substring(24,1)

$DATA[5].substring(24,4)

Which shows you all the information from position 24 down and then only 1 character or 4 characters.   It's not “Geeky cool” but for a new Powershell Admin, knowing how you could navigate the data can help.

So what does that mean to us?  Consistency and a pattern we can work with.

$MYPID=$DATA[5].Substring(24,7)

$HEX=$DATA[5].Substring(41,14)

So great I HAVE the information and I’ll just pass that right back to HANDLE.EXE to close the file handle.

But wait.  We forgot something.  There might be blank space in that text.   We want JUST the information, no blanks to screw us up with Delimiters! *ARGH!* Sean !  You lied to us!  You said this was going to be easy!

Well, that’s doable to.  Just a little more trickery in Powershell.   We can use what is known as the “Trim” method on a string to “Trim” the blank spaces off either side

$MYPID=($DATA[5].Substring(24,7)).trim()

$HEX=($DATA[5].Substring(41,14)).trim()

So now ARMED with this information we can just call HANDLE.EXE from Powershell.exe and

(& “C:\PathToHandle\Handle.exe” –c $HEX –p $MYPID -y)

So how is this efficient?   If you play with Handle it can do “wildcarding” on Paths and files.   If you have a particular file structure on a FILE server that users are forever leaving Excel documents open?  Backup can’t get to it?  You can NOW use Powershell AND Systinternals together to create an even more powerful tool.

You’ll also note that I only referenced Element [5] in the array.  If I wished to step up one more level and have ALL of the files revealed by HANDLE.EXE closed, I can just do this. (But be careful on your targeting!)

# Get the console output of HANDLE.EXE from Sysinternals into a Powershell Variable

$DATA=(& “C:\PathToHandle\Handle.exe” FolderFullOfStuffLeftOpenByTheSillyUsers)

# Get the count of lines in the output
$Count=($INFO.Count)-1

# Remember, the good stuff starts at line 5
Foreach ($line in  5..$Count)
{
      # Get the Process Id for each file
      #
      $MYPID=($DATA[$Count].Substring(24,7)).trim()
      # Get the Hexadecimal ID for each open file
      #
     
     
$HEX=($DATA[$Count].Substring(41,14)).trim()
      # Close that file!      
      #
      (& “C:\PathToHandle\Handle.exe” –c $HEX –p $MYPID -y)
}

 

This is why I love Powershell.  I do NOT need to throw away perfectly good tools just to use Powershell.  I can leverage BOTH, like the amazing Console based tools from Sysinternals, at the same time and sometimes come up with solutions I never could easily conceive of before.

Remember, the Power of Shell is in YOU

Sean
the Energized Tech

0 TrackBacks

Listed below are links to blogs that reference this entry: Combining Powershell and Sysinternals–Closing a File Handle.

TrackBack URL for this entry: http://www.energizedtech.com/cgi-sys/cgiwrap/jolyrogr/managed-mt/mt-tb.cgi/456

1 Comments

Hi Sean,

thanks a lot for your post. Your version did not appear to work for me. Please find below the modified version:

function Free-Handles([string]$lockedFolder){
$pathToHandle='E:\Sysinternals Suite\handle.exe'
#Get the console output of HANDLE.EXE from Sysinternals into a variable
$data=(& $pathToHandle $lockedFile)
$lenData=$data.Length-1
#check whether there are open handles for the file/folder
if ($lenData -gt 5){
# Remember, the good stuff starts at line 5
for ($index=5;$index -le $lenData; $index++) {
#split current "line" ignoring blanks
$splitData=$data[$index].ToString().split(": ") | where {$_ -ne ""}
# Get the Process Id for each file
$procID=$splitData[2]
# Get the Hexadecimal ID for each open file
$hexID=$splitData[5]
# Close that file!
(& $pathToHandle -c $hexID.toString() -p $procID.tostring() -y) #|out-null
}
}
}

Free-Handles "D:\testfolder"

Dirk