Skip to content

Instantly share code, notes, and snippets.

@i-e-b
Last active June 7, 2023 10:15
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save i-e-b/2290426 to your computer and use it in GitHub Desktop.
Save i-e-b/2290426 to your computer and use it in GitHub Desktop.
Discover Win32 processes locking a file, and file locks by process. This is quite old now, and better methods are available. Additional research by Walkman100 at https://github.com/Walkman100/FileLocks (see comments section)
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Text;
using System.Threading;
namespace FileLockInfo
{
public class Win32Processes
{
/// <summary>
/// Return a list of processes that hold on the given file.
/// </summary>
public static List<Process> GetProcessesLockingFile(string filePath)
{
var procs = new List<Process>();
var processListSnapshot = Process.GetProcesses();
foreach (var process in processListSnapshot)
{
if (process.Id <= 4) { continue; } // system processes
var files = GetFilesLockedBy(process);
if (files.Contains(filePath)) procs.Add(process);
}
return procs;
}
/// <summary>
/// Return a list of file locks held by the process.
/// </summary>
public static List<string> GetFilesLockedBy(Process process)
{
var outp = new List<string>();
ThreadStart ts = delegate
{
try
{
outp = UnsafeGetFilesLockedBy(process);
}
catch { Ignore(); }
};
try
{
var t = new Thread(ts);
t.IsBackground = true;
t.Start();
if (!t.Join(250))
{
try
{
t.Interrupt();
t.Abort();
}
catch { Ignore(); }
}
}
catch { Ignore(); }
return outp;
}
#region Inner Workings
private static void Ignore() { }
private static List<string> UnsafeGetFilesLockedBy(Process process)
{
try
{
var handles = GetHandles(process);
var files = new List<string>();
foreach (var handle in handles)
{
var file = GetFilePath(handle, process);
if (file != null) files.Add(file);
}
return files;
}
catch
{
return new List<string>();
}
}
const int CNST_SYSTEM_HANDLE_INFORMATION = 16;
private static string GetFilePath(Win32API.SYSTEM_HANDLE_INFORMATION systemHandleInformation, Process process)
{
var ipProcessHwnd = Win32API.OpenProcess(Win32API.ProcessAccessFlags.All, false, process.Id);
var objBasic = new Win32API.OBJECT_BASIC_INFORMATION();
var objObjectType = new Win32API.OBJECT_TYPE_INFORMATION();
var objObjectName = new Win32API.OBJECT_NAME_INFORMATION();
var strObjectName = "";
var nLength = 0;
IntPtr ipHandle;
if (!Win32API.DuplicateHandle(ipProcessHwnd, systemHandleInformation.Handle, Win32API.GetCurrentProcess(), out ipHandle, 0, false, Win32API.DUPLICATE_SAME_ACCESS))
return null;
IntPtr ipBasic = Marshal.AllocHGlobal(Marshal.SizeOf(objBasic));
Win32API.NtQueryObject(ipHandle, (int)Win32API.ObjectInformationClass.ObjectBasicInformation, ipBasic, Marshal.SizeOf(objBasic), ref nLength);
objBasic = (Win32API.OBJECT_BASIC_INFORMATION)Marshal.PtrToStructure(ipBasic, objBasic.GetType());
Marshal.FreeHGlobal(ipBasic);
IntPtr ipObjectType = Marshal.AllocHGlobal(objBasic.TypeInformationLength);
nLength = objBasic.TypeInformationLength;
// this one never locks...
while ((uint)(Win32API.NtQueryObject(ipHandle, (int)Win32API.ObjectInformationClass.ObjectTypeInformation, ipObjectType, nLength, ref nLength)) == Win32API.STATUS_INFO_LENGTH_MISMATCH)
{
if (nLength == 0)
{
Console.WriteLine("nLength returned at zero! ");
return null;
}
Marshal.FreeHGlobal(ipObjectType);
ipObjectType = Marshal.AllocHGlobal(nLength);
}
objObjectType = (Win32API.OBJECT_TYPE_INFORMATION)Marshal.PtrToStructure(ipObjectType, objObjectType.GetType());
var ipTemp = Is64Bits() ? new IntPtr(Convert.ToInt64(objObjectType.Name.Buffer.ToString(), 10) >> 32) : objObjectType.Name.Buffer;
var strObjectTypeName = Marshal.PtrToStringUni(ipTemp, objObjectType.Name.Length >> 1);
Marshal.FreeHGlobal(ipObjectType);
if (strObjectTypeName != "File")
return null;
nLength = objBasic.NameInformationLength;
var ipObjectName = Marshal.AllocHGlobal(nLength);
// ...this call sometimes hangs. Is a Windows error.
while ((uint)(Win32API.NtQueryObject(ipHandle, (int)Win32API.ObjectInformationClass.ObjectNameInformation, ipObjectName, nLength, ref nLength)) == Win32API.STATUS_INFO_LENGTH_MISMATCH)
{
Marshal.FreeHGlobal(ipObjectName);
if (nLength == 0)
{
Console.WriteLine("nLength returned at zero! " + strObjectTypeName);
return null;
}
ipObjectName = Marshal.AllocHGlobal(nLength);
}
objObjectName = (Win32API.OBJECT_NAME_INFORMATION)Marshal.PtrToStructure(ipObjectName, objObjectName.GetType());
ipTemp = Is64Bits() ? new IntPtr(Convert.ToInt64(objObjectName.Name.Buffer.ToString(), 10) >> 32) : objObjectName.Name.Buffer;
if (ipTemp != IntPtr.Zero)
{
var baTemp = new byte[nLength];
try
{
Marshal.Copy(ipTemp, baTemp, 0, nLength);
strObjectName = Marshal.PtrToStringUni(Is64Bits() ? new IntPtr(ipTemp.ToInt64()) : new IntPtr(ipTemp.ToInt32()));
}
catch (AccessViolationException)
{
return null;
}
finally
{
Marshal.FreeHGlobal(ipObjectName);
Win32API.CloseHandle(ipHandle);
}
}
string path = GetRegularFileNameFromDevice(strObjectName);
try
{
return path;
}
catch
{
return null;
}
}
private static string GetRegularFileNameFromDevice(string strRawName)
{
string strFileName = strRawName;
foreach (string strDrivePath in Environment.GetLogicalDrives())
{
var sbTargetPath = new StringBuilder(Win32API.MAX_PATH);
if (Win32API.QueryDosDevice(strDrivePath.Substring(0, 2), sbTargetPath, Win32API.MAX_PATH) == 0)
{
return strRawName;
}
string strTargetPath = sbTargetPath.ToString();
if (strFileName.StartsWith(strTargetPath))
{
strFileName = strFileName.Replace(strTargetPath, strDrivePath.Substring(0, 2));
break;
}
}
return strFileName;
}
private static IEnumerable<Win32API.SYSTEM_HANDLE_INFORMATION> GetHandles(Process process)
{
var nHandleInfoSize = 0x10000;
var ipHandlePointer = Marshal.AllocHGlobal(nHandleInfoSize);
var nLength = 0;
IntPtr ipHandle;
long lHandleCount;
try
{
while (Win32API.NtQuerySystemInformation(CNST_SYSTEM_HANDLE_INFORMATION, ipHandlePointer, nHandleInfoSize, ref nLength) == Win32API.STATUS_INFO_LENGTH_MISMATCH)
{
nHandleInfoSize = nLength;
Marshal.FreeHGlobal(ipHandlePointer);
ipHandlePointer = Marshal.AllocHGlobal(nLength);
}
var baTemp = new byte[nLength];
Marshal.Copy(ipHandlePointer, baTemp, 0, nLength);
if (Is64Bits())
{
lHandleCount = Marshal.ReadInt64(ipHandlePointer);
ipHandle = new IntPtr(ipHandlePointer.ToInt64() + 8);
}
else
{
lHandleCount = Marshal.ReadInt32(ipHandlePointer);
ipHandle = new IntPtr(ipHandlePointer.ToInt32() + 4);
}
}
finally
{
Marshal.FreeHGlobal(ipHandlePointer);
}
var lstHandles = new List<Win32API.SYSTEM_HANDLE_INFORMATION>();
for (long lIndex = 0; lIndex < lHandleCount; lIndex++)
{
var shHandle = new Win32API.SYSTEM_HANDLE_INFORMATION();
if (Is64Bits())
{
shHandle = (Win32API.SYSTEM_HANDLE_INFORMATION)Marshal.PtrToStructure(ipHandle, shHandle.GetType());
ipHandle = new IntPtr(ipHandle.ToInt64() + Marshal.SizeOf(shHandle) + 8);
}
else
{
ipHandle = new IntPtr(ipHandle.ToInt64() + Marshal.SizeOf(shHandle));
shHandle = (Win32API.SYSTEM_HANDLE_INFORMATION)Marshal.PtrToStructure(ipHandle, shHandle.GetType());
}
if (shHandle.ProcessID != process.Id) continue;
lstHandles.Add(shHandle);
}
return lstHandles;
}
private static bool Is64Bits()
{
return Marshal.SizeOf(typeof(IntPtr)) == 8;
}
internal class Win32API
{
[DllImport("ntdll.dll")]
public static extern int NtQueryObject(IntPtr ObjectHandle, int
ObjectInformationClass, IntPtr ObjectInformation, int ObjectInformationLength,
ref int returnLength);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern uint QueryDosDevice(string lpDeviceName, StringBuilder lpTargetPath, int ucchMax);
[DllImport("ntdll.dll")]
public static extern uint NtQuerySystemInformation(int
SystemInformationClass, IntPtr SystemInformation, int SystemInformationLength,
ref int returnLength);
[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll")]
public static extern int CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DuplicateHandle(IntPtr hSourceProcessHandle,
ushort hSourceHandle, IntPtr hTargetProcessHandle, out IntPtr lpTargetHandle,
uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwOptions);
[DllImport("kernel32.dll")]
public static extern IntPtr GetCurrentProcess();
public enum ObjectInformationClass
{
ObjectBasicInformation = 0,
ObjectNameInformation = 1,
ObjectTypeInformation = 2,
ObjectAllTypesInformation = 3,
ObjectHandleInformation = 4
}
[Flags]
public enum ProcessAccessFlags : uint
{
All = 0x001F0FFF,
Terminate = 0x00000001,
CreateThread = 0x00000002,
VMOperation = 0x00000008,
VMRead = 0x00000010,
VMWrite = 0x00000020,
DupHandle = 0x00000040,
SetInformation = 0x00000200,
QueryInformation = 0x00000400,
Synchronize = 0x00100000
}
[StructLayout(LayoutKind.Sequential)]
public struct OBJECT_BASIC_INFORMATION
{ // Information Class 0
public int Attributes;
public int GrantedAccess;
public int HandleCount;
public int PointerCount;
public int PagedPoolUsage;
public int NonPagedPoolUsage;
public int Reserved1;
public int Reserved2;
public int Reserved3;
public int NameInformationLength;
public int TypeInformationLength;
public int SecurityDescriptorLength;
public System.Runtime.InteropServices.ComTypes.FILETIME CreateTime;
}
[StructLayout(LayoutKind.Sequential)]
public struct OBJECT_TYPE_INFORMATION
{ // Information Class 2
public UNICODE_STRING Name;
public int ObjectCount;
public int HandleCount;
public int Reserved1;
public int Reserved2;
public int Reserved3;
public int Reserved4;
public int PeakObjectCount;
public int PeakHandleCount;
public int Reserved5;
public int Reserved6;
public int Reserved7;
public int Reserved8;
public int InvalidAttributes;
public GENERIC_MAPPING GenericMapping;
public int ValidAccess;
public byte Unknown;
public byte MaintainHandleDatabase;
public int PoolType;
public int PagedPoolUsage;
public int NonPagedPoolUsage;
}
[StructLayout(LayoutKind.Sequential)]
public struct OBJECT_NAME_INFORMATION
{ // Information Class 1
public UNICODE_STRING Name;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct UNICODE_STRING
{
public ushort Length;
public ushort MaximumLength;
public IntPtr Buffer;
}
[StructLayout(LayoutKind.Sequential)]
public struct GENERIC_MAPPING
{
public int GenericRead;
public int GenericWrite;
public int GenericExecute;
public int GenericAll;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SYSTEM_HANDLE_INFORMATION
{ // Information Class 16
public int ProcessID;
public byte ObjectTypeNumber;
public byte Flags; // 0x01 = PROTECT_FROM_CLOSE, 0x02 = INHERIT
public ushort Handle;
public int Object_Pointer;
public UInt32 GrantedAccess;
}
public const int MAX_PATH = 260;
public const uint STATUS_INFO_LENGTH_MISMATCH = 0xC0000004;
public const int DUPLICATE_SAME_ACCESS = 0x2;
public const uint FILE_SEQUENTIAL_ONLY = 0x00000004;
}
#endregion
}
}
@hypercube-software
Copy link

according to what is said here: http://forum.sysinternals.com/howto-enumerate-handles_topic18892.html
you should check if GrantedAccess is 0x0012019f before calling NtQueryObject. It's seems to hang for named pipes. In this way, no need to use Threads.

@Walkman100
Copy link

@hypercube-software From my testing only handles with GrantedAccess == 0x001a019f hang - and I also managed to narrow it down a bit to Flags == 2, with only a few false positives that don't actually cause it to hang. I couldn't find anything else in common, not that I know what I'm doing...

I started with the code in this answer (I believe it's similar but without the threading): https://stackoverflow.com/a/13735033/2999220
and checked stuff until it completed successfully - with some output:
https://pastebin.com/90h7Gk2r

it only outputs a line if it finds GrantedAccess == 0x001a019f or 0x0012019f - note the difference between 1a and 12,
only outputs the Flags value if GrantedAccess is 0x001a019f,
and only skips when GrantedAccess == 0x001a019f and Flags == 2 (as you can see from the output).

@i-e-b
Copy link
Author

i-e-b commented Apr 6, 2020

This code is old and buggy. There are much better ways of doing it.
Only use this if you need to support Windows XP for some reason.

https://stackoverflow.com/questions/860656/using-c-how-does-one-figure-out-what-process-locked-a-file/3504251#3504251

@Walkman100
Copy link

@i-e-b While it might be old and buggy, using the RestartManager method doesn't find all a file's locks. I've refactored this method, including some other code from other answers and my research is here: Walkman100/FileLocks

Specifically, the refactoring is here: Walkman100/FileLocks/WalkmanLib.SystemHandles.cs

I've added a DevicePathToDosPath method, and added WinAPI structs that NTQueryObject returns, so you can get strings with MarshalAs instead of converting a pointer address...

@hypercube-software Ok so after understanding more about the code I was using I saw there was already a section ignoring 0x0012019f, the ones I found were additional - see all my ignores from this line

@i-e-b
Copy link
Author

i-e-b commented Apr 27, 2020

Thanks for all your effort @Walkman100
I've added your link to the top of this Gist and to the Stackoverflow answer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment