PowerShell script to check an application that's locking a file?
Asked Answered
V

12

72

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.

Valentijn answered 5/6, 2009 at 21:0 Comment(1)
I had a Powershell script that was creating a script by redirecting output to a file then reading the content in. However, the command on one occasion was still running at the time I tried to read the content in, so I effectively did this to work around it. Do { $e = $False try { $content = [System.IO.File]::ReadAllText($calraw, [System.Text.Encoding]::UTF8) } catch { # cater for long running output $e = $True start-sleep -Milliseconds 1000 } } Until ($e -EQ $False)Bobsleigh
W
50

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
...
Wilhoit answered 5/6, 2009 at 23:35 Comment(5)
Thanks, I can just use handle [filename], to make it simpler.Valentijn
:( still having problems though. . . it's not that powerful to show if there files (i.e. text files) opened by a certain process.Valentijn
handle /dir worked for me. then I killed the process in task manager that was locking the folder.Endue
handle doesn't show me something for a PDF ...Leidaleiden
@testing, you must have administrative privilege to run Handle.Scrunch
A
24

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
Altheta answered 22/11, 2012 at 8:39 Comment(2)
Would have been the perfect answer for me, but seems that this would work only for dlls, and not for just any files, like locked text files.Originally
Native dll. It doesn't work for .NET assemblies.Anastos
Z
20

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.

Zobkiw answered 5/6, 2009 at 21:11 Comment(3)
From what I see that command simply enumerates files that are opened by a user from remote via SMB shares. It won't tell you anything about the process using it.Orvieto
You can't tell it from the link, but it looks like Johannes is right. It doesn't work on Vista x64 for me -- says "INFO: No shared open files found."Danas
this appears to tell if chrome is open not what chrome has openAnomalous
P
13

I was looking for a solution to this as well and hit some hiccups.

  1. Didn't want to use an external app
  2. Open Files requires the local ON attribute which meant systems had to be configured to use it before execution.

After extensive searching I found.

https://github.com/pldmgg/misc-powershell/blob/master/MyFunctions/PowerShellCore_Compatible/Get-FileLockProcess.ps1

Thanks to Paul DiMaggio

This seems to be pure powershell and .net / C#

Pulsation answered 19/5, 2020 at 13:20 Comment(3)
Including C# in the PowerShell code is brilliant, yet it seems like cheating in a way... peplaces external program handle.exe with an internally compiled C# module that does the job.Lumbar
Doesn't seem to work on directories :/Emmittemmons
I ended up opening resmon -> CPUs -> Associated Handles and searching for my directory name.Emmittemmons
S
12

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>
Serpigo answered 24/7, 2015 at 17:25 Comment(1)
Just this works for me:<# .Description Calls SysInternals handle.exe to get the locking bastard. .Parameter path File or folder #> function lock-handle([string] $path) { . "C:\Program Files\SysInternals\handle64.exe" $path }Rosanne
A
3

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.

Alexandra answered 27/10, 2020 at 22:51 Comment(1)
I found a Thread: that was attached to a directory, killed it in task manger so now no thread is listed, yet the directory still won't delete.Tungusic
C
2

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

Cesium answered 26/8, 2021 at 3:51 Comment(0)
T
1

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.

Trever answered 13/3, 2023 at 21:45 Comment(0)
P
1

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")
Pisgah answered 6/5 at 8:16 Comment(0)
M
-1

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
}
Mcgaha answered 15/9, 2012 at 18:20 Comment(1)
This only shows if the file is locked or not, it doesn't show which process is using it.Hotchpot
I
-1

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.

Impenitent answered 1/4, 2015 at 23:39 Comment(0)
A
-6

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
}
Apicella answered 18/9, 2012 at 11:40 Comment(3)
This indicates whether the file is locked or not, but doesn't give the application that's locking the file.Make
Additionally: it will actually create a new file if it doesn't already existSeagoing
Not to mention is copies Jordijs answer.Marder

© 2022 - 2024 — McMap. All rights reserved.