Reputation: 2249
Using in PowerShell, how can I check if an application is locking a file?
I like to check which process/application is using the file, so that I can close it.
Upvotes: 75
Views: 158000
Reputation: 32561
This could help you: Use PowerShell to find out which process locks a file. It parses the System.Diagnostics.ProcessModuleCollection Modules property of each process and it looks for the file path of the locked file:
$lockedFile="C:\Windows\System32\wshtcpip.dll"
Get-Process | foreach{$processVar = $_;$_.Modules | foreach{if($_.FileName -eq $lockedFile){$processVar.Name + " PID:" + $processVar.id}}}
Simplified:
Get-Process | Where-Object { $_.Modules.FileName -icontains "C:\windows\System32\CRYPT32.dll" } | select Name, ID
Upvotes: 25
Reputation: 2201
Here is a fully working standalone Powershell solution without any external tools/dependencies.
It uses the function "NtQueryInformationFile" with parameter FileInformationClass=47 to get FILE_PROCESS_IDS_USING_FILE_INFORMATION data.
# script to get all PIDs of processes accessing/blocking a given file
cls
remove-variable * -ea 0
$errorActionPreference = 'stop'
Add-Type -TypeDefinition @"
using System;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
public static class ProcessUtils {
[StructLayout(LayoutKind.Sequential)]
private struct IO_STATUS_BLOCK {
public IntPtr Information;
public IntPtr Status;
}
[StructLayout(LayoutKind.Sequential)]
public struct FILE_PROCESS_IDS_USING_FILE_INFORMATION {
public ulong NumberOfProcessIdsInList;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
public ulong[] ProcessIdList;
}
[DllImport("ntdll.dll")]
private static extern int NtQueryInformationFile(SafeFileHandle FileHandle, ref IO_STATUS_BLOCK IoStatusBlock,
IntPtr FileInformation, uint Length, int FileInformationClass);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern SafeFileHandle CreateFile(string lpFileName, FileAccess dwDesiredAccess,
FileShare dwShareMode, IntPtr lpSecurityAttributes, FileMode dwCreationDisposition,
FileAttributes dwFlagsAndAttributes, IntPtr hTemplateFile);
public static ulong[] GetProcessesUsingFile(string filePath) {
var processIds = new ulong[0];
var ioStatusBlock = new IO_STATUS_BLOCK();
var fileInfo = new FILE_PROCESS_IDS_USING_FILE_INFORMATION();
using (var fileHandle = CreateFile(filePath, FileAccess.Read, FileShare.ReadWrite, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero)) {
if (!fileHandle.IsInvalid) {
var fileInfoPtr = Marshal.AllocHGlobal(Marshal.SizeOf(fileInfo));
if (NtQueryInformationFile(fileHandle, ref ioStatusBlock, fileInfoPtr, (uint)Marshal.SizeOf(fileInfo), 47) == 0) {
fileInfo = Marshal.PtrToStructure<FILE_PROCESS_IDS_USING_FILE_INFORMATION>(fileInfoPtr);
if (fileInfo.NumberOfProcessIdsInList > 0) {
processIds = new ulong[fileInfo.NumberOfProcessIdsInList];
Array.Copy(fileInfo.ProcessIdList, processIds, (int)fileInfo.NumberOfProcessIdsInList);
}
}
Marshal.FreeHGlobal(fileInfoPtr);
}
}
return processIds;
}
}
"@
# Get the PIDs of all processes using a file:
[ProcessUtils]::GetProcessesUsingFile("C:\temp\test.txt")
Upvotes: 3
Reputation: 525
I ran into this issue and wrote an entirely self contained script because I didn't want to depend on SysInternals. Script will identify and kill any process locking a file before making a full recursive copy.
https://github.com/Tikinsin/ForceCopy.ps1/blob/main/ForceCopy.ps1
This leverages the answer by Zachery Fischer and Paul DiMaggio's Github solution.
Upvotes: 1
Reputation: 41
Posted a PowerShell module in PsGallery to discover & kill processes that have open handles to a file or folder. It exposes functions to: 1) find the locking process, and 2) kill the locking process. The module automatically downloads handle.exe on first usage.
Find-LockingProcess()
Retrieves process information that has a file handle open to the specified path.
Example: Find-LockingProcess -Path $Env:LOCALAPPDATA
Example: Find-LockingProcess -Path $Env:LOCALAPPDATA | Get-Process
Stop-LockingProcess()
Kills all processes that have a file handle open to the specified path.
Example: Stop-LockingProcess -Path $Home\Documents
PsGallery Link: https://www.powershellgallery.com/packages/LockingProcessKiller
To install run:
Install-Module -Name LockingProcessKiller
Upvotes: 3
Reputation: 974
You can find for your path on handle.exe.
I've used PowerShell but you can do with another command line tool.
With administrative privileges:
handle.exe -a | Select-String "<INSERT_PATH_PART>" -context 0,100
Down the lines and search for "Thread: ...", you should see there the name of the process using your path.
Upvotes: 3
Reputation: 391
I was looking for a solution to this as well and hit some hiccups.
After extensive searching I found.
Thanks to Paul DiMaggio
This seems to be pure powershell and .net / C#
Upvotes: 14
Reputation: 2005
You can find a solution using Sysinternal's Handle utility.
I had to modify the code (slightly) to work with PowerShell 2.0:
#/* http://jdhitsolutions.com/blog/powershell/3744/friday-fun-find-file-locking-process-with-powershell/ */
Function Get-LockingProcess {
[cmdletbinding()]
Param(
[Parameter(Position=0, Mandatory=$True,
HelpMessage="What is the path or filename? You can enter a partial name without wildcards")]
[Alias("name")]
[ValidateNotNullorEmpty()]
[string]$Path
)
# Define the path to Handle.exe
# //$Handle = "G:\Sysinternals\handle.exe"
$Handle = "C:\tmp\handle.exe"
# //[regex]$matchPattern = "(?<Name>\w+\.\w+)\s+pid:\s+(?<PID>\b(\d+)\b)\s+type:\s+(?<Type>\w+)\s+\w+:\s+(?<Path>.*)"
# //[regex]$matchPattern = "(?<Name>\w+\.\w+)\s+pid:\s+(?<PID>\d+)\s+type:\s+(?<Type>\w+)\s+\w+:\s+(?<Path>.*)"
# (?m) for multiline matching.
# It must be . (not \.) for user group.
[regex]$matchPattern = "(?m)^(?<Name>\w+\.\w+)\s+pid:\s+(?<PID>\d+)\s+type:\s+(?<Type>\w+)\s+(?<User>.+)\s+\w+:\s+(?<Path>.*)$"
# skip processing banner
$data = &$handle -u $path -nobanner
# join output for multi-line matching
$data = $data -join "`n"
$MyMatches = $matchPattern.Matches( $data )
# //if ($MyMatches.value) {
if ($MyMatches.count) {
$MyMatches | foreach {
[pscustomobject]@{
FullName = $_.groups["Name"].value
Name = $_.groups["Name"].value.split(".")[0]
ID = $_.groups["PID"].value
Type = $_.groups["Type"].value
User = $_.groups["User"].value.trim()
Path = $_.groups["Path"].value
toString = "pid: $($_.groups["PID"].value), user: $($_.groups["User"].value), image: $($_.groups["Name"].value)"
} #hashtable
} #foreach
} #if data
else {
Write-Warning "No matching handles found"
}
} #end function
Example:
PS C:\tmp> . .\Get-LockingProcess.ps1
PS C:\tmp> Get-LockingProcess C:\tmp\foo.txt
Name Value
---- -----
ID 2140
FullName WINWORD.EXE
toString pid: 2140, user: J17\Administrator, image: WINWORD.EXE
Path C:\tmp\foo.txt
Type File
User J17\Administrator
Name WINWORD
PS C:\tmp>
Upvotes: 12
Reputation: 201692
You can do this with the SysInternals tool handle.exe. Try something like this:
PS> $handleOut = handle
PS> foreach ($line in $handleOut) {
if ($line -match '\S+\spid:') {
$exe = $line
}
elseif ($line -match 'C:\\Windows\\Fonts\\segoeui\.ttf') {
"$exe - $line"
}
}
MSASCui.exe pid: 5608 ACME\hillr - 568: File (---) C:\Windows\Fonts\segoeui.ttf
...
Upvotes: 50
Reputation: 1790
You should be able to use the openfiles command from either the regular command line or from PowerShell.
The openfiles built-in tool can be used for file shares or for local files. For local files, you must turn on the tool and restart the machine (again, just for first time use). I believe the command to turn this feature on is:
openfiles /local on
For example (works on Windows Vista x64):
openfiles /query | find "chrome.exe"
That successfully returns file handles associated with Chrome. You can also pass in a file name to see the process currently accessing that file.
Upvotes: 22
Reputation: 158
I've seen a nice solution at Locked file detection that uses only PowerShell and .NET framework classes:
function TestFileLock {
## Attempts to open a file and trap the resulting error if the file is already open/locked
param ([string]$filePath )
$filelocked = $false
$fileInfo = New-Object System.IO.FileInfo $filePath
trap {
Set-Variable -name filelocked -value $true -scope 1
continue
}
$fileStream = $fileInfo.Open( [System.IO.FileMode]::OpenOrCreate,[System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None )
if ($fileStream) {
$fileStream.Close()
}
$obj = New-Object Object
$obj | Add-Member Noteproperty FilePath -value $filePath
$obj | Add-Member Noteproperty IsLocked -value $filelocked
$obj
}
Upvotes: -1
Reputation: 271
I like what the command prompt (CMD) has, and it can be used in PowerShell as well:
tasklist /m <dllName>
Just note that you can't enter the full path of the DLL file. Just the name is good enough.
Upvotes: -1
Reputation: 13
If you modify the above function slightly like below it will return True or False (you will need to execute with full admin rights) e.g. Usage:
PS> TestFileLock "c:\pagefile.sys"
function TestFileLock {
## Attempts to open a file and trap the resulting error if the file is already open/locked
param ([string]$filePath )
$filelocked = $false
$fileInfo = New-Object System.IO.FileInfo $filePath
trap {
Set-Variable -name Filelocked -value $true -scope 1
continue
}
$fileStream = $fileInfo.Open( [System.IO.FileMode]::OpenOrCreate, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None )
if ($fileStream) {
$fileStream.Close()
}
$filelocked
}
Upvotes: -6