Quick Links

One of the more frustrating errors that an end-user or IT administrator can deal with is that of locked files within Windows. When you delete a folder, move a file, or edit a configuration and you encounter a locked file error message, it's better to deal with that quickly and efficiently.

Microsoft introduced PowerShell as a replacement shell, but it has far more functionality than that and is a complex and capable language. Let's look in this article on how you can utilize PowerShell to deal with locked files.

The Locked File Problem

How exactly does a file get locked? During normal use, a process creates many handles to resources such as a file. By doing so, the processes often lock the file to prevent unintended configuration changes or other corruption from taking place. The problem often is that it is difficult to determine what process has locked the file, and subsequently, how to remove that lock from the file.

Unfortunately, there is no built-in cmdlet to test a file and tell whether it is locked or by what process. Therefore, you need to create your own functions or wrap other useful tools that exist to assist in finding out more about these files.

Testing for Locked Files

Within Windows, you are able to 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
    

variable needs to be set to 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')
    

command, 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 did say that Windows doesn't have a built-in function, but there is one case where a function does exist. If you have a remote share or even administrative shares (such as c$), then you can use the Get-SMBOpenFile cmdlet to report on those open files.

        PS C:> Get-SMBOpenFile

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

PS C:>

The downside is that this only works for files that are remotely accessed. Any locked files that are in use on your local system won't be reported on, so for most cases this is not a viable solution. To close, you can pipe the open files returned to the Close-SMBOpenFile command.

Get-SMBOpenFile | Close-SMBOpenFile

OpenFiles Utility

Windows has a built-in utility named openfiles that can help list what files are in use and disconnect them. At first glance, it looks perfect for your needs! You can even wrap this within a PowerShell function to ease the querying and disconnecting of files.

Open up an Administrative PowerShell prompt and run the command openfiles /query. Right away, you should receive an error message stating that the "maintain objects list" global flag needs to be on.

        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 objects list is what actually maintains the list of handles that are in use and enables openfiles to query that information. To turn this on, enter in openfiles /local on and then restart your computer. The downside to turning this feature on is that there is a slight performance hit, which depending on your system, may not be worth the utility of using this tool. That being said, let's see how we can make this work within 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 of openfiles into PowerShell. Using that information, you can then disconnect a file to unlock it. Due to the performance hit you may incur with enabling the maintain objects list capability, it might not be worthwhile for your needs. Because of that, other solutions may be needed.

The Handle Application

Sysinternals is known for the many useful and nearly essential IT tools that they make. Some time ago, Sysinternals was acquired by Microsoft, and you can download and use these well-supported tools for yourself. Conveniently, there is an application named handles that provides exactly what you are looking for!

First, youneed to download the application, unzip the files, and put the executables in a location that your Path environmental variable has included. By doing so, you can easily reference the application wherever you need it. 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 to find out what files are being used---and you can test them using your file locked code from before. But how do you make this easier to use? The following code reads each process and retrieves just the locked files. The downside is that this takes a while to run 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
        }
    }
}

Ultimately though, what you get is an actionable collection of files, listed by process, that you know are in use and can be further filtered. If you find out 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 further wrap all of this in a function to make it even easier to parse and search as necessary. TMany possibilities exist, especially in combining the various methods into a solution that fits 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 those files, but it does require a bit of work as Windows does not have a truly comprehensive built-in method of dealing with those locked files. The solutions outlined should make short work of whatever the issue might be and let you move on to far more important tasks!