What's the easiest way to get the filename associated with an open HANDLE in Win32?
7 Answers
I tried the code posted by Mehrdad here. It works, but with limitations:
- It should not be used for network shares because the MountPointManager may hang for a very long time.
- It uses undocumented API (IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH) I don't like that very much
- It does not support USB devices that create virtual COM ports (I need that in my project)
I also studied other approaches like GetFileInformationByHandleEx()
and GetFinalPathNameByHandle()
, but these are useless as they return only Path + Filename but without drive. Additionally GetFinalPathNameByHandle()
also has the hanging bug.
The GetMappedFileName()
approach in the MSDN (posted by Max here) is also very limited:
- It works only with real files
- The file size must not be zero bytes
- Directories, Network and COM ports are not supported
- The code is clumsy
So I wrote my own code. I tested it on Win XP and on Win 7, 8, and 10. It works perfectly.
NOTE: You do NOT need any additional LIB file to compile this code!
CPP FILE:
t_NtQueryObject NtQueryObject()
{
static t_NtQueryObject f_NtQueryObject = NULL;
if (!f_NtQueryObject)
{
HMODULE h_NtDll = GetModuleHandle(L"Ntdll.dll"); // Ntdll is loaded into EVERY process!
f_NtQueryObject = (t_NtQueryObject)GetProcAddress(h_NtDll, "NtQueryObject");
}
return f_NtQueryObject;
}
// returns
// "\Device\HarddiskVolume3" (Harddisk Drive)
// "\Device\HarddiskVolume3\Temp" (Harddisk Directory)
// "\Device\HarddiskVolume3\Temp\transparent.jpeg" (Harddisk File)
// "\Device\Harddisk1\DP(1)0-0+6\foto.jpg" (USB stick)
// "\Device\TrueCryptVolumeP\Data\Passwords.txt" (Truecrypt Volume)
// "\Device\Floppy0\Autoexec.bat" (Floppy disk)
// "\Device\CdRom1\VIDEO_TS\VTS_01_0.VOB" (DVD drive)
// "\Device\Serial1" (real COM port)
// "\Device\USBSER000" (virtual COM port)
// "\Device\Mup\ComputerName\C$\Boot.ini" (network drive share, Windows 7)
// "\Device\LanmanRedirector\ComputerName\C$\Boot.ini" (network drive share, Windwos XP)
// "\Device\LanmanRedirector\ComputerName\Shares\Dance.m3u" (network folder share, Windwos XP)
// "\Device\Afd" (internet socket)
// "\Device\Console000F" (unique name for any Console handle)
// "\Device\NamedPipe\Pipename" (named pipe)
// "\BaseNamedObjects\Objectname" (named mutex, named event, named semaphore)
// "\REGISTRY\MACHINE\SOFTWARE\Classes\.txt" (HKEY_CLASSES_ROOT\.txt)
DWORD GetNtPathFromHandle(HANDLE h_File, CString* ps_NTPath)
{
if (h_File == 0 || h_File == INVALID_HANDLE_VALUE)
return ERROR_INVALID_HANDLE;
// NtQueryObject() returns STATUS_INVALID_HANDLE for Console handles
if (IsConsoleHandle(h_File))
{
ps_NTPath->Format(L"\\Device\\Console%04X", (DWORD)(DWORD_PTR)h_File);
return 0;
}
BYTE u8_Buffer[2000];
DWORD u32_ReqLength = 0;
UNICODE_STRING* pk_Info = &((OBJECT_NAME_INFORMATION*)u8_Buffer)->Name;
pk_Info->Buffer = 0;
pk_Info->Length = 0;
// IMPORTANT: The return value from NtQueryObject is bullshit! (driver bug?)
// - The function may return STATUS_NOT_SUPPORTED although it has successfully written to the buffer.
// - The function returns STATUS_SUCCESS although h_File == 0xFFFFFFFF
NtQueryObject()(h_File, ObjectNameInformation, u8_Buffer, sizeof(u8_Buffer), &u32_ReqLength);
// On error pk_Info->Buffer is NULL
if (!pk_Info->Buffer || !pk_Info->Length)
return ERROR_FILE_NOT_FOUND;
pk_Info->Buffer[pk_Info->Length /2] = 0; // Length in Bytes!
*ps_NTPath = pk_Info->Buffer;
return 0;
}
// converts
// "\Device\HarddiskVolume3" -> "E:"
// "\Device\HarddiskVolume3\Temp" -> "E:\Temp"
// "\Device\HarddiskVolume3\Temp\transparent.jpeg" -> "E:\Temp\transparent.jpeg"
// "\Device\Harddisk1\DP(1)0-0+6\foto.jpg" -> "I:\foto.jpg"
// "\Device\TrueCryptVolumeP\Data\Passwords.txt" -> "P:\Data\Passwords.txt"
// "\Device\Floppy0\Autoexec.bat" -> "A:\Autoexec.bat"
// "\Device\CdRom1\VIDEO_TS\VTS_01_0.VOB" -> "H:\VIDEO_TS\VTS_01_0.VOB"
// "\Device\Serial1" -> "COM1"
// "\Device\USBSER000" -> "COM4"
// "\Device\Mup\ComputerName\C$\Boot.ini" -> "\\ComputerName\C$\Boot.ini"
// "\Device\LanmanRedirector\ComputerName\C$\Boot.ini" -> "\\ComputerName\C$\Boot.ini"
// "\Device\LanmanRedirector\ComputerName\Shares\Dance.m3u" -> "\\ComputerName\Shares\Dance.m3u"
// returns an error for any other device type
DWORD GetDosPathFromNtPath(const WCHAR* u16_NTPath, CString* ps_DosPath)
{
DWORD u32_Error;
if (wcsnicmp(u16_NTPath, L"\\Device\\Serial", 14) == 0 || // e.g. "Serial1"
wcsnicmp(u16_NTPath, L"\\Device\\UsbSer", 14) == 0) // e.g. "USBSER000"
{
HKEY h_Key;
if (u32_Error = RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Hardware\\DeviceMap\\SerialComm", 0, KEY_QUERY_VALUE, &h_Key))
return u32_Error;
WCHAR u16_ComPort[50];
DWORD u32_Type;
DWORD u32_Size = sizeof(u16_ComPort);
if (u32_Error = RegQueryValueEx(h_Key, u16_NTPath, 0, &u32_Type, (BYTE*)u16_ComPort, &u32_Size))
{
RegCloseKey(h_Key);
return ERROR_UNKNOWN_PORT;
}
*ps_DosPath = u16_ComPort;
RegCloseKey(h_Key);
return 0;
}
if (wcsnicmp(u16_NTPath, L"\\Device\\LanmanRedirector\\", 25) == 0) // Win XP
{
*ps_DosPath = L"\\\\";
*ps_DosPath += (u16_NTPath + 25);
return 0;
}
if (wcsnicmp(u16_NTPath, L"\\Device\\Mup\\", 12) == 0) // Win 7
{
*ps_DosPath = L"\\\\";
*ps_DosPath += (u16_NTPath + 12);
return 0;
}
WCHAR u16_Drives[300];
if (!GetLogicalDriveStrings(300, u16_Drives))
return GetLastError();
WCHAR* u16_Drv = u16_Drives;
while (u16_Drv[0])
{
WCHAR* u16_Next = u16_Drv +wcslen(u16_Drv) +1;
u16_Drv[2] = 0; // the backslash is not allowed for QueryDosDevice()
WCHAR u16_NtVolume[1000];
u16_NtVolume[0] = 0;
// may return multiple strings!
// returns very weird strings for network shares
if (!QueryDosDevice(u16_Drv, u16_NtVolume, sizeof(u16_NtVolume) /2))
return GetLastError();
int s32_Len = (int)wcslen(u16_NtVolume);
if (s32_Len > 0 && wcsnicmp(u16_NTPath, u16_NtVolume, s32_Len) == 0)
{
*ps_DosPath = u16_Drv;
*ps_DosPath += (u16_NTPath + s32_Len);
return 0;
}
u16_Drv = u16_Next;
}
return ERROR_BAD_PATHNAME;
}
HEADER FILE:
#pragma warning(disable: 4996) // wcsnicmp deprecated
#include <winternl.h>
// This makro assures that INVALID_HANDLE_VALUE (0xFFFFFFFF) returns FALSE
#define IsConsoleHandle(h) (((((ULONG_PTR)h) & 0x10000003) == 0x3) ? TRUE : FALSE)
enum OBJECT_INFORMATION_CLASS
{
ObjectBasicInformation,
ObjectNameInformation,
ObjectTypeInformation,
ObjectAllInformation,
ObjectDataInformation
};
struct OBJECT_NAME_INFORMATION
{
UNICODE_STRING Name; // defined in winternl.h
WCHAR NameBuffer;
};
typedef NTSTATUS (NTAPI* t_NtQueryObject)(HANDLE Handle, OBJECT_INFORMATION_CLASS Info, PVOID Buffer, ULONG BufferSize, PULONG ReturnLength);

- 7,602
- 3
- 47
- 57
-
First of all, this code needs at least kernel32.lib and advapi32.lib to compile. Seems that Elmue "wanted to say" that this code needs no additional libraries (for example: ntdll.lib) to compile. :) Second, this code is not perfect for all network redirectors (the UNC providers), for example, in case of VirtualBox (host) share (Device\VBoxMiniRdr as a target of (DosDevices\\)VBoxMiniRdrDN symbolic link). :) – Mosc Dec 13 '14 at 01:24
-
3Good answer. This is basically what Process Hacker does (and probably Sysinternals' Process Explorer too) and works for virtually all handle types and not just files. – conio Jun 02 '15 at 13:20
-
Hi there @Elmue this is working splendid! I was wondering if you got a chance to test with Win8, 8.1, or 10 yet? – Noitidart Feb 11 '16 at 06:50
-
"appears to cause memory corruption,..:" "Appears"??? If you do not make the effort to investigate the cause of your problem THOROUGHLY, your comment is completely useless. Probably you made a programming error. I can confirm that my code is working since many years on any Windows Version without any problem. – Elmue Feb 10 '23 at 00:48
-
Now I see that you did not understand what the article that you have linked is saying. What 0xC0000022L is explaining with very much details does not apply at all to my code. My code will never run into this problem. As you can easily see my code uses a FIXED buffer of 2000 bytes instead of asking for the required Buffer size as 0xC0000022L does. The longest string that my code will ever be confronted with is something like "\Device\LanmanRedirector\ComputerName\Shares\Dance.m3u" = 55 Unicode characters = 110 Byte = 5% of the buffer! So the problem described there will never happen. – Elmue Feb 13 '23 at 01:22
-
@Elmue I know it's not the same problem, but it seems that this undocumented function can do strange things, I will delete my previous comments however until I have something more concrete. – Patrick Feb 13 '23 at 02:00
-
One problem I had with `NtQueryObject` is that I had to compile it on the earliest available version of WinDDK for it to actually work on ALL versions of Windows XP+. If not and I compiled it with vc++ or whatever (any toolset) it would crash or return strange values sometimes for CERTAIN handles. Even after this I've found that `NtQueryObject` seems to return `STATUS_OBJECT_PATH_INVALID` for certain exotic handles (such as emulated COM ports) and not populate the name – Patrick Feb 14 '23 at 09:50
-
Everything you write is very vague. First you cite an article talking about a buffer overflow, then you delete your comment. You are surely not a scientific person. Then it depends suddenly on the compiler. This is 100% NONSENSE because NtQueryObject is a simple API call which is COMPLETELY independent of the compiler and does not even need the DDK which is for compiling drivers. I see clearly that you have beginner problems here and that you are not able to investigate the REAL cause of the problem that you have. You suppose this, then you suppose that, but everything without any proof. – Elmue Feb 14 '23 at 12:46
-
And if you would have read the comments in my code you would have seen that the return value of NtQueryObject is nonsense. Therefore I ignore it. – Elmue Feb 14 '23 at 12:47
-
@Elmue I deleted my comments because of your rather extreme reactions to them. I am putting my findings here in case other people need them, since you are unhelpful. Ignore them if you want, I put them here for other people. Ignoring the return value does not help (as stated), the name value is still not populated in these cases. Stating that all API calls are absolutely independent of the compiler is more unscientific than anything I have said. – Patrick Feb 14 '23 at 23:09
-
API calls are STDCALL which is strictly defined since Windows 95. There is no compiler which would not be able to compile this correctly. And apart from you I have NEVER heard the abstruse theory in my 30 years as a programmer. If API calls WOULD be compiler dependent this would be a heavy problem! Apart: You do not put any "findings" here. You just put vague theories which have no proof at all. Post a code which can 100% sure reproduce the problem. Otherwise your comments do not help anybody. I posted this code 10 years ago. Why are you the only person having these strange problems? – Elmue Feb 15 '23 at 23:23
-
This method works on most handles but not all. For an emulated COM port I encountered (see above comment), ProcessHacker (which also uses `NtQueryObject` in user mode) only can retrieve the name using it's kernel mode driver. – Patrick Feb 23 '23 at 04:37
There is a correct (although undocumented) way to do this on Windows XP which also works with directories -- the same method GetFinalPathNameByHandle uses on Windows Vista and later.
Here are the eneded declarations. Some of these are already in WInternl.h
and MountMgr.h
but I just put them here anyway:
#include "stdafx.h"
#include <Windows.h>
#include <assert.h>
enum OBJECT_INFORMATION_CLASS { ObjectNameInformation = 1 };
enum FILE_INFORMATION_CLASS { FileNameInformation = 9 };
struct FILE_NAME_INFORMATION { ULONG FileNameLength; WCHAR FileName[1]; };
struct IO_STATUS_BLOCK { PVOID Dummy; ULONG_PTR Information; };
struct UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; };
struct MOUNTMGR_TARGET_NAME { USHORT DeviceNameLength; WCHAR DeviceName[1]; };
struct MOUNTMGR_VOLUME_PATHS { ULONG MultiSzLength; WCHAR MultiSz[1]; };
extern "C" NTSYSAPI NTSTATUS NTAPI NtQueryObject(IN HANDLE Handle OPTIONAL,
IN OBJECT_INFORMATION_CLASS ObjectInformationClass,
OUT PVOID ObjectInformation OPTIONAL, IN ULONG ObjectInformationLength,
OUT PULONG ReturnLength OPTIONAL);
extern "C" NTSYSAPI NTSTATUS NTAPI NtQueryInformationFile(IN HANDLE FileHandle,
OUT PIO_STATUS_BLOCK IoStatusBlock, OUT PVOID FileInformation,
IN ULONG Length, IN FILE_INFORMATION_CLASS FileInformationClass);
#define MOUNTMGRCONTROLTYPE ((ULONG) 'm')
#define IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH \
CTL_CODE(MOUNTMGRCONTROLTYPE, 12, METHOD_BUFFERED, FILE_ANY_ACCESS)
union ANY_BUFFER {
MOUNTMGR_TARGET_NAME TargetName;
MOUNTMGR_VOLUME_PATHS TargetPaths;
FILE_NAME_INFORMATION NameInfo;
UNICODE_STRING UnicodeString;
WCHAR Buffer[USHRT_MAX];
};
Here's the core function:
LPWSTR GetFilePath(HANDLE hFile)
{
static ANY_BUFFER nameFull, nameRel, nameMnt;
ULONG returnedLength; IO_STATUS_BLOCK iosb; NTSTATUS status;
status = NtQueryObject(hFile, ObjectNameInformation,
nameFull.Buffer, sizeof(nameFull.Buffer), &returnedLength);
assert(status == 0);
status = NtQueryInformationFile(hFile, &iosb, nameRel.Buffer,
sizeof(nameRel.Buffer), FileNameInformation);
assert(status == 0);
//I'm not sure how this works with network paths...
assert(nameFull.UnicodeString.Length >= nameRel.NameInfo.FileNameLength);
nameMnt.TargetName.DeviceNameLength = (USHORT)(
nameFull.UnicodeString.Length - nameRel.NameInfo.FileNameLength);
wcsncpy(nameMnt.TargetName.DeviceName, nameFull.UnicodeString.Buffer,
nameMnt.TargetName.DeviceNameLength / sizeof(WCHAR));
HANDLE hMountPointMgr = CreateFile(_T("\\\\.\\MountPointManager"),
0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING, 0, NULL);
__try
{
DWORD bytesReturned;
BOOL success = DeviceIoControl(hMountPointMgr,
IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH, &nameMnt,
sizeof(nameMnt), &nameMnt, sizeof(nameMnt),
&bytesReturned, NULL);
assert(success && nameMnt.TargetPaths.MultiSzLength > 0);
wcsncat(nameMnt.TargetPaths.MultiSz, nameRel.NameInfo.FileName,
nameRel.NameInfo.FileNameLength / sizeof(WCHAR));
return nameMnt.TargetPaths.MultiSz;
}
__finally { CloseHandle(hMountPointMgr); }
}
and here's an example usage:
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hFile = CreateFile(_T("\\\\.\\C:\\Windows\\Notepad.exe"),
0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
assert(hFile != NULL && hFile != INVALID_HANDLE_VALUE);
__try
{
wprintf(L"%s\n", GetFilePath(hFile));
// Prints:
// C:\Windows\notepad.exe
}
__finally { CloseHandle(hFile); }
return 0;
}

- 205,094
- 128
- 528
- 886
-
1
-
4@pmg: I agree, but I didn't choose the names... the Windows API is already like that, I just copy/pasted what I needed here. – user541686 Mar 13 '11 at 02:03
-
@ybungalobill: Do you really need the DDK? I think I copied everything that was outside of the SDK onto here... – user541686 May 30 '11 at 21:09
-
I need the *.libs where NtQueryInformationFile and NtQueryObject are defined. – Yakov Galka May 31 '11 at 05:33
-
@ybungalobill: Oooooh yes good point, you need the DDK for those. I totally forgot about the libraries, sorry. :( – user541686 May 31 '11 at 05:36
-
2Though it's worth mentioning GetProcAddress would circumvent the need for the DDK. – user541686 Jan 16 '13 at 23:38
-
3This code works, but has some limitations. For more details see my posting here. – Elmue Sep 13 '13 at 19:17
edit Thanks for the comments about this being Vista or Server 2008 only. I missed that in the page. Guess I should have read the whole article ;)
It looks like you can use GetFileInformationByHandleEx() to get this information.
You'll likely want to do something like:
GetFileInformationByHandleEx( fileHandle, FILE_NAME_INFO, lpFileInformation, sizeof(FILE_NAME_INFO));
Double check the MSDN page to make sure I haven't misled you too badly :)
Cheers,
Taylor

- 622
- 1
- 8
- 21
-
The only problem with this solution is that GetFileInformationByHandleEx requires Windows Vista or Server 2008 (or later). – ChrisN Sep 15 '08 at 18:39
-
3
-
1It looks simple but it returns only path + filename, but the drive is missing, so not really usefull. – Elmue Sep 13 '13 at 19:15
-
1FILE_NAME_INFO is a variable length data structure with only one entry for the first character. So instead of sizeof(FILE_NAME_INFO), I think you'd want to calculate dwBufferSize as sizeof(DWORD) + MAX_PATH * sizeof(WCHAR). – Dwayne Robinson Apr 01 '15 at 12:43
-
I think the 2nd argument should be "FileNameInfo" instead of "FILE_NAME_INFO". So the correct call would be something like `union {FILE_NAME_INFO NameInfo; WCHAR buf[USHRT_MAX];} info; GetFileInformationByHandleEx(fileHandle, FileNameInfo, info.buf, sizeof(info));` – Holger Mar 08 '16 at 09:23
-
Although I am happy about any idea, I really wasted my time on GetFileInformationByHandleEx(), since the FILE_NAME_INFO structure seems not to contain the drive letter (or at least a volume device path). – Holger Mar 08 '16 at 09:24
-
1I am sorry for my previous comment, since the original post asked only for a file name. So it might be OK that GetFileInformationByHandleEx() does not report any drive information. Anyway I prefer to use GetFinalPathNameByHandle() for Windows Vista and later. – Holger Mar 08 '16 at 09:36
-
@Holger, thanks for the replies! IT looks like you're correct that GetFinalPathNameByHandle() is what should be used and looks to be much easier than GetFileInformationByHandleEx(). – Taylor Price Mar 09 '16 at 17:27
-
I tried this on a COM port and got ERROR_ADAP_HDW_ERR - I think this is for disk files only? – Patrick Feb 10 '23 at 07:51
For Windows Vista and later I prefer to use GetFinalPathNameByHandle()
char buf[MAX_PATH];
GetFinalPathNameByHandleA(fileHandle, buf, sizeof(buf), VOLUME_NAME_DOS)
For Windows XP I prefer the solution by Mehrdad.
So I load GetFinalPathNameByHandle() dynamically via GetProcAddress() and if this fails (because it's Windows XP) I go for Mehrdad's solution with NtQueryObject()
FWIW, here's the same solution from the MSDN article suggested by Prakash in Python using the wonderful ctypes:
from ctypes import *
# get handle to c:\boot.ini to test
handle = windll.kernel32.CreateFileA("c:\\boot.ini", 0x80000000, 3, 0, 3, 0x80, 0)
hfilemap = windll.kernel32.CreateFileMappingA(handle, 0, 2, 0, 1, 0)
pmem = windll.kernel32.MapViewOfFile(hfilemap, 4, 0, 0, 1)
name = create_string_buffer(1024)
windll.psapi.GetMappedFileNameA(windll.kernel32.GetCurrentProcess(), pmem, name, 1024)
print "The name for the handle 0x%08x is %s" % (handle, name.value)
# convert device name to drive letter
buf = create_string_buffer(512)
size = windll.kernel32.GetLogicalDriveStringsA(511, buf)
names = buf.raw[0:size-1].split("\0")
for drive in names:
windll.kernel32.QueryDosDeviceA(drive[0:2], buf, 512)
if name.value.startswith(buf.value):
print "%s%s" % (drive[0:2], name.value[len(buf.value):])
break

- 1,976
- 3
- 18
- 18
On unixes there is no real way of reliably doing this. In unix with the traditional unix filesystem, you can open a file and then unlink it (remove its entry from the directory) and use it, at which point the name isn't stored anywhere. In addition, because a file may have multiple hardlinks into the filesystem, each of the names are equivalent, so once you've got just the open handle it wouldn't be clear which filename you should map back towards.
So, you may be able to do this on Win32 using the other answers, but should you ever need to port the application to a unix enviornment, you'll be out of luck. My advice to you is to refactor your program, if possible, so that you don't need the OS to be able to maintain an open resource to filename connection.

- 16,145
- 6
- 29
- 32
If you need to do this on Win32 pre-Vista or Server 2008, look at the GetMappedFileName(...)
function, which is one of the best kept secrets in Win32. WIth a little C/C++-
fu, you can memory map a small portion of the file in question, and then pass that handle to this function.
Also, on Win32, you cannot really delete a file that is open (the open/unlink issue mentioned on another answer) - you can mark it for deletion on close, but it will still hang around until its last open handle is closed. Dunno if mapping (via mmap(...)
) the file in this case would help, because it has to point back to a physical file...
-=- James.