How to build an app to check any file that is opened by another program?

Rahmatulloh 40 Reputation points
2025-07-22T15:10:18.3166667+00:00

I want to build an app to check any file that is opened by another program. I used python to do so(psutil). But It didn't work. I also used ChatGPT, Gemini, though they didn't provide me proper solution. I need your help

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
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. Castorix31 90,956 Reputation points
    2025-07-23T10:20:27.7866667+00:00

    This test works for me (test with a .jpg opened with mspaint) :

    // The file must be opened with an app which locks it, like mspaint.exe
    string sFile = @"E:\Temp\test.jpg";
     
    IShellItem pShellItem = null;
    IFileIsInUse pFileIsInUse = null;
    HRESULT hr = SHCreateItemFromParsingName(sFile, IntPtr.Zero, typeof(IShellItem).GUID, out pShellItem);
    if (hr == HRESULT.S_OK)
    {
        hr = GetInfoForFileInUse(pShellItem, out pFileIsInUse);
        if (hr == HRESULT.S_OK)
        {
            StringBuilder sbString = new StringBuilder(260);
            hr = pFileIsInUse.GetAppName(out sbString);
            if (hr == HRESULT.S_OK)
            {
                uint nFlags = 0;
                hr = pFileIsInUse.GetCapabilities(out nFlags);
                string sRightProcessPath = null;
                string sProcessPath = null;
                int nRightPID = 0;
                System.Diagnostics.Process rightProcess = null;
                foreach (var procs in System.Diagnostics.Process.GetProcesses())
                {                          
                    int nPID = procs.Id;
                    IntPtr hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, nPID);
                    if (hProcess != IntPtr.Zero)
                    {
                        uint nSize = 260;
                        StringBuilder sProcessImageName = new StringBuilder((int)nSize);
                        QueryFullProcessImageName(hProcess, 0, sProcessImageName, ref nSize);
                        sProcessPath = sProcessImageName.ToString();
                        CloseHandle(hProcess);
                    }
                    if (sProcessPath != null)
                    {                               
                        try
                        {
                            System.Diagnostics.FileVersionInfo fvi = System.Diagnostics.FileVersionInfo.GetVersionInfo(sProcessPath);
                            string sFileDescription = fvi.FileDescription;
                            if (sFileDescription != null)
                            {
                                if (sFileDescription.ToLower().Contains(sbString.ToString().ToLower()))
                                {
                                    sRightProcessPath = sProcessPath;
                                    nRightPID = nPID;
                                    rightProcess = procs;
                                    break;
                                }
                            }
                        }
                        catch (Exception Ex)
                        {
                            string sText = Convert.ToString(Ex);
                        }
                    }
                }
                string sMessage = "Application : " + sbString.ToString();
                if (sRightProcessPath != null)
                    sMessage += Environment.NewLine + "Process path : " + sRightProcessPath;
                sMessage += Environment.NewLine + "PID : " + nRightPID.ToString();
                sMessage += Environment.NewLine + string.Format("Do you want to kill the process with PID = {0} ?", nRightPID.ToString());           
                DialogResult dlgResult = MessageBox.Show(sMessage, "Information", MessageBoxButtons.YesNo, MessageBoxIcon.Information);
                if (dlgResult == DialogResult.Yes)
                {
                    rightProcess.Kill();
                }
            }
            else
            {
                string sMessage = sFile + " not locked";
                System.Windows.Forms.MessageBox.Show(sMessage, "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }
    }
    
    

    with declarations :

    public enum HRESULT : int
    {
        S_OK = 0,
        S_FALSE = 1,
        E_NOINTERFACE = unchecked((int)0x80004002),
        E_NOTIMPL = unchecked((int)0x80004001),
        E_FAIL = unchecked((int)0x80004005),
        E_UNEXPECTED = unchecked((int)0x8000FFFF),
        E_OUTOFMEMORY = unchecked((int)0x8007000E)
    }
    
    [DllImport("Windows.storage.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern HRESULT GetInfoForFileInUse(IShellItem psi, out IFileIsInUse ppof);
    
    [ComImport()]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE")]
    public interface IShellItem
    {
        HRESULT BindToHandler(IntPtr pbc, ref Guid bhid, ref Guid riid, ref IntPtr ppv);
        HRESULT GetParent(ref IShellItem ppsi);
        [PreserveSig]
        HRESULT GetDisplayName(SIGDN sigdnName, ref System.Text.StringBuilder ppszName);
        HRESULT GetAttributes(uint sfgaoMask, ref uint psfgaoAttribs);
        HRESULT Compare(IShellItem psi, uint hint, ref int piOrder);
    }
    
    public enum SIGDN : int
    {
        SIGDN_NORMALDISPLAY = 0x0,
        SIGDN_PARENTRELATIVEPARSING = unchecked((int)0x80018001),
        SIGDN_DESKTOPABSOLUTEPARSING = unchecked((int)0x80028000),
        SIGDN_PARENTRELATIVEEDITING = unchecked((int)0x80031001),
        SIGDN_DESKTOPABSOLUTEEDITING = unchecked((int)0x8004C000),
        SIGDN_FILESYSPATH = unchecked((int)0x80058000),
        SIGDN_URL = unchecked((int)0x80068000),
        SIGDN_PARENTRELATIVEFORADDRESSBAR = unchecked((int)0x8007C001),
        SIGDN_PARENTRELATIVE = unchecked((int)0x80080001)
    }
    
    [ComImport()]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("64a1cbf0-3a1a-4461-9158-376969693950")]
    public interface IFileIsInUse
    {
        [PreserveSig]
        HRESULT GetAppName(out StringBuilder ppszName); 
        HRESULT GetUsage(out FILE_USAGE_TYPE pfut); 
        HRESULT GetCapabilities(out uint pdwCapFlags);
        [PreserveSig]
        HRESULT GetSwitchToHWND(out IntPtr phwnd);
        HRESULT CloseFile();
    }
    
    public enum FILE_USAGE_TYPE
    {
        FUT_PLAYING = 0,
        FUT_EDITING = (FUT_PLAYING + 1),
        FUT_GENERIC = (FUT_EDITING + 1)
    }
    
    [DllImport("Shell32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern HRESULT SHCreateItemFromParsingName(string pszPath, IntPtr pbc, [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IShellItem ppv);
    
    [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, int dwProcessId);
    
    public const int PROCESS_QUERY_LIMITED_INFORMATION = (0x1000);
    
    [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern bool QueryFullProcessImageName(IntPtr hProcess, int dwFlags, StringBuilder lpExeName, ref uint lpdwSize);
    
    [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern bool CloseHandle(IntPtr hObject);
    
    
    0 comments No comments

Your answer

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