How to Enumerate All Open Handles for All Processes on a Windows Machine

Have you ever wanted to walk through all of the open handles across all processes that are active on a Windows machine and even be able to do things like close specific handles that those processes have open? I sure have, and I did…but more specifically for my needs I wanted to know what processes had file handles open to specific files on my machine, or what processes had locked specific folders, and so forth. The following code snippet I am going to share is working code, complete with a nice unmanaged library wrapper in case you want to use it through the CLR with your favourite C# projects. It calls on some very low level APIs. These APIs are not your run of the mill, Windows application-friendly type of APIs, so be warned that like all other code on my site, you are responsible for what you do with it. I take no responsibility for any damages. If you happen to bring open handles to a close, that could result in a system-wide catastrophe, the results of which are completely unpredictable relative to what process had that handle open.

UnmanagedLibrary.h:

#include <windows.h>
#include <TlHelp32.h>
#include <iostream>
#include <sstream>
#include <map>
#include <vector>

using namespace std;

void CheckForLocks(wstring fullPath, vector<unsigned long> *processes);
bool CloseHandles(wstring fullPath, int process);

UnmanagedLibrary.cpp:

#include "UnmanagedLibrary.h"

#define NT_SUCCESS(x) ((x) >= 0)
#define STATUS_INFO_LENGTH_MISMATCH 0xc0000004
#define SystemHandleInformation 16
#define ObjectBasicInformation 0
#define ObjectNameInformation 1
#define ObjectTypeInformation 2

typedef NTSTATUS(NTAPI *_NtQuerySystemInformation)(ULONG SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength);
typedef NTSTATUS(NTAPI *_NtDuplicateObject)(HANDLE SourceProcessHandle, HANDLE SourceHandle, HANDLE TargetProcessHandle, PHANDLE TargetHandle, ACCESS_MASK DesiredAccess, ULONG Attributes, ULONG Options);
typedef NTSTATUS(NTAPI *_NtQueryObject)(HANDLE ObjectHandle, ULONG ObjectInformationClass, PVOID ObjectInformation, ULONG ObjectInformationLength, PULONG ReturnLength);

typedef struct _UNICODE_STRING
{
	USHORT Length;
	USHORT MaximumLength;
	PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

typedef struct _SYSTEM_HANDLE
{
	ULONG ProcessId;
	BYTE ObjectTypeNumber;
	BYTE Flags;
	USHORT Handle;
	PVOID Object;
	ACCESS_MASK GrantedAccess;
} SYSTEM_HANDLE, *PSYSTEM_HANDLE;

typedef struct _SYSTEM_HANDLE_INFORMATION
{
	ULONG HandleCount;
	SYSTEM_HANDLE Handles[1];
} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;

typedef enum _POOL_TYPE
{
	NonPagedPool,
	PagedPool,
	NonPagedPoolMustSucceed,
	DontUseThisType,
	NonPagedPoolCacheAligned,
	PagedPoolCacheAligned,
	NonPagedPoolCacheAlignedMustS
} POOL_TYPE, *PPOOL_TYPE;

typedef struct _OBJECT_TYPE_INFORMATION
{
	UNICODE_STRING Name;
	ULONG TotalNumberOfObjects;
	ULONG TotalNumberOfHandles;
	ULONG TotalPagedPoolUsage;
	ULONG TotalNonPagedPoolUsage;
	ULONG TotalNamePoolUsage;
	ULONG TotalHandleTableUsage;
	ULONG HighWaterNumberOfObjects;
	ULONG HighWaterNumberOfHandles;
	ULONG HighWaterPagedPoolUsage;
	ULONG HighWaterNonPagedPoolUsage;
	ULONG HighWaterNamePoolUsage;
	ULONG HighWaterHandleTableUsage;
	ULONG InvalidAttributes;
	GENERIC_MAPPING GenericMapping;
	ULONG ValidAccess;
	BOOLEAN SecurityRequired;
	BOOLEAN MaintainHandleCount;
	USHORT MaintainTypeList;
	POOL_TYPE PoolType;
	ULONG PagedPoolUsage;
	ULONG NonPagedPoolUsage;
} OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION;

using namespace std;

PVOID GetLibraryProcAddress(PSTR LibraryName, PSTR ProcName)
{
	return GetProcAddress(GetModuleHandleA(LibraryName), ProcName);
}

_NtQuerySystemInformation NtQuerySystemInformation = (_NtQuerySystemInformation)GetLibraryProcAddress("ntdll.dll", "NtQuerySystemInformation");
_NtDuplicateObject NtDuplicateObject = (_NtDuplicateObject)GetLibraryProcAddress("ntdll.dll", "NtDuplicateObject");
_NtQueryObject NtQueryObject = (_NtQueryObject)GetLibraryProcAddress("ntdll.dll", "NtQueryObject");

struct QueryStructure
{
	HANDLE dupHandle;
	PVOID objectNameInfo;
	ULONG objectInfoLength;
	ULONG returnLength;
	NTSTATUS result;
};

HANDLE beginQuery = CreateEvent(0, FALSE, FALSE, 0);
HANDLE endQuery = CreateEvent(0, FALSE, FALSE, 0);
QueryStructure queryStructure;

HANDLE beginQueryCloseHandle = CreateEvent(0, FALSE, FALSE, 0);
HANDLE endQueryCloseHandle = CreateEvent(0, FALSE, FALSE, 0);
QueryStructure queryStructureCloseHandle;

DWORD WINAPI queryThread(LPVOID parameter)
{
	while (WaitForSingleObject(beginQuery, INFINITE) == WAIT_OBJECT_0)
	{
		queryStructure.result = NtQueryObject(queryStructure.dupHandle, ObjectNameInformation, queryStructure.objectNameInfo, queryStructure.objectInfoLength, &queryStructure.returnLength);
		SetEvent(endQuery);
	}
	return 0;
}

DWORD WINAPI queryThreadCloseHandle(LPVOID parameter)
{
	while (WaitForSingleObject(beginQueryCloseHandle, INFINITE) == WAIT_OBJECT_0)
	{
		queryStructureCloseHandle.result = NtQueryObject(queryStructureCloseHandle.dupHandle, ObjectNameInformation, queryStructureCloseHandle.objectNameInfo, queryStructureCloseHandle.objectInfoLength, &queryStructureCloseHandle.returnLength);
		SetEvent(endQueryCloseHandle);
	}
	return 0;
}

HANDLE queryThreadHandle = CreateThread(0, 0, &queryThread, 0, 0, 0);
HANDLE queryThreadCloseHandleHandle = CreateThread(0, 0, &queryThreadCloseHandle, 0, 0, 0);

void ConvertPath(wstring *path, map<wstring, wstring> *volumes)
{
	for (map<wstring, wstring>::iterator i = (*volumes).begin(); i != (*volumes).end(); ++i)
	{
		if ((*path).compare(0, (*i).first.size(), (*i).first) == 0)
		{
			*path = (*path).replace(0, (*i).first.size(), (*i).second);
			break;
		}
	}
}

void CheckForLocks(wstring fullPath, vector<unsigned long> *processes)
{
	DWORD	CharCount = 0;
	WCHAR	DeviceName[MAX_PATH] = L"";
	HANDLE	FindHandle = INVALID_HANDLE_VALUE;
	size_t	Index = 0;
	BOOL	Success = FALSE;
	WCHAR	VolumeName[MAX_PATH] = L"";
	map<wstring, wstring> volumes;
	FindHandle = FindFirstVolumeW(VolumeName, ARRAYSIZE(VolumeName));
	while (true)
	{
		Index = wcslen(VolumeName) - 1;
		if (VolumeName[0] != L'\\' || VolumeName[1] != L'\\' || VolumeName[2] != L'?' || VolumeName[3] != L'\\' || VolumeName[Index] != L'\\')
		{
			break;
		}
		VolumeName[Index] = L'\0';
		CharCount = QueryDosDeviceW(&VolumeName[4], DeviceName, ARRAYSIZE(DeviceName));
		VolumeName[Index] = L'\\';
		if (CharCount == 0)
		{
			break;
		}
		DWORD	size = MAX_PATH + 1;
		PWCHAR	name = NULL;
		BOOL	success = FALSE;
		while (!success)
		{
			name = (PWCHAR) new BYTE[size * sizeof(WCHAR)];
			success = GetVolumePathNamesForVolumeNameW(VolumeName, name, size, &size);
			if (!success)
			{
				delete[] name;
				name = NULL;
			}
		}
		volumes[DeviceName + wstring(L"\\")] = name;
		if (name != NULL)
		{
			delete[] name;
			name = NULL;
		}
		Success = FindNextVolumeW(FindHandle, VolumeName, ARRAYSIZE(VolumeName));
		if (!Success)
		{
			if (GetLastError() != ERROR_NO_MORE_FILES)
			{
				break;
			}
			break;
		}
	}
	FindVolumeClose(FindHandle);
	FindHandle = INVALID_HANDLE_VALUE;
	NTSTATUS status;
	PSYSTEM_HANDLE_INFORMATION handleInfo;
	ULONG handleInfoSize = 0x10000;
	HANDLE processHandle;
	ULONG i;
	DWORD pid;
	HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	PROCESSENTRY32 process;
	ZeroMemory(&process, sizeof(process));
	process.dwSize = sizeof(process);
	if (Process32First(snapshot, &process))
	{
		do
		{
			pid = process.th32ProcessID;
			if (!(processHandle = OpenProcess(PROCESS_DUP_HANDLE, FALSE, pid)))
			{
				continue;
			}
			handleInfo = (PSYSTEM_HANDLE_INFORMATION)malloc(handleInfoSize);
			while ((status = NtQuerySystemInformation(SystemHandleInformation, handleInfo, handleInfoSize, NULL)) == STATUS_INFO_LENGTH_MISMATCH)
			{
				handleInfo = (PSYSTEM_HANDLE_INFORMATION)realloc(handleInfo, handleInfoSize *= 2);
			}
			if (!NT_SUCCESS(status))
			{
				return;
			}
			for (i = 0; i < handleInfo->HandleCount; i++)
			{
				SYSTEM_HANDLE handle = handleInfo->Handles[i];
				HANDLE dupHandle = NULL;
				POBJECT_TYPE_INFORMATION objectTypeInfo;
				PVOID objectNameInfo;
				UNICODE_STRING objectName;
				ULONG returnLength = 0;
				if (handle.ProcessId != pid)
					continue;
				if (!NT_SUCCESS(NtDuplicateObject(processHandle, (HANDLE)handle.Handle, GetCurrentProcess(), &dupHandle, 0, 0, 0)))
				{
					continue;
				}
				objectTypeInfo = (POBJECT_TYPE_INFORMATION)malloc(0x1000);
				size_t currentSize = 0x1000;
				objectNameInfo = malloc(currentSize);
				queryStructure.dupHandle = dupHandle;
				queryStructure.objectNameInfo = objectNameInfo;
				queryStructure.objectInfoLength = 0x1000;
				queryStructure.returnLength = returnLength;
				queryStructure.result = -1;
				SetEvent(beginQuery);
				if (WaitForSingleObject(endQuery, 100) == WAIT_TIMEOUT)
				{
					TerminateThread(queryThreadHandle, 1);
					CloseHandle(queryThreadHandle);
					queryThreadHandle = CreateThread(0, 0, &queryThread, 0, 0, 0);
					CloseHandle(dupHandle);
					continue;
				}
				if (!NT_SUCCESS(queryStructure.result))
				{
					objectNameInfo = realloc(objectNameInfo, currentSize *= 2);
					if (!NT_SUCCESS(NtQueryObject(dupHandle, ObjectNameInformation, objectNameInfo, returnLength, NULL)))
					{
						free(objectTypeInfo);
						free(objectNameInfo);
						CloseHandle(dupHandle);
						continue;
					}
				}
				objectName = *(PUNICODE_STRING)objectNameInfo;
				if (objectName.Length)
				{
					wstring objectNameAsWString = wstring(objectName.Buffer, objectName.Length / sizeof(WCHAR));
					ConvertPath(&objectNameAsWString, &volumes);
					if ((int)objectNameAsWString.find(fullPath) >= 0)
					{
						(*processes).push_back(pid);
						free(objectTypeInfo);
						free(objectNameInfo);
						CloseHandle(dupHandle);
						break;
					}
				}
				free(objectTypeInfo);
				free(objectNameInfo);
				CloseHandle(dupHandle);
			}
			free(handleInfo);
			CloseHandle(processHandle);
		} while (Process32Next(snapshot, &process));
	}
}

bool CloseHandles(wstring fullPath, int process)
{
	DWORD	CharCount = 0;
	WCHAR	DeviceName[MAX_PATH] = L"";
	HANDLE	FindHandle = INVALID_HANDLE_VALUE;
	size_t	Index = 0;
	BOOL	Success = FALSE;
	WCHAR	VolumeName[MAX_PATH] = L"";
	map<wstring, wstring> volumes;
	FindHandle = FindFirstVolumeW(VolumeName, ARRAYSIZE(VolumeName));
	while (true)
	{
		Index = wcslen(VolumeName) - 1;
		if (VolumeName[0] != L'\\' || VolumeName[1] != L'\\' || VolumeName[2] != L'?' || VolumeName[3] != L'\\' || VolumeName[Index] != L'\\')
		{
			break;
		}
		VolumeName[Index] = L'\0';
		CharCount = QueryDosDeviceW(&VolumeName[4], DeviceName, ARRAYSIZE(DeviceName));
		VolumeName[Index] = L'\\';
		if (CharCount == 0)
		{
			break;
		}
		DWORD	size = MAX_PATH + 1;
		PWCHAR	name = NULL;
		BOOL	success = FALSE;
		while (!success)
		{
			name = (PWCHAR) new BYTE[size * sizeof(WCHAR)];
			success = GetVolumePathNamesForVolumeNameW(VolumeName, name, size, &size);
			if (!success)
			{
				delete[] name;
				name = NULL;
			}
		}
		volumes[DeviceName + wstring(L"\\")] = name;
		if (name != NULL)
		{
			delete[] name;
			name = NULL;
		}
		Success = FindNextVolumeW(FindHandle, VolumeName, ARRAYSIZE(VolumeName));
		if (!Success)
		{
			if (GetLastError() != ERROR_NO_MORE_FILES)
			{
				break;
			}
			break;
		}
	}
	FindVolumeClose(FindHandle);
	FindHandle = INVALID_HANDLE_VALUE;
	_NtQuerySystemInformation NtQuerySystemInformation = (_NtQuerySystemInformation)GetLibraryProcAddress("ntdll.dll", "NtQuerySystemInformation");
	_NtDuplicateObject NtDuplicateObject = (_NtDuplicateObject)GetLibraryProcAddress("ntdll.dll", "NtDuplicateObject");
	_NtQueryObject NtQueryObject = (_NtQueryObject)GetLibraryProcAddress("ntdll.dll", "NtQueryObject");
	NTSTATUS status;
	PSYSTEM_HANDLE_INFORMATION handleInfo;
	ULONG handleInfoSize = 0x10000;
	HANDLE processHandle;
	ULONG i;
	if (!(processHandle = OpenProcess(PROCESS_DUP_HANDLE, FALSE, process)))
	{
		return false;
	}
	handleInfo = (PSYSTEM_HANDLE_INFORMATION)malloc(handleInfoSize);
	while ((status = NtQuerySystemInformation(SystemHandleInformation, handleInfo, handleInfoSize, NULL)) == STATUS_INFO_LENGTH_MISMATCH)
	{
		handleInfo = (PSYSTEM_HANDLE_INFORMATION)realloc(handleInfo, handleInfoSize *= 2);
	}
	if (!NT_SUCCESS(status))
	{
		return false;
	}
	for (i = 0; i < handleInfo->HandleCount; i++)
	{
		SYSTEM_HANDLE handle = handleInfo->Handles[i];
		HANDLE dupHandle = NULL;
		POBJECT_TYPE_INFORMATION objectTypeInfo;
		PVOID objectNameInfo;
		UNICODE_STRING objectName;
		ULONG returnLength = 0;
		if (handle.ProcessId != process)
			continue;
		if (!NT_SUCCESS(NtDuplicateObject(processHandle, (HANDLE)handle.Handle, GetCurrentProcess(), &dupHandle, 0, 0, 0)))
		{
			continue;
		}
		objectTypeInfo = (POBJECT_TYPE_INFORMATION)malloc(0x1000);
		size_t currentSize = 0x1000;
		objectNameInfo = malloc(currentSize);
		queryStructureCloseHandle.dupHandle = dupHandle;
		queryStructureCloseHandle.objectNameInfo = objectNameInfo;
		queryStructureCloseHandle.objectInfoLength = 0x1000;
		queryStructureCloseHandle.returnLength = returnLength;
		queryStructureCloseHandle.result = -1;
		SetEvent(beginQueryCloseHandle);
		if (WaitForSingleObject(endQueryCloseHandle, 100) == WAIT_TIMEOUT)
		{
			TerminateThread(queryThreadCloseHandleHandle, 1);
			CloseHandle(queryThreadCloseHandleHandle);
			queryThreadCloseHandleHandle = CreateThread(0, 0, &queryThreadCloseHandle, 0, 0, 0);
			CloseHandle(dupHandle);
			continue;
		}
		if (!NT_SUCCESS(queryStructureCloseHandle.result))
		{
			objectNameInfo = realloc(objectNameInfo, currentSize *= 2);
			if (!NT_SUCCESS(NtQueryObject(dupHandle, ObjectNameInformation, objectNameInfo, returnLength, NULL)))
			{
				free(objectTypeInfo);
				free(objectNameInfo);
				CloseHandle(dupHandle);
				continue;
			}
		}
		objectName = *(PUNICODE_STRING)objectNameInfo;
		if (objectName.Length)
		{
			wstring objectNameAsWString = wstring(objectName.Buffer, objectName.Length / sizeof(WCHAR));
			ConvertPath(&objectNameAsWString, &volumes);
			if ((int)objectNameAsWString.find(fullPath) >= 0)
			{
				NtDuplicateObject(processHandle, (HANDLE)handle.Handle, 0, 0, 0, 0, 1);
			}
		}
		free(objectTypeInfo);
		free(objectNameInfo);
		CloseHandle(dupHandle);
	}
	free(handleInfo);
	CloseHandle(processHandle);
	return true;
}

Note: The two most important methods above are firstly, CheckForLocks, which takes either a system drive path, file path, or folder path, and returns a vector of processes with open handles at the given path, and secondly CloseHandles, which takes the same types of paths and a process ID. CloseHandles closes any open handles (containing the specified path) of a target process via a specified process ID. Below, I will even include some code to produce a CLR wrapper around this Static Library so that you can call it from C#.

Unmanaged.h:

#include "..\UnmanagedLibrary\UnmanagedLibrary.h"

using namespace System;
using namespace System::Collections::Generic;

namespace Unmanaged
{
	public ref class Handles
	{
	public:
		static List<int>^ GetProcessesWithOpenHandlesToPath(String^ path);
		static bool CloseOpenHandlesToPathInProcess(String^ path, int process);
	};
}

Unmanaged.cpp:

#include "Unmanaged.h"
#include "msclr\marshal_cppstd.h"

List<int>^ Unmanaged::Handles::GetProcessesWithOpenHandlesToPath(String^ path)
{
	List<int>^ list = gcnew List<int>();
	wstring convertedString = msclr::interop::marshal_as<wstring>(path);
	vector<unsigned long> processes;
	CheckForLocks(convertedString, &processes);
	for (vector<unsigned long>::iterator i = processes.begin(); i != processes.end(); i++) {
		list->Add(*i);
	}
	return list;
}

bool Unmanaged::Handles::CloseOpenHandlesToPathInProcess(String^ path, int process)
{
	List<int>^ list = gcnew List<int>();
	wstring convertedString = msclr::interop::marshal_as<wstring>(path);
	return CloseHandles(convertedString, process);
}

From C#, you simply call these two functions of the Unmanaged DLL:

List<int> processes = Unmanaged.Handles.GetProcessesWithOpenHandlesToPath(path);
Unmanaged.Handles.CloseOpenHandlesToPathInProcess(path, processID);

If you study the code well, you will realize that the only specialty item here is that you need to terminate threads while you enumerate handles in order to stop deadlocks from happening. There is one special, odd, weird (whatever you want to call it) case in which deadlocks happen, and that is when you call NtQueryObject on handles which reference synchronous file objects (chances are that the file object was opened by a kernel-mode driver with FILE_SYNCHRONOUS_IO_NONALERT). The thread will wait indefinitely to acquire the file lock because interrupting the wait isn’t possible, which means you have to terminate the thread.

Alexandru

"To avoid criticism, say nothing, do nothing, be nothing." - Aristotle

"It is wise to direct your anger towards problems - not people; to focus your energies on answers - not excuses." - William Arthur Ward

"Science does not know its debt to imagination." - Ralph Waldo Emerson

"Money was never a big motivation for me, except as a way to keep score. The real excitement is playing the game." - Donald Trump

"All our dreams can come true, if we have the courage to pursue them." - Walt Disney

"Mitch flashes back to a basketball game held in the Brandeis University gymnasium in 1979. The team is doing well and chants, 'We're number one!' Morrie stands and shouts, 'What's wrong with being number two?' The students fall silent." - Tuesdays with Morrie

I'm not entirely sure what makes me successful in general programming or development, but to any newcomers to this blood-sport, my best guess would be that success in programming comes from some strange combination of interest, persistence, patience, instincts (for example, someone might tell you that something can't be done, or that it can't be done a certain way, but you just know that can't be true, or you look at a piece of code and know something doesn't seem right with it at first glance, but you can't quite put your finger on it until you think it through some more), fearlessness of tinkering, and an ability to take advice because you should be humble. Its okay to be wrong or to have a bad approach, realize it, and try to find a better one, and even better to be wrong and find a better approach to solve something than to have had a bad approach to begin with. I hope that whatever fragments of information I sprinkle across here help those who hit the same roadblocks.

3 thoughts on “How to Enumerate All Open Handles for All Processes on a Windows Machine

    1. By the way, if you already know the path to the file, and do not need to support WinXP, then you should use Restart Manager – https://msdn.microsoft.com/en-us/library/windows/desktop/cc948910(v=vs.85).aspx

      My issue though, was that I had a PID and needed to get all the files it was locking. Restart Manager does not do that, so I had to use this handle list techqniue, then duplicate the handle, then use GetFileType and then NtQueryObject to get the path.

Leave a Reply

Your email address will not be published. Required fields are marked *