How to Manage Open File Handles with PowerShell

People in command line
Julia Tim / Shutterstock

One of the most frustrating errors that an end user or IT administrator can deal with is that of locked files in Windows. When you delete a folder, move a file or change a configuration and you encounter a locked file error message, it is best to deal with this quickly and efficiently.

Microsoft introduced PowerShell as a replacement shell, but it has many more features than that and is complex and capable language. Let’s see in this article how to use PowerShell to manage locked files.

The problem of locked files

How exactly is a file locked? During normal use, a process creates many resource descriptors such as a file. In doing so, processes often lock the file to prevent unintended configuration changes or other corruption. The problem is often that it is difficult to determine which process has locked the file and, subsequently, how to remove the lock from the file.

Unfortunately, there is no built-in cmdlet to test a file and say if it is locked or by what process. Therefore, you should create your own functions or wrap other useful tools that exist to help you learn more about these files.

Locked files test

On Windows, you can test to see if an individual file is locked. Using the following code block, you can test to see if a given file is locked. the $Item The variable must be defined on a full file path. By testing to see if the file can be opened for writing, as seen with the [System.IO.File]::Open($Item,'Open','Write') , you can tell if the file is locked.

If ([System.IO.File]::Exists($Item)) {
  Try {
      $FileStream = [System.IO.File]::Open($Item,'Open','Write')

      $FileStream.Close()
      $FileStream.Dispose()

      $IsLocked = $False
  } Catch [System.UnauthorizedAccessException] {
      $IsLocked = 'AccessDenied'
  } Catch {
      $IsLocked = $True
  }
}

Get-SMBOpenFile

I said that Windows does not have a built-in function, but there is a case where a function exists. If you have a remote share or even administrative shares (such as c$), you can use the Get-SMBOpenFile cmdlet to report these open files.

PS C:> Get-SMBOpenFile

FileId       SessionId    Path  ShareRelativePath
------       ---------    ----  -----------------
154618822665 154618822657 C:

PS C:> 

The downside is that it only works for remotely accessible files. No locked files in use on your local system will be reported. Therefore, in most cases, this is not a viable solution. To close, you can direct the opened files returned to the Close-SMBOpenFile order.

Get-SMBOpenFile | Close-SMBOpenFile

OpenFiles utility

Windows has a built-in utility named openfiles which can help to list the files used and to disconnect them. At first glance, it looks perfect for your needs! You can even wrap this in a PowerShell function to make it easier to query and disconnect files.

Open a PowerShell administration prompt and run the command openfiles /query. Immediately, you should receive an error message indicating that the global flag “maintain list of objects” must be activated.

PS C:/> openfiles /query

INFO: The system global flag 'maintain objects list' needs
      to be enabled to see local opened files.
      See Openfiles /? for more information.


Files opened remotely via local share points:
---------------------------------------------

INFO: No shared open files found.

This object list is what actually maintains the list of handles in use and allows openfiles to query this information. To activate it, enter openfiles /local on then restart your computer. The downside to enabling this feature is that there is a slight drop in performance, which, depending on your system, may not be worth the use of this tool. That being said, let’s see how we can make it work in PowerShell.

PS C:> openfiles /Query /fo csv /nh

Files opened remotely via local share points:
---------------------------------------------
"ID","Accessed By","Type","Open File (Pathexecutable)"
"608","user","Windows","C:"

PS C:> openfiles /Query /fo csv | Select-Object -Skip 4 | ConvertFrom-CSV

ID  Accessed By  Type    Open File (Pathexecutable)
--  -----------  ----    ---------------------------
608 user         Windows C:

PS C:> openfiles /disconnect /id 608

SUCCESS: The connection to the open file "C:" has been terminated.

With the previous examples, you can see how to import the CSV output from openfiles in PowerShell. Using this information, you can then disconnect a file to unlock it. Due to lower performance, you may need to activate maintain objects list capacity, it might not be worth it for your needs. For this reason, other solutions may be necessary.

The Handle application

Sysinternals is known for the many useful and almost essential computer tools it makes. Some time ago, Sysinternals was acquired by Microsoft, and you can download and use these well-supported tools yourself. Ideally, there is an application named handles which provides exactly what you are looking for!

First, you must Download the application, decompress the files and place the executables in a location that your Path environment variable included. By doing so, you can easily reference the application where you need it. By using a simple query for open files, you can see that you get a lot of results (truncated for readability).

PS C:/> handle64 -NoBanner
...
------------------------------------------------------------------------------
RuntimeBroker.exe pid: 9860 User
   48: File          C:WindowsSystem32
  188: Section       BaseNamedObjects__ComCatalogCache__
  1EC: Section       BaseNamedObjects__ComCatalogCache__
------------------------------------------------------------------------------
chrome.exe pid: 4628 User
   78: File          C:Program Files (x86)GoogleChromeApplication78.0.3904.108
  1C4: Section       Sessions1BaseNamedObjectswindows_shell_global_counters
...

You seem to get what you want – at least a way of knowing which files are in use – and you can test them using your locked file code before. But how do you make it easier to use? The following code reads each process and retrieves only the locked files. The downside is that it takes time as there are many processes.

$Processes = Get-Process

$results = $Processes | Foreach-Object {
    $handles = (handle64 -p $_.ID -NoBanner) | Where-Object { $_ -Match " File " } | Foreach-Object {
            [PSCustomObject]@{
        "Hex"  = ((($_ -Split " ").Where({ $_ -NE "" })[0]).Split(":")[0]).Trim()
        "File" = (($_ -Split " ")[-1]).Trim()
        }
    }

    If ( $handles ) {
        [PSCustomObject]@{
            "Name"    = $_.Name
            "PID"     = $_.ID
            "Handles" = $handles
        }
    }
}

In the end, what you get is a collection of actionable files, listed by process, which you know are in use and can be further filtered. If you find that you need to close one of them, you can do the following (as an administrator):

PS C:> $results |
>>  Where-Object Name -EQ 'Notepad' |
>>  Where-Object { $_.Handles.File -Match "test.txt" }

Name                      PID Handles
----                      --- -------
Notepad                   12028 {@{Hex=44; File=C:test.txt}


PS C:> handle64 -p 12028 -c 44 -y -nobanner

44: File  (R-D)   C:test.txt

Handle closed.

You can wrap it all up in a function to make it even easier to analyze and find if necessary. Many possibilities exist, in particular by combining the different methods in a solution adapted to your environment.

Conclusion

Dealing with locked files can be a challenge, especially when it stops what you need to get done quickly. There are a number of ways to find and unlock these files, but it does take a bit of work because Windows doesn’t have a really comprehensive built-in method for handling these locked files. The solutions described should solve the problem quickly and let you move on to much more important tasks!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.