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!