-
-
Save i-e-b/2290426 to your computer and use it in GitHub Desktop.
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 | |
} | |
} |
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.
@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
Thanks for all your effort @Walkman100
I've added your link to the top of this Gist and to the Stackoverflow answer.
@hypercube-software From my testing only handles with
GrantedAccess == 0x001a019f
hang - and I also managed to narrow it down a bit toFlags == 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
or0x0012019f
- note the difference between1a
and12
,only outputs the
Flags
value ifGrantedAccess
is0x001a019f
,and only skips when
GrantedAccess == 0x001a019f
andFlags == 2
(as you can see from the output).