On Windows, every file on an NTFS volume has a unique File ID, i.e. a numeric identifier that works similarly to an inode number on Linux/Unix systems. File IDs are useful for tracking files regardless of renames or moves within the same volume, building file indexing systems, or implementing deduplication logic.
In this guide, you'll learn how to retrieve a Windows file's unique ID using Python and how to reverse the process to find a file's path from its ID.
Understanding Windows File IDs
A File ID is a unique identifier assigned by the NTFS file system to every file and directory on a volume. Key characteristics include:
- File IDs are unique within a volume (for example, the
C:drive). - They persist even when a file is renamed or moved within the same volume.
- They are represented as hexadecimal values (for example,
0x0000000000000000000100000000589).
Windows provides the fsutil command-line utility to query and work with file IDs. We'll invoke this utility from Python using os.popen() or the subprocess module.
Getting a File ID from a File Path
The fsutil file queryfileid command retrieves the unique ID for a file at a given path. You can call it from Python using os.popen():
from os import popen
file_path = r"C:\Users\YourName\Desktop\example.txt"
output = popen(f'fsutil file queryfileid "{file_path}"').read().strip()
print(output)Output:
File ID is 0x00000000000000000001000000000589How it works:
fsutil file queryfileidis a Windows command that returns the NTFS file ID for the specified path.os.popen()runs the command in a subprocess and captures its output..read().strip()retrieves the output text and removes trailing whitespace.
Getting a File Path from a File ID
The reverse operation, finding a file's path from its ID, uses the fsutil file queryfilenamebyid command:
from os import popen
file_id = "0x00000000000000000001000000000589"
output = popen(f'fsutil file queryfilenamebyid C:\\ {file_id}').read().strip()
print(output)Output:
A random link name to this file is \\?\C:\Users\YourName\Desktop\example.txtThe actual file path is C:\Users\YourName\Desktop\example.txt.
Using subprocess Instead of os.popen() (Recommended)
The subprocess module provides better error handling, security, and control over the process. It's the recommended alternative to os.popen():
import subprocess
def get_file_id(file_path):
"""Get the NTFS file ID for a given file path."""
result = subprocess.run(
["fsutil", "file", "queryfileid", file_path],
capture_output=True,
text=True
)
if result.returncode != 0:
raise FileNotFoundError(
f"Could not get file ID: {result.stderr.strip()}"
)
output = result.stdout.strip()
file_id = output.split("is")[-1].strip()
return file_id
def get_path_from_id(volume, file_id):
"""Get the file path from a file ID on the specified volume."""
result = subprocess.run(
["fsutil", "file", "queryfilenamebyid", volume, file_id],
capture_output=True,
text=True
)
if result.returncode != 0:
raise FileNotFoundError(
f"Could not resolve file ID: {result.stderr.strip()}"
)
output = result.stdout.strip()
path = output.split("\\\\?\\")[-1] if "\\\\?\\" in output else output
return path
file_path = r"C:\Users\YourName\Desktop\example.txt"
try:
fid = get_file_id(file_path)
print(f"File ID: {fid}")
resolved_path = get_path_from_id("C:\\", fid)
print(f"Resolved path: {resolved_path}")
except FileNotFoundError as e:
print(f"Error: {e}")Output:
File ID: 0x00000000000000000001000000000589
Resolved path: C:\Users\YourName\Desktop\example.txtUsing the Windows API Directly with ctypes
For a more robust approach that doesn't depend on command-line tools, you can use the Windows API through ctypes:
import ctypes
from ctypes import wintypes
import os
def get_file_id_by_handle(file_path):
"""Get the file ID using Windows API (GetFileInformationByHandle)."""
GENERIC_READ = 0x80000000
FILE_SHARE_READ = 0x00000001
OPEN_EXISTING = 3
handle = ctypes.windll.kernel32.CreateFileW(
file_path,
GENERIC_READ,
FILE_SHARE_READ,
None,
OPEN_EXISTING,
0,
None,
)
if handle == -1:
raise FileNotFoundError(f"Cannot open file: {file_path}")
try:
class BY_HANDLE_FILE_INFORMATION(ctypes.Structure):
_fields_ = [
("dwFileAttributes", wintypes.DWORD),
("ftCreationTime", wintypes.FILETIME),
("ftLastAccessTime", wintypes.FILETIME),
("ftLastWriteTime", wintypes.FILETIME),
("dwVolumeSerialNumber", wintypes.DWORD),
("nFileSizeHigh", wintypes.DWORD),
("nFileSizeLow", wintypes.DWORD),
("nNumberOfLinks", wintypes.DWORD),
("nFileIndexHigh", wintypes.DWORD),
("nFileIndexLow", wintypes.DWORD),
]
info = BY_HANDLE_FILE_INFORMATION()
success = ctypes.windll.kernel32.GetFileInformationByHandle(
handle, ctypes.byref(info)
)
if not success:
raise OSError("GetFileInformationByHandle failed")
file_id = (info.nFileIndexHigh << 32) | info.nFileIndexLow
return file_id
finally:
ctypes.windll.kernel32.CloseHandle(handle)
file_path = r"C:\Users\YourName\Desktop\example.txt"
try:
file_id = get_file_id_by_handle(file_path)
print(f"File ID: {file_id}")
print(f"File ID (hex): 0x{file_id:016X}")
except (FileNotFoundError, OSError) as e:
print(f"Error: {e}")This approach bypasses the command line entirely and queries the file system directly through the Windows API.
Common Mistake: Forgetting to Handle Missing Files
If the specified file doesn't exist, fsutil returns an error message instead of a file ID. Without proper error handling, your code may silently return incorrect results.
Wrong approach: no error handling.
from os import popen
file_path = r"C:\nonexistent\file.txt"
output = popen(f'fsutil file queryfileid "{file_path}"').read()
print(output) # Prints an error message, not a file IDOutput:
Error: The system cannot find the file specified.If your code tries to parse this as a file ID, it will produce garbage.
Correct approach: validate the result.
import subprocess
def get_file_id_safe(file_path):
"""Get file ID with proper error handling."""
result = subprocess.run(
["fsutil", "file", "queryfileid", file_path],
capture_output=True,
text=True
)
if result.returncode != 0 or "Error" in result.stdout:
return None
return result.stdout.strip().split("is")[-1].strip()
file_id = get_file_id_safe(r"C:\nonexistent\file.txt")
if file_id:
print(f"File ID: {file_id}")
else:
print("File not found or access denied.")Output:
File not found or access denied.Understanding the fsutil Commands
| Command | Purpose | Example |
|---|---|---|
fsutil file queryfileid <path> |
Get a file's unique ID from its path | Returns 0x... hex value |
fsutil file queryfilenamebyid <volume> <id> |
Get a file's path from its ID | Returns the file's location |
Conclusion
Retrieving Windows File IDs in Python is straightforward using either command-line utilities or the Windows API:
fsutilwithsubprocess.run()is the recommended approach for most use cases. It's simple, handles errors well, and avoids shell injection risks.os.popen()works but lacks proper error handling and is considered a legacy API.- Windows API via
ctypesprovides the most robust solution without command-line dependencies. - Always handle missing files and permission errors gracefully to avoid silent failures.
- Remember that File IDs are volume-specific: the same ID on different drives refers to different files.