How to build an app that checks if a text file or any file is being used by another program
Rahmatulloh
40
Reputation points
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
Sign in to answer