How to build an app that checks if a text file or any file is being used by another program

Rahmatulloh 40 Reputation points
2025-07-22T15:05:57.1566667+00:00

Hello, I want to check if a text file or any file is being used by another program, I tried to do so by chatgpt, gemini using python. But chatgpt and gemini couldn't help me(instead, they demonstrated me a thing like file lock by another program). However, all of their examples that they provided for me to do so above mentioned app doesn't work or work properly. Here is C# code that ChatGPT generated for me:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
class FileLockChecker
{
    private const int SystemHandleInformation = 16;
    private const uint STATUS_INFO_LENGTH_MISMATCH = 0xC0000004;
    private const int DUPLICATE_SAME_ACCESS = 0x2;
    private const uint PROCESS_DUP_HANDLE = 0x0040;
    private const int ObjectNameInformation = 1;
    private const int ObjectTypeInformation = 2;
    [StructLayout(LayoutKind.Sequential)]
    struct SYSTEM_HANDLE_INFORMATION
    {
        public int ProcessId;
        public byte ObjectTypeNumber;
        public byte Flags;
        public ushort Handle;
        public IntPtr Object;
        public uint GrantedAccess;
    }
    [StructLayout(LayoutKind.Sequential)]
    private struct UNICODE_STRING
    {
        public ushort Length;
        public ushort MaximumLength;
        public IntPtr Buffer;
    }
    [DllImport("ntdll.dll")]
    private static extern uint NtQuerySystemInformation(
        int systemInformationClass,
        IntPtr systemInformation,
        int systemInformationLength,
        ref int returnLength
    );
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern IntPtr OpenProcess(uint desiredAccess, bool inheritHandle, int processId);
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool CloseHandle(IntPtr handle);
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool DuplicateHandle(
        IntPtr hSourceProcessHandle,
        ushort hSourceHandle,
        IntPtr hTargetProcessHandle,
        out IntPtr lpTargetHandle,
        uint dwDesiredAccess,
        bool bInheritHandle,
        uint dwOptions
    );
    [DllImport("ntdll.dll")]
    private static extern int NtQueryObject(
        IntPtr handle,
        int objectInformationClass,
        IntPtr objectInformation,
        int objectInformationLength,
        ref int returnLength);
    [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
    private static extern int QueryDosDevice(string lpDeviceName, StringBuilder lpTargetPath, int ucchMax);
    static List<(int pid, ushort handle)> GetHandles()
    {
        int length = 0x10000;
        IntPtr ptr = Marshal.AllocHGlobal(length);
        uint status;
        while ((status = NtQuerySystemInformation(SystemHandleInformation, ptr, length, ref length)) == STATUS_INFO_LENGTH_MISMATCH)
        {
            Marshal.FreeHGlobal(ptr);
            ptr = Marshal.AllocHGlobal(length);
        }
        if (status != 0)
        {
            Marshal.FreeHGlobal(ptr);
            throw new Exception("NtQuerySystemInformation failed with status 0x" + status.ToString("X"));
        }
        int handleCount = Marshal.ReadInt32(ptr);
        IntPtr handlePtr = IntPtr.Add(ptr, 4);
        List<(int, ushort)> handles = new List<(int, ushort)>();
        int size = Marshal.SizeOf(typeof(SYSTEM_HANDLE_INFORMATION));
        for (int i = 0; i < handleCount; i++)
        {
            var shi = Marshal.PtrToStructure<SYSTEM_HANDLE_INFORMATION>(handlePtr);
            handles.Add((shi.ProcessId, shi.Handle));
            handlePtr = IntPtr.Add(handlePtr, size);
        }
        Marshal.FreeHGlobal(ptr);
        Console.WriteLine($"[Debug] Total system handles retrieved: {handles.Count}");
        return handles;
    }
    static string? NtPathToWin32Path(string ntPath)
    {
        foreach (var drive in Environment.GetLogicalDrives())
        {
            string driveLetter = drive.Substring(0, 2); // e.g. "C:"
            string? devicePath = QueryDosDevice(driveLetter);
            if (devicePath != null && ntPath.StartsWith(devicePath))
            {
                string converted = driveLetter + ntPath.Substring(devicePath.Length);
                Console.WriteLine($"[Debug] Converted NT path '{ntPath}' to Win32 path '{converted}'");
                return converted;
            }
        }
        Console.WriteLine($"[Debug] No DOS device mapping found for NT path '{ntPath}'");
        return ntPath; // fallback
    }
    static string? QueryDosDevice(string driveLetter)
    {
        var sb = new StringBuilder(260);
        int result = QueryDosDevice(driveLetter, sb, sb.Capacity);
        if (result != 0)
            return sb.ToString();
        return null;
    }
    static string? GetObjectTypeName(IntPtr handle)
    {
        int length = 0x1000;
        IntPtr ptr = Marshal.AllocHGlobal(length);
        try
        {
            int retLength = 0;
            int status = NtQueryObject(handle, ObjectTypeInformation, ptr, length, ref retLength);
            int tries = 0;
            while (status == unchecked((int)0xC0000004) || status == unchecked((int)0x80000005))
            {
                Marshal.FreeHGlobal(ptr);
                length = retLength;
                ptr = Marshal.AllocHGlobal(length);
                status = NtQueryObject(handle, ObjectTypeInformation, ptr, length, ref retLength);
                tries++;
                Console.WriteLine($"[Debug] NtQueryObject (ObjectTypeInformation) buffer too small, retry #{tries} with size {length}");
            }
            if (status != 0)
            {
                Console.WriteLine($"[Debug] NtQueryObject (ObjectTypeInformation) failed with status 0x{status:X}");
                return null;
            }
            UNICODE_STRING typeName = Marshal.PtrToStructure<UNICODE_STRING>(ptr);
            if (typeName.Length == 0)
            {
                Console.WriteLine("[Debug] Object type name length is zero.");
                return null;
            }
            string result = Marshal.PtrToStringUni(typeName.Buffer, typeName.Length / 2) ?? "";
            Console.WriteLine($"[Debug] Object type name: {result}");
            return result;
        }
        finally
        {
            Marshal.FreeHGlobal(ptr);
        }
    }
    static string? GetFileNameFromHandle(IntPtr processHandle, ushort handle)
    {
        Console.WriteLine($"[Debug] Trying to duplicate handle {handle}...");
        IntPtr dupHandle = IntPtr.Zero;
        if (!DuplicateHandle(processHandle, handle, Process.GetCurrentProcess().Handle,
            out dupHandle, 0, false, DUPLICATE_SAME_ACCESS))
        {
            Console.WriteLine($"[Debug] DuplicateHandle failed for handle {handle}.");
            return null;
        }
        try
        {
            string? typeName = GetObjectTypeName(dupHandle);
            if (typeName != "File")
            {
                Console.WriteLine($"[Debug] Handle {handle} is not a file handle (type: {typeName ?? "null"}).");
                return null;
            }
            int length = 0x1000;
            IntPtr ptr = Marshal.AllocHGlobal(length);
            try
            {
                int retLength = 0;
                int status = NtQueryObject(dupHandle, ObjectNameInformation, ptr, length, ref retLength);
                int retries = 0;
                while (status == unchecked((int)0xC0000004) || status == unchecked((int)0x80000005))
                {
                    Marshal.FreeHGlobal(ptr);
                    length = retLength;
                    ptr = Marshal.AllocHGlobal(length);
                    status = NtQueryObject(dupHandle, ObjectNameInformation, ptr, length, ref retLength);
                    retries++;
                    Console.WriteLine($"[Debug] NtQueryObject (ObjectNameInformation) buffer too small, retry #{retries} with size {length}.");
                }
                if (status != 0)
                {
                    Console.WriteLine($"[Debug] NtQueryObject (ObjectNameInformation) failed with status 0x{status:X}.");
                    return null;
                }
                UNICODE_STRING name = Marshal.PtrToStructure<UNICODE_STRING>(ptr);
                if (name.Length == 0)
                {
                    Console.WriteLine($"[Debug] Object name length is zero for handle {handle}.");
                    return null;
                }
                string objectName = Marshal.PtrToStringUni(name.Buffer, name.Length / 2) ?? "";
                if (string.IsNullOrEmpty(objectName))
                {
                    Console.WriteLine($"[Debug] Object name string is null or empty for handle {handle}.");
                    return null;
                }
                return NtPathToWin32Path(objectName);
            }
            finally
            {
                Marshal.FreeHGlobal(ptr);
            }
        }
        finally
        {
            CloseHandle(dupHandle);
        }
    }
    static void CloseHandleForProcess(int pid, ushort handle)
    {
        IntPtr procHandle = OpenProcess(PROCESS_DUP_HANDLE, false, pid);
        if (procHandle == IntPtr.Zero) return;
        IntPtr dupHandle;
        if (DuplicateHandle(procHandle, handle, Process.GetCurrentProcess().Handle, out dupHandle, 0, false, 0))
        {
            CloseHandle(dupHandle);
        }
        CloseHandle(procHandle);
    }
    static void Main(string[] args)
    {
        if (args.Length == 0)
        {
            Console.WriteLine("Usage: FileLockChecker.exe <file-path> [--force]");
            return;
        }
        string targetFile = System.IO.Path.GetFullPath(args[0]).ToLowerInvariant();
        bool forceClose = args.Length > 1 && args[1] == "--force";
        Console.WriteLine($"Running as {(Environment.Is64BitProcess ? "x64" : "x86")} process.");
        Console.WriteLine($"Checking for processes locking: {targetFile}\n");
        try
        {
            var handles = GetHandles();
            int found = 0;
            foreach (var (pid, handle) in handles)
            {
                try
                {
                    IntPtr procHandle = OpenProcess(PROCESS_DUP_HANDLE, false, pid);
                    if (procHandle == IntPtr.Zero)
                        continue;
                    string? fileName = GetFileNameFromHandle(procHandle, handle);
                    CloseHandle(procHandle);
                    if (fileName == null)
                        continue;
                    string normalizedFileName = System.IO.Path.GetFullPath(fileName).ToLowerInvariant();
                    if (normalizedFileName != targetFile)
                        continue;
                    Process proc = Process.GetProcessById(pid);
                    string name = proc.ProcessName;
                    Console.WriteLine($"Process: {name} (PID {pid}), Handle: {handle}, File: {fileName}");
                    found++;
                    if (forceClose)
                    {
                        Console.WriteLine($" -> Closing handle {handle} for {name}");
                        CloseHandleForProcess(pid, handle);
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"[Warning] Cannot access PID {pid}: {ex.Message}");
                }
            }
            if (found == 0)
            {
                Console.WriteLine("No locking processes found.");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
        }
    }
}

Windows development | Windows API - Win32

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.