Skip to content

How to Get the File ID of a Windows File in Python

Learn how to retrieve and use Windows NTFS File IDs in Python with fsutil, subprocess, or the Windows API for file tracking, deduplication, and path resolution.

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 0x00000000000000000001000000000589

How it works:

  1. fsutil file queryfileid is a Windows command that returns the NTFS file ID for the specified path.
  2. os.popen() runs the command in a subprocess and captures its output.
  3. .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.txt

The actual file path is C:\Users\YourName\Desktop\example.txt.

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.txt

Using 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 ID

Output:

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:

  • fsutil with subprocess.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 ctypes provides 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.
Navigation