The Hammer of Thor, the Lightening of Zeus, None compares to Windows Powershell! It can CLOSE MY OPEN FILES!

Hi, I’m Sean.

I’m an ITPro and about three years ago (and you can’t tell my wife this) I began my affair with Windows Powershell.   As you can tell from my background (and forget all the silly letters) I really just consider myself a true blue, grease under my fingers, crimpers in my pocket, screwdriver in my mouth, TECH.

A few months back somebody asked on TechNet Talk Radio if Powershell could close files.   It might be able to do this but somebody else already wrote a great utility that already does this.  It’s called HANDLE.EXE from Sysinternals 

The STRENGTH of this application is that it works well and can’t accidentally close all the files on the server. The weakness is that I cannot easily (out of the box) automate it. Or can I?

Within Powershell one of the biggest pieces you need to remember is that everything (And I mean EVERYTHING) is returned as an “Object”.

Now the rest of you ItPros, quit hissing like Gollum.   An “Object” really isn’t scary or nasty.   You won’t turn into “Darth Vader” because you used one.

So here we’ve downloaded HANDLE.EXE into a little folder called C:\HANDLE PROGRAM and we’re going to query for all the open Word documents on the local computer.  As per the instructions in HANDLE.EXE I have run the console as an Administrator to gain the rights needed to globally close files

C:\Handle Program\HANDLE.EXE .DOCX

And we’ll get some output on the screen like this

Handle v3.45
Copyright (C) 1997-2011 Mark Russinovich
Sysinternals - www.sysinternals.com

WINWORD.EXE pid: 13352 type: File 15C: C:\TEC2011\Why does Sean always leave his files open.docx
WINWORD.EXE pid: 13352 type: File 1B0: C:\TEC2011\If Sean Keeps doing this we won.docx
WINWORD.EXE pid: 7496 type: File 1A8: C:\TEC2011\demodocbadsean.docx

It appears “SOMEBODY” who shall remain nameless has been leaving their Word documents open (tap tap tap)

Normally if I would like to close an open file in HANDLE.EXE I would execute

C:\Handle Program\HANDLE.EXE -c 15c -p 13352 –y

And we would get an output like this, which if it’s successful even outputs a line with the status at the end.

Handle v3.45
Copyright (C) 1997-2011 Mark Russinovich
Sysinternals - www.sysinternals.com

  15C: File (R--) C:\TEC2011\Why does Sean always leave his files open.docx

Handle closed.

So I can do this in Windows Powershell as well with a minor difference.  I’m going to store all the output from HANDLE.EXE (Since the output to my screen is an Object) into a Variable.

$RESULTS=(& 'C:\HANDLE PROGRAM\HANDLE.EXE' .docx)

So the output that WOULD have gone to the console (which is an Object) is stored in $RESULTS.   If you type $RESULTS into the Powershell Console you’ll see the same output as before.

But since it’s in a variable we can now access and manipulate that output in a way we couldn’t before.  If we look at the screen we’ll see that the output is consistent.   All the lines that have the information we need contain “pid: ” just before the Process and “type: File” just before the File handle.

So in Windows Powershell I can take the [String] object that is stored in $RESULTS (My Console output) and pull all of that out with a quick comparison, which I’ll store away in a variable I’ll call $HANDLEDATA

$HANDLEDATA=$RESULTS –match “pid:”

Which will output to the screen only that data.  At this point we can run each line through a SELECT-STRING to find where in the string the content sits.  I need to find the location of “pid:” and “type: file” in each line and pull the number following it out.  I’m going to work on the first entry in the [String] array as an example

We’ll grab the ProcessID number first.  SELECT-STRING will find it’s position which we’ll store away as $StartPid

$StartPid=($HandleData[0] | SELECT-STRING 'pid:').matches[0].Index

Then we’ll access the content which is consistently starting at positions away (The length of ‘pid: ‘) and is never more than 7 bytes long (I counted Smile with tongue out).  We tack on the “trim()” to make sure if there are any blank spaces before or AFTER the Number they are “Trimmed” off

$Processpid=$Handledata[0].substring($StartPid+5,7).trim()

We’ll repeat the process for the File Handle in a similar method.  Find the String with SELECT-STRING, Grab it’s index and then pull out what we need with a little substring()

$StartFileID=($HandleData[0] | SELECT-STRING 'type: File').matches[0].Index
$Fileid=$HandleData[0].substring($StartFileID+10,14).trim()

At this point we have a way of pulling out consistently the the necessary data to send it back to HANDLE.EXE and close it.  I can execute a line like this now after I pull the information on each line.

(& 'C:\HANDLE PROGRAM\HANDLE.EXE' –p $ProcessId –c $FileId -y)

Now this will work but looks messy on the screen.  We could just store the results away in a variable and grab that last line showing a success/failure

$CLOSEFILESTATUS=(& 'C:\HANDLE PROGRAM\HANDLE.EXE' –p $ProcessId –c $FileId -y)[-1]

Ok so there’s all the pieces.   So what does it look like as a script?

Just look below

-------------------------CLOSEWORDDOC.PS1-------------------------------------------------------------------------

#
# Script to close all open Word Documents on my File Server because Sean keeps forgetting
# Requires HANDLE.EXE from Sysinternals on the system. Free download
#

$RESULTS=(& 'C:\HANDLE PROGRAM\HANDLE.EXE' .docx)

$HANDLEDATA=$RESULTS –match ‘pid: ‘

Foreach ($File in $Handledata)
{
# Get the Handle ID of the Process for this file

     $StartPid=($File | SELECT-STRING 'pid:').matches[0].Index
$Processpid=$File.substring($StartPid+5,7).trim()

     # Get the File ID of this open File

     $StartFileID=($File | SELECT-STRING 'type: File').matches[0].Index
$Fileid=$File.substring($StartFileID+10,14).trim()

     # Close It

     $CLOSEFILESTATUS=(& 'C:\HANDLE PROGRAM\HANDLE.EXE' –p $ProcessId –c $FileId -y)[-1]

     }

#

-------------------------CLOSEWORDDOC.PS1-------------------------------------------------------------------------

Now of course this is a pretty static solution.  We could improve this in so many ways like using a variable for the file name or extension, dropping in some error handling.  But this is to give you a taste of how you can automate systems that weren’t DESIGNED to automate.

If you want to have some real fun, here is a script that within Windows Powershell I took that SAME application (HANDLE.EXE) and extended it’s features into New Cmdlets via Advanced Functions.  One to show the open files, one to obtain their Process ID’s and FILE ID’s and a third to close them.  If you’re interested on where this one has gone I’ve uploaded it to the Technet Script Repository so you can download it.  With this version you can execute Cmdlets like

GET-openfile DOCX

To get the list

GET-openfile XLSX | Close-Openfile –whatif

To CAUTIOUSLY do it

GET-openfile DOCX | Close-Openfile

This can easily be turned into module as well (how I’m running on the system right now) to extend the abilities of your shell as well.  

If you’re interested more in playing with Powershell and some of your legacy applications, there is an entire series on “Hey Scripting Guy” which delves into this. 

Ah Powershell.  Where were without you before Smile

---------------

clip_image001

Sean Kearney is an infrastructure support analyst and Microsoft MVP in Window PowerShell. He is absolutely passionate about automation technologies—especially Windows PowerShell. If you say, “Powershell,” he may just break into song. Sean considers himself “just a tech at heart,” but he can often be found dabbling with most Microsoft technologies at a moment’s notice. We have also been advised to keep him at arm’s length from any open Starbucks in Seattle. We have been advised that any rumors about him ever singing for Microsoft JobsBlog are completely unfounded. Complete rubbish. Let us just keep it between you and me, OK?

Sean's contact information:
Twitter: EnergizedTech
Blog: Energized About Technology
Website: Powershell: Releasing the Power of Shell to You