Related
I was looking a lot for method to get parent process in .NET, but found only P/Invoke way.
Here is a solution. It uses p/invoke, but seems to work well, 32 or 64 cpu:
/// <summary>
/// A utility class to determine a process parent.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct ParentProcessUtilities
{
// These members must match PROCESS_BASIC_INFORMATION
internal IntPtr Reserved1;
internal IntPtr PebBaseAddress;
internal IntPtr Reserved2_0;
internal IntPtr Reserved2_1;
internal IntPtr UniqueProcessId;
internal IntPtr InheritedFromUniqueProcessId;
[DllImport("ntdll.dll")]
private static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, ref ParentProcessUtilities processInformation, int processInformationLength, out int returnLength);
/// <summary>
/// Gets the parent process of the current process.
/// </summary>
/// <returns>An instance of the Process class.</returns>
public static Process GetParentProcess()
{
return GetParentProcess(Process.GetCurrentProcess().Handle);
}
/// <summary>
/// Gets the parent process of specified process.
/// </summary>
/// <param name="id">The process id.</param>
/// <returns>An instance of the Process class.</returns>
public static Process GetParentProcess(int id)
{
Process process = Process.GetProcessById(id);
return GetParentProcess(process.Handle);
}
/// <summary>
/// Gets the parent process of a specified process.
/// </summary>
/// <param name="handle">The process handle.</param>
/// <returns>An instance of the Process class.</returns>
public static Process GetParentProcess(IntPtr handle)
{
ParentProcessUtilities pbi = new ParentProcessUtilities();
int returnLength;
int status = NtQueryInformationProcess(handle, 0, ref pbi, Marshal.SizeOf(pbi), out returnLength);
if (status != 0)
throw new Win32Exception(status);
try
{
return Process.GetProcessById(pbi.InheritedFromUniqueProcessId.ToInt32());
}
catch (ArgumentException)
{
// not found
return null;
}
}
}
This code provides a nice interface for finding the Parent process object and takes into account the possibility of multiple processes with the same name:
Usage:
Console.WriteLine("ParentPid: " + Process.GetProcessById(6972).Parent().Id);
Code:
public static class ProcessExtensions {
private static string FindIndexedProcessName(int pid) {
var processName = Process.GetProcessById(pid).ProcessName;
var processesByName = Process.GetProcessesByName(processName);
string processIndexdName = null;
for (var index = 0; index < processesByName.Length; index++) {
processIndexdName = index == 0 ? processName : processName + "#" + index;
var processId = new PerformanceCounter("Process", "ID Process", processIndexdName);
if ((int) processId.NextValue() == pid) {
return processIndexdName;
}
}
return processIndexdName;
}
private static Process FindPidFromIndexedProcessName(string indexedProcessName) {
var parentId = new PerformanceCounter("Process", "Creating Process ID", indexedProcessName);
return Process.GetProcessById((int) parentId.NextValue());
}
public static Process Parent(this Process process) {
return FindPidFromIndexedProcessName(FindIndexedProcessName(process.Id));
}
}
This way:
public static Process GetParent(this Process process)
{
try
{
using (var query = new ManagementObjectSearcher(
"SELECT * " +
"FROM Win32_Process " +
"WHERE ProcessId=" + process.Id))
{
return query
.Get()
.OfType<ManagementObject>()
.Select(p => Process.GetProcessById((int)(uint)p["ParentProcessId"]))
.FirstOrDefault();
}
}
catch
{
return null;
}
}
Here's my try at a managed solution.
It polls the performance counters for all processes and returns a dictionary of child PID to parent PID. Then you can check the dictionary with your current PID to see your parent, grandparent, etc.
It is overkill in how much info it gets, for sure. Feel free to optimize.
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace PidExamples
{
class ParentPid
{
static void Main(string[] args)
{
var childPidToParentPid = GetAllProcessParentPids();
int currentProcessId = Process.GetCurrentProcess().Id;
Console.WriteLine("Current Process ID: " + currentProcessId);
Console.WriteLine("Parent Process ID: " + childPidToParentPid[currentProcessId]);
}
public static Dictionary<int, int> GetAllProcessParentPids()
{
var childPidToParentPid = new Dictionary<int, int>();
var processCounters = new SortedDictionary<string, PerformanceCounter[]>();
var category = new PerformanceCounterCategory("Process");
// As the base system always has more than one process running,
// don't special case a single instance return.
var instanceNames = category.GetInstanceNames();
foreach(string t in instanceNames)
{
try
{
processCounters[t] = category.GetCounters(t);
}
catch (InvalidOperationException)
{
// Transient processes may no longer exist between
// GetInstanceNames and when the counters are queried.
}
}
foreach (var kvp in processCounters)
{
int childPid = -1;
int parentPid = -1;
foreach (var counter in kvp.Value)
{
if ("ID Process".CompareTo(counter.CounterName) == 0)
{
childPid = (int)(counter.NextValue());
}
else if ("Creating Process ID".CompareTo(counter.CounterName) == 0)
{
parentPid = (int)(counter.NextValue());
}
}
if (childPid != -1 && parentPid != -1)
{
childPidToParentPid[childPid] = parentPid;
}
}
return childPidToParentPid;
}
}
}
In other news, I learned how many performance counters there were on my machine: 13401. Holy cow.
If accepting P/Invoke, there's a better way, which is more documented than
NtQueryInformationProcess: namely PROCESSENTRY32 (CreateToolhelp32Snapshot, Process32First, Process32Next). It's shown in this post.
Pay attention to the subtle details
and note that parent PID is not necessarily the creator PID, in fact these may be completely unrelated, as pointed out by the community comments at
PROCESSENTRY32.
If you've ever digged the BCL, you shall find that the ways to find parent process are deliberately avoided, take this for example:
https://referencesource.microsoft.com/#System/services/monitoring/system/diagnosticts/ProcessManager.cs,327
As you can see in the source code, it contains comprehensive structures and imported native methods which are absolutely sufficient to get the job done. However, even if you access them via reflection(this is possible), you would not find a method for doing it directly. I can't answer why, yet this phenomenon causes questions like yours are asked for somewhat repeatedly; for example:
How can I get the PID of the parent process of my application
For there is no answer along with some code using CreateToolhelp32Snapshot in this thread, I'd add it -- part of the structure definitions and names I steal from the MS' reference source :)
Code
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Linq;
using System;
public static class Toolhelp32 {
public const uint Inherit = 0x80000000;
public const uint SnapModule32 = 0x00000010;
public const uint SnapAll = SnapHeapList|SnapModule|SnapProcess|SnapThread;
public const uint SnapHeapList = 0x00000001;
public const uint SnapProcess = 0x00000002;
public const uint SnapThread = 0x00000004;
public const uint SnapModule = 0x00000008;
[DllImport("kernel32.dll")]
static extern bool CloseHandle(IntPtr handle);
[DllImport("kernel32.dll")]
static extern IntPtr CreateToolhelp32Snapshot(uint flags, int processId);
public static IEnumerable<T> TakeSnapshot<T>(uint flags, int id) where T : IEntry, new() {
using(var snap = new Snapshot(flags, id))
for(IEntry entry = new T { }; entry.TryMoveNext(snap, out entry);)
yield return (T)entry;
}
public interface IEntry {
bool TryMoveNext(Toolhelp32.Snapshot snap, out IEntry entry);
}
public struct Snapshot:IDisposable {
void IDisposable.Dispose() {
Toolhelp32.CloseHandle(m_handle);
}
public Snapshot(uint flags, int processId) {
m_handle=Toolhelp32.CreateToolhelp32Snapshot(flags, processId);
}
IntPtr m_handle;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct WinProcessEntry:Toolhelp32.IEntry {
[DllImport("kernel32.dll")]
public static extern bool Process32Next(Toolhelp32.Snapshot snap, ref WinProcessEntry entry);
public bool TryMoveNext(Toolhelp32.Snapshot snap, out Toolhelp32.IEntry entry) {
var x = new WinProcessEntry { dwSize=Marshal.SizeOf(typeof(WinProcessEntry)) };
var b = Process32Next(snap, ref x);
entry=x;
return b;
}
public int dwSize;
public int cntUsage;
public int th32ProcessID;
public IntPtr th32DefaultHeapID;
public int th32ModuleID;
public int cntThreads;
public int th32ParentProcessID;
public int pcPriClassBase;
public int dwFlags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public String fileName;
//byte fileName[260];
//public const int sizeofFileName = 260;
}
public static class Extensions {
public static Process Parent(this Process p) {
var entries = Toolhelp32.TakeSnapshot<WinProcessEntry>(Toolhelp32.SnapAll, 0);
var parentid = entries.First(x => x.th32ProcessID==p.Id).th32ParentProcessID;
return Process.GetProcessById(parentid);
}
}
And we can use it like:
Test
public class TestClass {
public static void TestMethod() {
var p = Process.GetCurrentProcess().Parent();
Console.WriteLine("{0}", p.Id);
}
}
For alternative ending ..
According to the documentation, there are a pair of iteration methods per type of the entries such as Process32First and Process32Next are for the iteration of processes; but I found the `xxxxFirst' methods are unnecessary, and then I thought why not put the iteration method with its corresponding entry type? It'd be easier to implement and be understood(I guess so ..).
Just as Toolhelp32 suffixed with help, I think a static helper class is proper, so that we can have the clear qualified names such as Toolhelp32.Snapshot or Toolhelp32.IEntry though it'd be irrelevant here ..
Once the parent process is obtained, if you further want to get some detailed infos, you can extend with this easily, for example, iterate on its modules, then add:
Code - WinModuleEntry
[StructLayout(LayoutKind.Sequential)]
public struct WinModuleEntry:Toolhelp32.IEntry { // MODULEENTRY32
[DllImport("kernel32.dll")]
public static extern bool Module32Next(Toolhelp32.Snapshot snap, ref WinModuleEntry entry);
public bool TryMoveNext(Toolhelp32.Snapshot snap, out Toolhelp32.IEntry entry) {
var x = new WinModuleEntry { dwSize=Marshal.SizeOf(typeof(WinModuleEntry)) };
var b = Module32Next(snap, ref x);
entry=x;
return b;
}
public int dwSize;
public int th32ModuleID;
public int th32ProcessID;
public int GlblcntUsage;
public int ProccntUsage;
public IntPtr modBaseAddr;
public int modBaseSize;
public IntPtr hModule;
//byte moduleName[256];
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string moduleName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string fileName;
//byte fileName[260];
//public const int sizeofModuleName = 256;
//public const int sizeofFileName = 260;
}
and some test ..
public class TestClass {
public static void TestMethod() {
var p = Process.GetCurrentProcess().Parent();
Console.WriteLine("{0}", p.Id);
var formatter = new CustomFormatter { };
foreach(var x in Toolhelp32.TakeSnapshot<WinModuleEntry>(Toolhelp32.SnapModule, p.Id)) {
Console.WriteLine(String.Format(formatter, "{0}", x));
}
}
}
public class CustomFormatter:IFormatProvider, ICustomFormatter {
String ICustomFormatter.Format(String format, object arg, IFormatProvider formatProvider) {
var type = arg.GetType();
var fields = type.GetFields();
var q = fields.Select(x => String.Format("{0}:{1}", x.Name, x.GetValue(arg)));
return String.Format("{{{0}}}", String.Join(", ", q.ToArray()));
}
object IFormatProvider.GetFormat(Type formatType) {
return typeof(ICustomFormatter)!=formatType ? null : this;
}
}
In case you want a code example ..
https://github.com/kenkins/ToolHelp32ConsoleApp
Unfortunately, there is still no managed cross-platform solution to get a parent process as of .NET 6.
This issue is being discussed (since Dec 12, 2017) on the .NET repository: Expose Parent/Child Process Information via System.Diagnostics.Process (#24423).
I want to use a system wide hotkey in my powershell GUI application. I found this C# code for registering a hotkey and integrated it in my script.
The trouble is, because I can't fully understand the C# code, I don't know how to properly add the parameters for the registerhotkey method.
The goal is: on key press SHIFT+ALT+Z execute the code in $hotkeyExecCode.
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[System.Windows.Forms.Application]::EnableVisualStyles()
$form1 = New-Object 'System.Windows.Forms.Form'
$form1.ClientSize = '200, 150'
$cCode = #"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
/// <summary> This class allows you to manage a hotkey </summary>
public class GlobalHotkeys : IDisposable
{
[DllImport( "user32", SetLastError = true )]
[return: MarshalAs( UnmanagedType.Bool )]
public static extern bool RegisterHotKey (IntPtr hwnd, int id, uint fsModifiers, uint vk);
[DllImport( "user32", SetLastError = true )]
public static extern int UnregisterHotKey (IntPtr hwnd, int id);
[DllImport( "kernel32", SetLastError = true )]
public static extern short GlobalAddAtom (string lpString);
[DllImport( "kernel32", SetLastError = true )]
public static extern short GlobalDeleteAtom (short nAtom);
public const int MOD_ALT = 1;
public const int MOD_CONTROL = 2;
public const int MOD_SHIFT = 4;
public const int MOD_WIN = 8;
public const int WM_HOTKEY = 0x312;
public GlobalHotkeys()
{
this.Handle = Process.GetCurrentProcess().Handle;
}
/// <summary>Handle of the current process</summary>
public IntPtr Handle;
/// <summary>The ID for the hotkey</summary>
public short HotkeyID { get; private set; }
/// <summary>Register the hotkey</summary>
public void RegisterGlobalHotKey(int hotkey, int modifiers, IntPtr handle)
{
UnregisterGlobalHotKey();
this.Handle = handle;
RegisterGlobalHotKey(hotkey, modifiers);
}
/// <summary>Register the hotkey</summary>
public void RegisterGlobalHotKey(int hotkey, int modifiers)
{
UnregisterGlobalHotKey();
try
{
// use the GlobalAddAtom API to get a unique ID (as suggested by MSDN)
string atomName = Thread.CurrentThread.ManagedThreadId.ToString("X8") + this.GetType().FullName;
HotkeyID = GlobalAddAtom(atomName);
if (HotkeyID == 0)
throw new Exception("Unable to generate unique hotkey ID. Error: " + Marshal.GetLastWin32Error().ToString());
// register the hotkey, throw if any error
if (!RegisterHotKey(this.Handle, HotkeyID, (uint)modifiers, (uint)hotkey))
throw new Exception("Unable to register hotkey. Error: " + Marshal.GetLastWin32Error().ToString());
}
catch (Exception ex)
{
// clean up if hotkey registration failed
Dispose();
Console.WriteLine(ex);
}
}
/// <summary>Unregister the hotkey</summary>
public void UnregisterGlobalHotKey()
{
if ( this.HotkeyID != 0 )
{
UnregisterHotKey(this.Handle, HotkeyID);
// clean up the atom list
GlobalDeleteAtom(HotkeyID);
HotkeyID = 0;
}
}
public void Dispose()
{
UnregisterGlobalHotKey();
}
}
"#
Add-Type -TypeDefinition $cCode
$hotkeyExecCode = { $form1.WindowState = "normal"}
[GlobalHotkeys]::RegisterHotKey( ???????? ) # <--------- How do i add the parameters here?
$form1.windowstate = "minimized"
$form1.ShowDialog()
So I thought I would include the final answer here so you don't have to make sense of this post. Big thanks to Simon Mourier for taking the time to figure this one out.
MY WORKING CODE
try
{
//Get a list of available devices attached to the USB hub
List<string> disks = new List<string>();
var usbDevices = GetUSBDevices();
//Enumerate the USB devices to see if any have specific VID/PID
foreach (var usbDevice in usbDevices)
{
if (usbDevice.DeviceID.Contains(USB_PID) && usbDevice.DeviceID.Contains(USB_VID))
{
foreach (string name in usbDevice.GetDiskNames())
{
//Open dialog to show file names
textbox1.Text = name.ToString();
}
}
}
So just use GetUSBDevices from my original question and then include the two classes shown by Simon Mourier's answer and it should be good to go!
ORIGINAL QUESTION
I know this question has been asked before (see here) but none of them have a confirmed answer and I've tried all of the suggested answers. Unfortunately those threads are long dead and I was hoping someone could give a better answer here.
So far I have two 'starting points', each of which I'll show below.
OPTION 1: (gets VID/PID but not drive letter)
I have an embedded device which I connect to through an application. I have code which succesfully scans any USB devices and checks the VID/PID. I successfully detect my device but I have no idea how to get the drive letter. Can someone help me out? I feel like I could add another line in the class but when I go through Device Manager I can't find any property there that describes the drive letter.
Thanks!
I'll include my code so far below.
private void tsDownload_Click(object sender, EventArgs e)
{
var usbDevices = GetUSBDevices();
foreach (var usbDevice in usbDevices)
{
if (usbDevice.DeviceID.Contains(USB_PID) && usbDevice.DeviceID.Contains(USB_VID))
{
//Find drive letter here
}
}
}
where those functions are:
static List<USBDeviceInfo> GetUSBDevices()
{
List<USBDeviceInfo> devices = new List<USBDeviceInfo>();
ManagementObjectCollection collection;
using (var searcher = new ManagementObjectSearcher(#"Select * From Win32_USBHub"))
collection = searcher.Get();
foreach (var device in collection)
{
devices.Add(new USBDeviceInfo(
(string)device.GetPropertyValue("DeviceID"),
(string)device.GetPropertyValue("PNPDeviceID"),
(string)device.GetPropertyValue("Description")
));
}
collection.Dispose();
return devices;
}
and the class is:
class USBDeviceInfo
{
public USBDeviceInfo(string deviceID, string pnpDeviceID, string description)
{
this.DeviceID = deviceID;
this.PnpDeviceID = pnpDeviceID;
this.Description = description;
}
public string DeviceID { get; private set; }
public string PnpDeviceID { get; private set; }
public string Description { get; private set; }
}
OPTION 2: (get drive letter but not VID/PID)
foreach (ManagementObject drive in new ManagementObjectSearcher("select * from Win32_DiskDrive where InterfaceType='USB'").Get())
{
foreach(ManagementObject partition in new ManagementObjectSearcher("ASSOCIATORS OF {Win32_DiskDrive.DeviceID='" + drive["DeviceID"] + "'} WHERE AssocClass = Win32_DiskDriveToDiskPartition").Get())
{
foreach (ManagementObject disk in new ManagementObjectSearcher("ASSOCIATORS OF {Win32_DiskPartition.DeviceID='" + partition["DeviceID"] + "'} WHERE AssocClass = Win32_LogicalDiskToPartition").Get())
{
textBox1.Text = disk["Name"].ToString();
}
}
}
I'm going to guess the VID/PID is in one of the disk object properties but I just can't find which one.
I may be mistaken but it seems WMI doesn't know about the parent-child relation that exists in the Windows device setup API.
So, I have created a small Device utility class that can add this missing link from the native Setup API. Here is how you would use it in your original USBDeviceInfo class:
class USBDeviceInfo
{
public USBDeviceInfo(string deviceID, string pnpDeviceID, string description)
{
this.DeviceID = deviceID;
this.PnpDeviceID = pnpDeviceID;
this.Description = description;
}
public string DeviceID { get; private set; }
public string PnpDeviceID { get; private set; }
public string Description { get; private set; }
public IEnumerable<string> GetDiskNames()
{
using (Device device = Device.Get(PnpDeviceID))
{
// get children devices
foreach (string childDeviceId in device.ChildrenPnpDeviceIds)
{
// get the drive object that correspond to this id (escape the id)
foreach (ManagementObject drive in new ManagementObjectSearcher("SELECT DeviceID FROM Win32_DiskDrive WHERE PNPDeviceID='" + childDeviceId.Replace(#"\", #"\\") + "'").Get())
{
// associate physical disks with partitions
foreach (ManagementObject partition in new ManagementObjectSearcher("ASSOCIATORS OF {Win32_DiskDrive.DeviceID='" + drive["DeviceID"] + "'} WHERE AssocClass=Win32_DiskDriveToDiskPartition").Get())
{
// associate partitions with logical disks (drive letter volumes)
foreach (ManagementObject disk in new ManagementObjectSearcher("ASSOCIATORS OF {Win32_DiskPartition.DeviceID='" + partition["DeviceID"] + "'} WHERE AssocClass=Win32_LogicalDiskToPartition").Get())
{
yield return (string)disk["DeviceID"];
}
}
}
}
}
}
}
And here is the new Device class:
public sealed class Device : IDisposable
{
private IntPtr _hDevInfo;
private SP_DEVINFO_DATA _data;
private Device(IntPtr hDevInfo, SP_DEVINFO_DATA data)
{
_hDevInfo = hDevInfo;
_data = data;
}
public static Device Get(string pnpDeviceId)
{
if (pnpDeviceId == null)
throw new ArgumentNullException("pnpDeviceId");
IntPtr hDevInfo = SetupDiGetClassDevs(IntPtr.Zero, pnpDeviceId, IntPtr.Zero, DIGCF.DIGCF_ALLCLASSES | DIGCF.DIGCF_DEVICEINTERFACE);
if (hDevInfo == (IntPtr)INVALID_HANDLE_VALUE)
throw new Win32Exception(Marshal.GetLastWin32Error());
SP_DEVINFO_DATA data = new SP_DEVINFO_DATA();
data.cbSize = Marshal.SizeOf(data);
if (!SetupDiEnumDeviceInfo(hDevInfo, 0, ref data))
{
int err = Marshal.GetLastWin32Error();
if (err == ERROR_NO_MORE_ITEMS)
return null;
throw new Win32Exception(err);
}
return new Device(hDevInfo, data) {PnpDeviceId = pnpDeviceId};
}
public void Dispose()
{
if (_hDevInfo != IntPtr.Zero)
{
SetupDiDestroyDeviceInfoList(_hDevInfo);
_hDevInfo = IntPtr.Zero;
}
}
public string PnpDeviceId { get; private set; }
public string ParentPnpDeviceId
{
get
{
if (IsVistaOrHiger)
return GetStringProperty(DEVPROPKEY.DEVPKEY_Device_Parent);
uint parent;
int cr = CM_Get_Parent(out parent, _data.DevInst, 0);
if (cr != 0)
throw new Exception("CM Error:" + cr);
return GetDeviceId(parent);
}
}
private static string GetDeviceId(uint inst)
{
IntPtr buffer = Marshal.AllocHGlobal(MAX_DEVICE_ID_LEN + 1);
int cr = CM_Get_Device_ID(inst, buffer, MAX_DEVICE_ID_LEN + 1, 0);
if (cr != 0)
throw new Exception("CM Error:" + cr);
try
{
return Marshal.PtrToStringAnsi(buffer);
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}
public string[] ChildrenPnpDeviceIds
{
get
{
if (IsVistaOrHiger)
return GetStringListProperty(DEVPROPKEY.DEVPKEY_Device_Children);
uint child;
int cr = CM_Get_Child(out child, _data.DevInst, 0);
if (cr != 0)
return new string[0];
List<string> ids = new List<string>();
ids.Add(GetDeviceId(child));
do
{
cr = CM_Get_Sibling(out child, child, 0);
if (cr != 0)
return ids.ToArray();
ids.Add(GetDeviceId(child));
}
while (true);
}
}
private static bool IsVistaOrHiger
{
get
{
return (Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version.CompareTo(new Version(6, 0)) >= 0);
}
}
private const int INVALID_HANDLE_VALUE = -1;
private const int ERROR_NO_MORE_ITEMS = 259;
private const int MAX_DEVICE_ID_LEN = 200;
[StructLayout(LayoutKind.Sequential)]
private struct SP_DEVINFO_DATA
{
public int cbSize;
public Guid ClassGuid;
public uint DevInst;
public IntPtr Reserved;
}
[Flags]
private enum DIGCF : uint
{
DIGCF_DEFAULT = 0x00000001,
DIGCF_PRESENT = 0x00000002,
DIGCF_ALLCLASSES = 0x00000004,
DIGCF_PROFILE = 0x00000008,
DIGCF_DEVICEINTERFACE = 0x00000010,
}
[DllImport("setupapi.dll", SetLastError = true)]
private static extern bool SetupDiEnumDeviceInfo(IntPtr DeviceInfoSet, uint MemberIndex, ref SP_DEVINFO_DATA DeviceInfoData);
[DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr SetupDiGetClassDevs(IntPtr ClassGuid, string Enumerator, IntPtr hwndParent, DIGCF Flags);
[DllImport("setupapi.dll")]
private static extern int CM_Get_Parent(out uint pdnDevInst, uint dnDevInst, uint ulFlags);
[DllImport("setupapi.dll")]
private static extern int CM_Get_Device_ID(uint dnDevInst, IntPtr Buffer, int BufferLen, uint ulFlags);
[DllImport("setupapi.dll")]
private static extern int CM_Get_Child(out uint pdnDevInst, uint dnDevInst, uint ulFlags);
[DllImport("setupapi.dll")]
private static extern int CM_Get_Sibling(out uint pdnDevInst, uint dnDevInst, uint ulFlags);
[DllImport("setupapi.dll")]
private static extern bool SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet);
// vista and higher
[DllImport("setupapi.dll", SetLastError = true, EntryPoint = "SetupDiGetDevicePropertyW")]
private static extern bool SetupDiGetDeviceProperty(IntPtr DeviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData, ref DEVPROPKEY propertyKey, out int propertyType, IntPtr propertyBuffer, int propertyBufferSize, out int requiredSize, int flags);
[StructLayout(LayoutKind.Sequential)]
private struct DEVPROPKEY
{
public Guid fmtid;
public uint pid;
// from devpkey.h
public static readonly DEVPROPKEY DEVPKEY_Device_Parent = new DEVPROPKEY { fmtid = new Guid("{4340A6C5-93FA-4706-972C-7B648008A5A7}"), pid = 8 };
public static readonly DEVPROPKEY DEVPKEY_Device_Children = new DEVPROPKEY { fmtid = new Guid("{4340A6C5-93FA-4706-972C-7B648008A5A7}"), pid = 9 };
}
private string[] GetStringListProperty(DEVPROPKEY key)
{
int type;
int size;
SetupDiGetDeviceProperty(_hDevInfo, ref _data, ref key, out type, IntPtr.Zero, 0, out size, 0);
if (size == 0)
return new string[0];
IntPtr buffer = Marshal.AllocHGlobal(size);
try
{
if (!SetupDiGetDeviceProperty(_hDevInfo, ref _data, ref key, out type, buffer, size, out size, 0))
throw new Win32Exception(Marshal.GetLastWin32Error());
List<string> strings = new List<string>();
IntPtr current = buffer;
do
{
string s = Marshal.PtrToStringUni(current);
if (string.IsNullOrEmpty(s))
break;
strings.Add(s);
current += (1 + s.Length) * 2;
}
while (true);
return strings.ToArray();
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}
private string GetStringProperty(DEVPROPKEY key)
{
int type;
int size;
SetupDiGetDeviceProperty(_hDevInfo, ref _data, ref key, out type, IntPtr.Zero, 0, out size, 0);
if (size == 0)
return null;
IntPtr buffer = Marshal.AllocHGlobal(size);
try
{
if (!SetupDiGetDeviceProperty(_hDevInfo, ref _data, ref key, out type, buffer, size, out size, 0))
throw new Win32Exception(Marshal.GetLastWin32Error());
return Marshal.PtrToStringUni(buffer);
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}
}
I've had the same problem and also browsing through the WMI did not help me out, really.
But I ended up with these few lines of code, and it works great for me:
private string GetAvailableStorageDrive()
{
foreach (var info in System.IO.DriveInfo.GetDrives())
{
if (info.DriveType == System.IO.DriveType.Removable &&
info.IsReady &&
!info.Name.Equals("A:\\"))
{
return info.Name;
}
}
return string.Empty;
}
Basically the above function looks if the DriveType is Removable andalso if the Drive is ready.
I also exclude the drive letter 'A' because in default windows environments, this is the Floppy.
Description of DriveType.Removable:
The drive is a removable storage device, such as a
floppy disk drive or a USB flash drive.
Note: As CodeCaster pointed out, this function will also return removable storage devices such as SATA. So if that's the case, you will have to look into more complex solutions as provided by others.
There's an ancient Win32 API for device enumeration, it used to be part of the installer API but has fallen into disuse - I'm only aware of its existence, notsomuch its usage, but hopefully it'll be helpful:
http://msdn.microsoft.com/en-us/library/windows/hardware/ff551015(v=vs.85).aspx
The SP_DEVICE_INTERFACE_DETAIL structure is your end-goal: it has the actual device path. The path to get there, however... It'd all be PInvoke, and fairly nasty looking from the signatures, but here are some references:
The PInvoke signatures:
http://pinvoke.net/default.aspx/setupapi/SetupDiGetClassDevs.html
http://pinvoke.net/default.aspx/setupapi/SetupDiEnumDeviceInterfaces.html
http://pinvoke.net/default.aspx/setupapi/SetupDiGetDeviceInterfaceDetail.html
The structure mappings:
http://pinvoke.net/default.aspx/Structures/SP_DEVICE_INTERFACE_DATA.html
http://pinvoke.net/default.aspx/Structures/SP_DEVICE_INTERFACE_DETAIL_DATA.html
Ooh, I found some example usage (in C++, but fairly translatable):
http://oroboro.com/usb-serial-number/
I've seen several of answers about using Handle or Process Monitor, but I would like to be able to find out in my own code (C#)
which process is locking a file.
I have a nasty feeling that I'm going to have to spelunk around in the win32 API, but if anyone has already done this and can put me on the right track, I'd really appreciate the help.
Update
Links to similar questions
How does one figure out what process locked a file using c#?
Command line tool
Across a Network
Locking a USB device
Unit test fails with locked file
deleting locked file
Long ago it was impossible to reliably get the list of processes locking a file because Windows simply did not track that information. To support the Restart Manager API, that information is now tracked.
I put together code that takes the path of a file and returns a List<Process> of all processes that are locking that file.
using System.Runtime.InteropServices;
using System.Diagnostics;
using System;
using System.Collections.Generic;
static public class FileUtil
{
[StructLayout(LayoutKind.Sequential)]
struct RM_UNIQUE_PROCESS
{
public int dwProcessId;
public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime;
}
const int RmRebootReasonNone = 0;
const int CCH_RM_MAX_APP_NAME = 255;
const int CCH_RM_MAX_SVC_NAME = 63;
enum RM_APP_TYPE
{
RmUnknownApp = 0,
RmMainWindow = 1,
RmOtherWindow = 2,
RmService = 3,
RmExplorer = 4,
RmConsole = 5,
RmCritical = 1000
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct RM_PROCESS_INFO
{
public RM_UNIQUE_PROCESS Process;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_APP_NAME + 1)]
public string strAppName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_SVC_NAME + 1)]
public string strServiceShortName;
public RM_APP_TYPE ApplicationType;
public uint AppStatus;
public uint TSSessionId;
[MarshalAs(UnmanagedType.Bool)]
public bool bRestartable;
}
[DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
static extern int RmRegisterResources(uint pSessionHandle,
UInt32 nFiles,
string[] rgsFilenames,
UInt32 nApplications,
[In] RM_UNIQUE_PROCESS[] rgApplications,
UInt32 nServices,
string[] rgsServiceNames);
[DllImport("rstrtmgr.dll", CharSet = CharSet.Auto)]
static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, string strSessionKey);
[DllImport("rstrtmgr.dll")]
static extern int RmEndSession(uint pSessionHandle);
[DllImport("rstrtmgr.dll")]
static extern int RmGetList(uint dwSessionHandle,
out uint pnProcInfoNeeded,
ref uint pnProcInfo,
[In, Out] RM_PROCESS_INFO[] rgAffectedApps,
ref uint lpdwRebootReasons);
/// <summary>
/// Find out what process(es) have a lock on the specified file.
/// </summary>
/// <param name="path">Path of the file.</param>
/// <returns>Processes locking the file</returns>
/// <remarks>See also:
/// http://msdn.microsoft.com/en-us/library/windows/desktop/aa373661(v=vs.85).aspx
/// http://wyupdate.googlecode.com/svn-history/r401/trunk/frmFilesInUse.cs (no copyright in code at time of viewing)
///
/// </remarks>
static public List<Process> WhoIsLocking(string path)
{
uint handle;
string key = Guid.NewGuid().ToString();
List<Process> processes = new List<Process>();
int res = RmStartSession(out handle, 0, key);
if (res != 0) throw new Exception("Could not begin restart session. Unable to determine file locker.");
try
{
const int ERROR_MORE_DATA = 234;
uint pnProcInfoNeeded = 0,
pnProcInfo = 0,
lpdwRebootReasons = RmRebootReasonNone;
string[] resources = new string[] { path }; // Just checking on one resource.
res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null);
if (res != 0) throw new Exception("Could not register resource.");
//Note: there's a race condition here -- the first call to RmGetList() returns
// the total number of process. However, when we call RmGetList() again to get
// the actual processes this number may have increased.
res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons);
if (res == ERROR_MORE_DATA)
{
// Create an array to store the process results
RM_PROCESS_INFO[] processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded];
pnProcInfo = pnProcInfoNeeded;
// Get the list
res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons);
if (res == 0)
{
processes = new List<Process>((int)pnProcInfo);
// Enumerate all of the results and add them to the
// list to be returned
for (int i = 0; i < pnProcInfo; i++)
{
try
{
processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId));
}
// catch the error -- in case the process is no longer running
catch (ArgumentException) { }
}
}
else throw new Exception("Could not list processes locking resource.");
}
else if (res != 0) throw new Exception("Could not list processes locking resource. Failed to get size of result.");
}
finally
{
RmEndSession(handle);
}
return processes;
}
}
Using from Limited Permission (e.g. IIS)
This call accesses the registry. If the process does not have permission to do so, you will get ERROR_WRITE_FAULT, meaning An operation was unable to read or write to the registry. You could selectively grant permission to your restricted account to the necessary part of the registry. It is more secure though to have your limited access process set a flag (e.g. in the database or the file system, or by using an interprocess communication mechanism such as queue or named pipe) and have a second process call the Restart Manager API.
Granting other-than-minimal permissions to the IIS user is a security risk.
This question had an original answer that is now over 7 years old. That code is preserved at https://gist.github.com/i-e-b/2290426
This old version might work for you if you need to use Windows XP for some reason.
A much better answer is at How to check for file lock?
I've replicated Eric J's answer below (with using statements added, and class & method names to match the old code that was here) Please note that the comments to this answer may be out-of-date.
Research by user 'Walkman' is ongoing to improve the older code, as there are some conditions where the Restart Manager does not list all locks. See Github repo: https://github.com/Walkman100/FileLocks
Use like:
List<Process> locks = Win32Processes.GetProcessesLockingFile(#"C:\Hello.docx");
Code:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace FileLockInfo
{
public static class Win32Processes
{
/// <summary>
/// Find out what process(es) have a lock on the specified file.
/// </summary>
/// <param name="path">Path of the file.</param>
/// <returns>Processes locking the file</returns>
/// <remarks>See also:
/// http://msdn.microsoft.com/en-us/library/windows/desktop/aa373661(v=vs.85).aspx
/// http://wyupdate.googlecode.com/svn-history/r401/trunk/frmFilesInUse.cs (no copyright in code at time of viewing)
/// </remarks>
public static List<Process> GetProcessesLockingFile(string path)
{
uint handle;
string key = Guid.NewGuid().ToString();
int res = RmStartSession(out handle, 0, key);
if (res != 0) throw new Exception("Could not begin restart session. Unable to determine file locker.");
try
{
const int MORE_DATA = 234;
uint pnProcInfoNeeded, pnProcInfo = 0, lpdwRebootReasons = RmRebootReasonNone;
string[] resources = {path}; // Just checking on one resource.
res = RmRegisterResources(handle, (uint) resources.Length, resources, 0, null, 0, null);
if (res != 0) throw new Exception("Could not register resource.");
//Note: there's a race condition here -- the first call to RmGetList() returns
// the total number of process. However, when we call RmGetList() again to get
// the actual processes this number may have increased.
res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons);
if (res == MORE_DATA)
{
return EnumerateProcesses(pnProcInfoNeeded, handle, lpdwRebootReasons);
}
else if (res != 0) throw new Exception("Could not list processes locking resource. Failed to get size of result.");
}
finally
{
RmEndSession(handle);
}
return new List<Process>();
}
[StructLayout(LayoutKind.Sequential)]
public struct RM_UNIQUE_PROCESS
{
public int dwProcessId;
public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime;
}
const int RmRebootReasonNone = 0;
const int CCH_RM_MAX_APP_NAME = 255;
const int CCH_RM_MAX_SVC_NAME = 63;
public enum RM_APP_TYPE
{
RmUnknownApp = 0,
RmMainWindow = 1,
RmOtherWindow = 2,
RmService = 3,
RmExplorer = 4,
RmConsole = 5,
RmCritical = 1000
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct RM_PROCESS_INFO
{
public RM_UNIQUE_PROCESS Process;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_APP_NAME + 1)] public string strAppName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_SVC_NAME + 1)] public string strServiceShortName;
public RM_APP_TYPE ApplicationType;
public uint AppStatus;
public uint TSSessionId;
[MarshalAs(UnmanagedType.Bool)] public bool bRestartable;
}
[DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
static extern int RmRegisterResources(uint pSessionHandle, uint nFiles, string[] rgsFilenames,
uint nApplications, [In] RM_UNIQUE_PROCESS[] rgApplications, uint nServices,
string[] rgsServiceNames);
[DllImport("rstrtmgr.dll", CharSet = CharSet.Auto)]
static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, string strSessionKey);
[DllImport("rstrtmgr.dll")]
static extern int RmEndSession(uint pSessionHandle);
[DllImport("rstrtmgr.dll")]
static extern int RmGetList(uint dwSessionHandle, out uint pnProcInfoNeeded,
ref uint pnProcInfo, [In, Out] RM_PROCESS_INFO[] rgAffectedApps,
ref uint lpdwRebootReasons);
private static List<Process> EnumerateProcesses(uint pnProcInfoNeeded, uint handle, uint lpdwRebootReasons)
{
var processes = new List<Process>(10);
// Create an array to store the process results
var processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded];
var pnProcInfo = pnProcInfoNeeded;
// Get the list
var res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons);
if (res != 0) throw new Exception("Could not list processes locking resource.");
for (int i = 0; i < pnProcInfo; i++)
{
try
{
processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId));
}
catch (ArgumentException) { } // catch the error -- in case the process is no longer running
}
return processes;
}
}
}
It is very complex to invoke Win32 from C#.
You should use the tool Handle.exe.
After that your C# code have to be the following:
string fileName = #"c:\aaa.doc";//Path to locked file
Process tool = new Process();
tool.StartInfo.FileName = "handle.exe";
tool.StartInfo.Arguments = fileName+" /accepteula";
tool.StartInfo.UseShellExecute = false;
tool.StartInfo.RedirectStandardOutput = true;
tool.Start();
tool.WaitForExit();
string outputTool = tool.StandardOutput.ReadToEnd();
string matchPattern = #"(?<=\s+pid:\s+)\b(\d+)\b(?=\s+)";
foreach(Match match in Regex.Matches(outputTool, matchPattern))
{
Process.GetProcessById(int.Parse(match.Value)).Kill();
}
One of the good things about handle.exe is that you can run it as a subprocess and parse the output.
We do this in our deployment script - works like a charm.
The code I found here,
https://vmccontroller.svn.codeplex.com/svn/VmcController/VmcServices/DetectOpenFiles.cs
Works for me much better than the code provided by Iain. Iain's code seemed to be acquiring a lock of its own. Here is my slightly modified version of the code above modified to return the string path of the files locked instead of the FileSystemInfo object,
using System;
using System.Collections.Generic;
//using System.EnterpriseServices;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using Microsoft.Win32.SafeHandles;
namespace Crmc.Core.BuildTasks
{
using System.Diagnostics;
using System.Linq;
#region ENUMs
internal enum NT_STATUS
{
STATUS_SUCCESS = 0x00000000,
STATUS_BUFFER_OVERFLOW = unchecked((int)0x80000005L),
STATUS_INFO_LENGTH_MISMATCH = unchecked((int)0xC0000004L)
}
internal enum SYSTEM_INFORMATION_CLASS
{
SystemBasicInformation = 0,
SystemPerformanceInformation = 2,
SystemTimeOfDayInformation = 3,
SystemProcessInformation = 5,
SystemProcessorPerformanceInformation = 8,
SystemHandleInformation = 16,
SystemInterruptInformation = 23,
SystemExceptionInformation = 33,
SystemRegistryQuotaInformation = 37,
SystemLookasideInformation = 45
}
internal enum OBJECT_INFORMATION_CLASS
{
ObjectBasicInformation = 0,
ObjectNameInformation = 1,
ObjectTypeInformation = 2,
ObjectAllTypesInformation = 3,
ObjectHandleInformation = 4
}
[Flags]
internal enum ProcessAccessRights
{
PROCESS_DUP_HANDLE = 0x00000040
}
[Flags]
internal enum DuplicateHandleOptions
{
DUPLICATE_CLOSE_SOURCE = 0x1,
DUPLICATE_SAME_ACCESS = 0x2
}
#endregion
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
internal sealed class SafeObjectHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private SafeObjectHandle()
: base(true)
{ }
internal SafeObjectHandle(IntPtr preexistingHandle, bool ownsHandle)
: base(ownsHandle)
{
base.SetHandle(preexistingHandle);
}
protected override bool ReleaseHandle()
{
return NativeMethods.CloseHandle(base.handle);
}
}
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
internal sealed class SafeProcessHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private SafeProcessHandle()
: base(true)
{ }
internal SafeProcessHandle(IntPtr preexistingHandle, bool ownsHandle)
: base(ownsHandle)
{
base.SetHandle(preexistingHandle);
}
protected override bool ReleaseHandle()
{
return NativeMethods.CloseHandle(base.handle);
}
}
#region Native Methods
internal static class NativeMethods
{
[DllImport("ntdll.dll")]
internal static extern NT_STATUS NtQuerySystemInformation(
[In] SYSTEM_INFORMATION_CLASS SystemInformationClass,
[In] IntPtr SystemInformation,
[In] int SystemInformationLength,
[Out] out int ReturnLength);
[DllImport("ntdll.dll")]
internal static extern NT_STATUS NtQueryObject(
[In] IntPtr Handle,
[In] OBJECT_INFORMATION_CLASS ObjectInformationClass,
[In] IntPtr ObjectInformation,
[In] int ObjectInformationLength,
[Out] out int ReturnLength);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern SafeProcessHandle OpenProcess(
[In] ProcessAccessRights dwDesiredAccess,
[In, MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
[In] int dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool DuplicateHandle(
[In] IntPtr hSourceProcessHandle,
[In] IntPtr hSourceHandle,
[In] IntPtr hTargetProcessHandle,
[Out] out SafeObjectHandle lpTargetHandle,
[In] int dwDesiredAccess,
[In, MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
[In] DuplicateHandleOptions dwOptions);
[DllImport("kernel32.dll")]
internal static extern IntPtr GetCurrentProcess();
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern int GetProcessId(
[In] IntPtr Process);
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool CloseHandle(
[In] IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern int QueryDosDevice(
[In] string lpDeviceName,
[Out] StringBuilder lpTargetPath,
[In] int ucchMax);
}
#endregion
//[ComVisible(true), EventTrackingEnabled(true)]
public class DetectOpenFiles// : ServicedComponent
{
private static Dictionary<string, string> deviceMap;
private const string networkDevicePrefix = "\\Device\\LanmanRedirector\\";
private const int MAX_PATH = 260;
private enum SystemHandleType
{
OB_TYPE_UNKNOWN = 0,
OB_TYPE_TYPE = 1,
OB_TYPE_DIRECTORY,
OB_TYPE_SYMBOLIC_LINK,
OB_TYPE_TOKEN,
OB_TYPE_PROCESS,
OB_TYPE_THREAD,
OB_TYPE_UNKNOWN_7,
OB_TYPE_EVENT,
OB_TYPE_EVENT_PAIR,
OB_TYPE_MUTANT,
OB_TYPE_UNKNOWN_11,
OB_TYPE_SEMAPHORE,
OB_TYPE_TIMER,
OB_TYPE_PROFILE,
OB_TYPE_WINDOW_STATION,
OB_TYPE_DESKTOP,
OB_TYPE_SECTION,
OB_TYPE_KEY,
OB_TYPE_PORT,
OB_TYPE_WAITABLE_PORT,
OB_TYPE_UNKNOWN_21,
OB_TYPE_UNKNOWN_22,
OB_TYPE_UNKNOWN_23,
OB_TYPE_UNKNOWN_24,
//OB_TYPE_CONTROLLER,
//OB_TYPE_DEVICE,
//OB_TYPE_DRIVER,
OB_TYPE_IO_COMPLETION,
OB_TYPE_FILE
};
private const int handleTypeTokenCount = 27;
private static readonly string[] handleTypeTokens = new string[] {
"", "", "Directory", "SymbolicLink", "Token",
"Process", "Thread", "Unknown7", "Event", "EventPair", "Mutant",
"Unknown11", "Semaphore", "Timer", "Profile", "WindowStation",
"Desktop", "Section", "Key", "Port", "WaitablePort",
"Unknown21", "Unknown22", "Unknown23", "Unknown24",
"IoCompletion", "File"
};
[StructLayout(LayoutKind.Sequential)]
private struct SYSTEM_HANDLE_ENTRY
{
public int OwnerPid;
public byte ObjectType;
public byte HandleFlags;
public short HandleValue;
public int ObjectPointer;
public int AccessMask;
}
/// <summary>
/// Gets the open files enumerator.
/// </summary>
/// <param name="processId">The process id.</param>
/// <returns></returns>
public static IEnumerable<String> GetOpenFilesEnumerator(int processId)
{
return new OpenFiles(processId);
}
public static List<Process> GetProcessesUsingFile(string fName)
{
List<Process> result = new List<Process>();
foreach (var p in Process.GetProcesses())
{
try
{
if (DetectOpenFiles.GetOpenFilesEnumerator(p.Id).Contains(fName))
{
result.Add(p);
}
}
catch { }//some processes will fail
}
return result;
}
private sealed class OpenFiles : IEnumerable<String>
{
private readonly int processId;
internal OpenFiles(int processId)
{
this.processId = processId;
}
#region IEnumerable<FileSystemInfo> Members
public IEnumerator<String> GetEnumerator()
{
NT_STATUS ret;
int length = 0x10000;
// Loop, probing for required memory.
do
{
IntPtr ptr = IntPtr.Zero;
RuntimeHelpers.PrepareConstrainedRegions();
try
{
RuntimeHelpers.PrepareConstrainedRegions();
try { }
finally
{
// CER guarantees that the address of the allocated
// memory is actually assigned to ptr if an
// asynchronous exception occurs.
ptr = Marshal.AllocHGlobal(length);
}
int returnLength;
ret = NativeMethods.NtQuerySystemInformation(SYSTEM_INFORMATION_CLASS.SystemHandleInformation, ptr, length, out returnLength);
if (ret == NT_STATUS.STATUS_INFO_LENGTH_MISMATCH)
{
// Round required memory up to the nearest 64KB boundary.
length = ((returnLength + 0xffff) & ~0xffff);
}
else if (ret == NT_STATUS.STATUS_SUCCESS)
{
int handleCount = Marshal.ReadInt32(ptr);
int offset = sizeof(int);
int size = Marshal.SizeOf(typeof(SYSTEM_HANDLE_ENTRY));
for (int i = 0; i < handleCount; i++)
{
SYSTEM_HANDLE_ENTRY handleEntry = (SYSTEM_HANDLE_ENTRY)Marshal.PtrToStructure((IntPtr)((int)ptr + offset), typeof(SYSTEM_HANDLE_ENTRY));
if (handleEntry.OwnerPid == processId)
{
IntPtr handle = (IntPtr)handleEntry.HandleValue;
SystemHandleType handleType;
if (GetHandleType(handle, handleEntry.OwnerPid, out handleType) && handleType == SystemHandleType.OB_TYPE_FILE)
{
string devicePath;
if (GetFileNameFromHandle(handle, handleEntry.OwnerPid, out devicePath))
{
string dosPath;
if (ConvertDevicePathToDosPath(devicePath, out dosPath))
{
if (File.Exists(dosPath))
{
yield return dosPath; // return new FileInfo(dosPath);
}
else if (Directory.Exists(dosPath))
{
yield return dosPath; // new DirectoryInfo(dosPath);
}
}
}
}
}
offset += size;
}
}
}
finally
{
// CER guarantees that the allocated memory is freed,
// if an asynchronous exception occurs.
Marshal.FreeHGlobal(ptr);
//sw.Flush();
//sw.Close();
}
}
while (ret == NT_STATUS.STATUS_INFO_LENGTH_MISMATCH);
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
#region Private Members
private static bool GetFileNameFromHandle(IntPtr handle, int processId, out string fileName)
{
IntPtr currentProcess = NativeMethods.GetCurrentProcess();
bool remote = (processId != NativeMethods.GetProcessId(currentProcess));
SafeProcessHandle processHandle = null;
SafeObjectHandle objectHandle = null;
try
{
if (remote)
{
processHandle = NativeMethods.OpenProcess(ProcessAccessRights.PROCESS_DUP_HANDLE, true, processId);
if (NativeMethods.DuplicateHandle(processHandle.DangerousGetHandle(), handle, currentProcess, out objectHandle, 0, false, DuplicateHandleOptions.DUPLICATE_SAME_ACCESS))
{
handle = objectHandle.DangerousGetHandle();
}
}
return GetFileNameFromHandle(handle, out fileName, 200);
}
finally
{
if (remote)
{
if (processHandle != null)
{
processHandle.Close();
}
if (objectHandle != null)
{
objectHandle.Close();
}
}
}
}
private static bool GetFileNameFromHandle(IntPtr handle, out string fileName, int wait)
{
using (FileNameFromHandleState f = new FileNameFromHandleState(handle))
{
ThreadPool.QueueUserWorkItem(new WaitCallback(GetFileNameFromHandle), f);
if (f.WaitOne(wait))
{
fileName = f.FileName;
return f.RetValue;
}
else
{
fileName = string.Empty;
return false;
}
}
}
private class FileNameFromHandleState : IDisposable
{
private ManualResetEvent _mr;
private IntPtr _handle;
private string _fileName;
private bool _retValue;
public IntPtr Handle
{
get
{
return _handle;
}
}
public string FileName
{
get
{
return _fileName;
}
set
{
_fileName = value;
}
}
public bool RetValue
{
get
{
return _retValue;
}
set
{
_retValue = value;
}
}
public FileNameFromHandleState(IntPtr handle)
{
_mr = new ManualResetEvent(false);
this._handle = handle;
}
public bool WaitOne(int wait)
{
return _mr.WaitOne(wait, false);
}
public void Set()
{
try
{
_mr.Set();
}
catch{}
}
#region IDisposable Members
public void Dispose()
{
if (_mr != null)
_mr.Close();
}
#endregion
}
private static void GetFileNameFromHandle(object state)
{
FileNameFromHandleState s = (FileNameFromHandleState)state;
string fileName;
s.RetValue = GetFileNameFromHandle(s.Handle, out fileName);
s.FileName = fileName;
s.Set();
}
private static bool GetFileNameFromHandle(IntPtr handle, out string fileName)
{
IntPtr ptr = IntPtr.Zero;
RuntimeHelpers.PrepareConstrainedRegions();
try
{
int length = 0x200; // 512 bytes
RuntimeHelpers.PrepareConstrainedRegions();
try { }
finally
{
// CER guarantees the assignment of the allocated
// memory address to ptr, if an ansynchronous exception
// occurs.
ptr = Marshal.AllocHGlobal(length);
}
NT_STATUS ret = NativeMethods.NtQueryObject(handle, OBJECT_INFORMATION_CLASS.ObjectNameInformation, ptr, length, out length);
if (ret == NT_STATUS.STATUS_BUFFER_OVERFLOW)
{
RuntimeHelpers.PrepareConstrainedRegions();
try { }
finally
{
// CER guarantees that the previous allocation is freed,
// and that the newly allocated memory address is
// assigned to ptr if an asynchronous exception occurs.
Marshal.FreeHGlobal(ptr);
ptr = Marshal.AllocHGlobal(length);
}
ret = NativeMethods.NtQueryObject(handle, OBJECT_INFORMATION_CLASS.ObjectNameInformation, ptr, length, out length);
}
if (ret == NT_STATUS.STATUS_SUCCESS)
{
fileName = Marshal.PtrToStringUni((IntPtr)((int)ptr + 8), (length - 9) / 2);
return fileName.Length != 0;
}
}
finally
{
// CER guarantees that the allocated memory is freed,
// if an asynchronous exception occurs.
Marshal.FreeHGlobal(ptr);
}
fileName = string.Empty;
return false;
}
private static bool GetHandleType(IntPtr handle, int processId, out SystemHandleType handleType)
{
string token = GetHandleTypeToken(handle, processId);
return GetHandleTypeFromToken(token, out handleType);
}
private static bool GetHandleType(IntPtr handle, out SystemHandleType handleType)
{
string token = GetHandleTypeToken(handle);
return GetHandleTypeFromToken(token, out handleType);
}
private static bool GetHandleTypeFromToken(string token, out SystemHandleType handleType)
{
for (int i = 1; i < handleTypeTokenCount; i++)
{
if (handleTypeTokens[i] == token)
{
handleType = (SystemHandleType)i;
return true;
}
}
handleType = SystemHandleType.OB_TYPE_UNKNOWN;
return false;
}
private static string GetHandleTypeToken(IntPtr handle, int processId)
{
IntPtr currentProcess = NativeMethods.GetCurrentProcess();
bool remote = (processId != NativeMethods.GetProcessId(currentProcess));
SafeProcessHandle processHandle = null;
SafeObjectHandle objectHandle = null;
try
{
if (remote)
{
processHandle = NativeMethods.OpenProcess(ProcessAccessRights.PROCESS_DUP_HANDLE, true, processId);
if (NativeMethods.DuplicateHandle(processHandle.DangerousGetHandle(), handle, currentProcess, out objectHandle, 0, false, DuplicateHandleOptions.DUPLICATE_SAME_ACCESS))
{
handle = objectHandle.DangerousGetHandle();
}
}
return GetHandleTypeToken(handle);
}
finally
{
if (remote)
{
if (processHandle != null)
{
processHandle.Close();
}
if (objectHandle != null)
{
objectHandle.Close();
}
}
}
}
private static string GetHandleTypeToken(IntPtr handle)
{
int length;
NativeMethods.NtQueryObject(handle, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, IntPtr.Zero, 0, out length);
IntPtr ptr = IntPtr.Zero;
RuntimeHelpers.PrepareConstrainedRegions();
try
{
RuntimeHelpers.PrepareConstrainedRegions();
try { }
finally
{
ptr = Marshal.AllocHGlobal(length);
}
if (NativeMethods.NtQueryObject(handle, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, ptr, length, out length) == NT_STATUS.STATUS_SUCCESS)
{
return Marshal.PtrToStringUni((IntPtr)((int)ptr + 0x60));
}
}
finally
{
Marshal.FreeHGlobal(ptr);
}
return string.Empty;
}
private static bool ConvertDevicePathToDosPath(string devicePath, out string dosPath)
{
EnsureDeviceMap();
int i = devicePath.Length;
while (i > 0 && (i = devicePath.LastIndexOf('\\', i - 1)) != -1)
{
string drive;
if (deviceMap.TryGetValue(devicePath.Substring(0, i), out drive))
{
dosPath = string.Concat(drive, devicePath.Substring(i));
return dosPath.Length != 0;
}
}
dosPath = string.Empty;
return false;
}
private static void EnsureDeviceMap()
{
if (deviceMap == null)
{
Dictionary<string, string> localDeviceMap = BuildDeviceMap();
Interlocked.CompareExchange<Dictionary<string, string>>(ref deviceMap, localDeviceMap, null);
}
}
private static Dictionary<string, string> BuildDeviceMap()
{
string[] logicalDrives = Environment.GetLogicalDrives();
Dictionary<string, string> localDeviceMap = new Dictionary<string, string>(logicalDrives.Length);
StringBuilder lpTargetPath = new StringBuilder(MAX_PATH);
foreach (string drive in logicalDrives)
{
string lpDeviceName = drive.Substring(0, 2);
NativeMethods.QueryDosDevice(lpDeviceName, lpTargetPath, MAX_PATH);
localDeviceMap.Add(NormalizeDeviceName(lpTargetPath.ToString()), lpDeviceName);
}
localDeviceMap.Add(networkDevicePrefix.Substring(0, networkDevicePrefix.Length - 1), "\\");
return localDeviceMap;
}
private static string NormalizeDeviceName(string deviceName)
{
if (string.Compare(deviceName, 0, networkDevicePrefix, 0, networkDevicePrefix.Length, StringComparison.InvariantCulture) == 0)
{
string shareName = deviceName.Substring(deviceName.IndexOf('\\', networkDevicePrefix.Length) + 1);
return string.Concat(networkDevicePrefix, shareName);
}
return deviceName;
}
#endregion
}
}
Not very straightforward, but on Windows Vista and above you can use the Restart Manager APIs to see who is using a file. Internet Explorer caches settings includes details on using this to detect which process has iexplore.exe open.
Omitting a lot of detail:
// Start an RM session
RmStartSession(&sessionHandle, 0, sessionKey);
// Register the file you are checking
RmRegisterResources(sessionHandle, 1, filePathArray, 0, NULL, 0, NULL);
// Get all processes that have that file open.
RmGetList(sessionHAndle, &nProcInfoNeeded, &nProcInfo, processes, &rebootReason);
RmEndSession(sessionHandle);
Handle, from Windows Sysinternals. This is a free command-line utility provided by Microsoft.
You could run it, and parse the result.
I had issues with stefan's solution. Below is a modified version which seems to work well.
using System;
using System.Collections;
using System.Diagnostics;
using System.Management;
using System.IO;
static class Module1
{
static internal ArrayList myProcessArray = new ArrayList();
private static Process myProcess;
public static void Main()
{
string strFile = "c:\\windows\\system32\\msi.dll";
ArrayList a = getFileProcesses(strFile);
foreach (Process p in a)
{
Debug.Print(p.ProcessName);
}
}
private static ArrayList getFileProcesses(string strFile)
{
myProcessArray.Clear();
Process[] processes = Process.GetProcesses();
int i = 0;
for (i = 0; i <= processes.GetUpperBound(0) - 1; i++)
{
myProcess = processes[i];
//if (!myProcess.HasExited) //This will cause an "Access is denied" error
if (myProcess.Threads.Count > 0)
{
try
{
ProcessModuleCollection modules = myProcess.Modules;
int j = 0;
for (j = 0; j <= modules.Count - 1; j++)
{
if ((modules[j].FileName.ToLower().CompareTo(strFile.ToLower()) == 0))
{
myProcessArray.Add(myProcess);
break;
// TODO: might not be correct. Was : Exit For
}
}
}
catch (Exception exception)
{
//MsgBox(("Error : " & exception.Message))
}
}
}
return myProcessArray;
}
}
UPDATE
If you just want to know which process(es) are locking a particular DLL, you can execute and parse the output of tasklist /m YourDllName.dll. Works on Windows XP and later. See
What does this do? tasklist /m "mscor*"
This works for DLLs locked by other processes. This routine will not find out for example that a text file is locked by a word process.
C#:
using System.Management;
using System.IO;
static class Module1
{
static internal ArrayList myProcessArray = new ArrayList();
private static Process myProcess;
public static void Main()
{
string strFile = "c:\\windows\\system32\\msi.dll";
ArrayList a = getFileProcesses(strFile);
foreach (Process p in a) {
Debug.Print(p.ProcessName);
}
}
private static ArrayList getFileProcesses(string strFile)
{
myProcessArray.Clear();
Process[] processes = Process.GetProcesses;
int i = 0;
for (i = 0; i <= processes.GetUpperBound(0) - 1; i++) {
myProcess = processes(i);
if (!myProcess.HasExited) {
try {
ProcessModuleCollection modules = myProcess.Modules;
int j = 0;
for (j = 0; j <= modules.Count - 1; j++) {
if ((modules.Item(j).FileName.ToLower.CompareTo(strFile.ToLower) == 0)) {
myProcessArray.Add(myProcess);
break; // TODO: might not be correct. Was : Exit For
}
}
}
catch (Exception exception) {
}
//MsgBox(("Error : " & exception.Message))
}
}
return myProcessArray;
}
}
VB.Net:
Imports System.Management
Imports System.IO
Module Module1
Friend myProcessArray As New ArrayList
Private myProcess As Process
Sub Main()
Dim strFile As String = "c:\windows\system32\msi.dll"
Dim a As ArrayList = getFileProcesses(strFile)
For Each p As Process In a
Debug.Print(p.ProcessName)
Next
End Sub
Private Function getFileProcesses(ByVal strFile As String) As ArrayList
myProcessArray.Clear()
Dim processes As Process() = Process.GetProcesses
Dim i As Integer
For i = 0 To processes.GetUpperBound(0) - 1
myProcess = processes(i)
If Not myProcess.HasExited Then
Try
Dim modules As ProcessModuleCollection = myProcess.Modules
Dim j As Integer
For j = 0 To modules.Count - 1
If (modules.Item(j).FileName.ToLower.CompareTo(strFile.ToLower) = 0) Then
myProcessArray.Add(myProcess)
Exit For
End If
Next j
Catch exception As Exception
'MsgBox(("Error : " & exception.Message))
End Try
End If
Next i
Return myProcessArray
End Function
End Module
The following was produced based on Iain Ballard's code dump. It is broken: it will occasionally lock up when you retrieve the handle name. This code doesn't contain any work-arounds for that issue, and .NET leaves few options: Thread.Abort can no longer abort a thread that's currently in a native method.
So, with that disclaimer, here is the code to retrieve handles which has been adapted to work (apart from the occasional lock-up) both in 32 and 64 bit modes:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
namespace BrokenHandleRetrieval
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Enumerates open handles.");
Console.WriteLine("This *will* lock up on calling HandleInfo.Name from time to time. Thread.Abort() won't help.");
foreach (var hi in HandleUtil.GetHandles().Where(hi => hi.Type == HandleType.File))
Console.WriteLine("pid: " + hi.ProcessId + ", name: " + hi.Name);
}
}
public enum HandleType
{
Unknown,
Other,
File, Directory, SymbolicLink, Key,
Process, Thread, Job, Session, WindowStation,
Timer, Desktop, Semaphore, Token,
Mutant, Section, Event, KeyedEvent, IoCompletion, IoCompletionReserve,
TpWorkerFactory, AlpcPort, WmiGuid, UserApcReserve,
}
public class HandleInfo
{
public int ProcessId { get; private set; }
public ushort Handle { get; private set; }
public int GrantedAccess { get; private set; }
public byte RawType { get; private set; }
public HandleInfo(int processId, ushort handle, int grantedAccess, byte rawType)
{
ProcessId = processId;
Handle = handle;
GrantedAccess = grantedAccess;
RawType = rawType;
}
private static Dictionary<byte, string> _rawTypeMap = new Dictionary<byte, string>();
private string _name, _typeStr;
private HandleType _type;
public string Name { get { if (_name == null) initTypeAndName(); return _name; } }
public string TypeString { get { if (_typeStr == null) initType(); return _typeStr; } }
public HandleType Type { get { if (_typeStr == null) initType(); return _type; } }
private void initType()
{
if (_rawTypeMap.ContainsKey(RawType))
{
_typeStr = _rawTypeMap[RawType];
_type = HandleTypeFromString(_typeStr);
}
else
initTypeAndName();
}
bool _typeAndNameAttempted = false;
private void initTypeAndName()
{
if (_typeAndNameAttempted)
return;
_typeAndNameAttempted = true;
IntPtr sourceProcessHandle = IntPtr.Zero;
IntPtr handleDuplicate = IntPtr.Zero;
try
{
sourceProcessHandle = NativeMethods.OpenProcess(0x40 /* dup_handle */, true, ProcessId);
// To read info about a handle owned by another process we must duplicate it into ours
// For simplicity, current process handles will also get duplicated; remember that process handles cannot be compared for equality
if (!NativeMethods.DuplicateHandle(sourceProcessHandle, (IntPtr) Handle, NativeMethods.GetCurrentProcess(), out handleDuplicate, 0, false, 2 /* same_access */))
return;
// Query the object type
if (_rawTypeMap.ContainsKey(RawType))
_typeStr = _rawTypeMap[RawType];
else
{
int length;
NativeMethods.NtQueryObject(handleDuplicate, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, IntPtr.Zero, 0, out length);
IntPtr ptr = IntPtr.Zero;
try
{
ptr = Marshal.AllocHGlobal(length);
if (NativeMethods.NtQueryObject(handleDuplicate, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, ptr, length, out length) != NT_STATUS.STATUS_SUCCESS)
return;
_typeStr = Marshal.PtrToStringUni((IntPtr) ((int) ptr + 0x58 + 2 * IntPtr.Size));
_rawTypeMap[RawType] = _typeStr;
}
finally
{
Marshal.FreeHGlobal(ptr);
}
}
_type = HandleTypeFromString(_typeStr);
// Query the object name
if (_typeStr != null && GrantedAccess != 0x0012019f && GrantedAccess != 0x00120189 && GrantedAccess != 0x120089) // don't query some objects that could get stuck
{
int length;
NativeMethods.NtQueryObject(handleDuplicate, OBJECT_INFORMATION_CLASS.ObjectNameInformation, IntPtr.Zero, 0, out length);
IntPtr ptr = IntPtr.Zero;
try
{
ptr = Marshal.AllocHGlobal(length);
if (NativeMethods.NtQueryObject(handleDuplicate, OBJECT_INFORMATION_CLASS.ObjectNameInformation, ptr, length, out length) != NT_STATUS.STATUS_SUCCESS)
return;
_name = Marshal.PtrToStringUni((IntPtr) ((int) ptr + 2 * IntPtr.Size));
}
finally
{
Marshal.FreeHGlobal(ptr);
}
}
}
finally
{
NativeMethods.CloseHandle(sourceProcessHandle);
if (handleDuplicate != IntPtr.Zero)
NativeMethods.CloseHandle(handleDuplicate);
}
}
public static HandleType HandleTypeFromString(string typeStr)
{
switch (typeStr)
{
case null: return HandleType.Unknown;
case "File": return HandleType.File;
case "IoCompletion": return HandleType.IoCompletion;
case "TpWorkerFactory": return HandleType.TpWorkerFactory;
case "ALPC Port": return HandleType.AlpcPort;
case "Event": return HandleType.Event;
case "Section": return HandleType.Section;
case "Directory": return HandleType.Directory;
case "KeyedEvent": return HandleType.KeyedEvent;
case "Process": return HandleType.Process;
case "Key": return HandleType.Key;
case "SymbolicLink": return HandleType.SymbolicLink;
case "Thread": return HandleType.Thread;
case "Mutant": return HandleType.Mutant;
case "WindowStation": return HandleType.WindowStation;
case "Timer": return HandleType.Timer;
case "Semaphore": return HandleType.Semaphore;
case "Desktop": return HandleType.Desktop;
case "Token": return HandleType.Token;
case "Job": return HandleType.Job;
case "Session": return HandleType.Session;
case "IoCompletionReserve": return HandleType.IoCompletionReserve;
case "WmiGuid": return HandleType.WmiGuid;
case "UserApcReserve": return HandleType.UserApcReserve;
default: return HandleType.Other;
}
}
}
public static class HandleUtil
{
public static IEnumerable<HandleInfo> GetHandles()
{
// Attempt to retrieve the handle information
int length = 0x10000;
IntPtr ptr = IntPtr.Zero;
try
{
while (true)
{
ptr = Marshal.AllocHGlobal(length);
int wantedLength;
var result = NativeMethods.NtQuerySystemInformation(SYSTEM_INFORMATION_CLASS.SystemHandleInformation, ptr, length, out wantedLength);
if (result == NT_STATUS.STATUS_INFO_LENGTH_MISMATCH)
{
length = Math.Max(length, wantedLength);
Marshal.FreeHGlobal(ptr);
ptr = IntPtr.Zero;
}
else if (result == NT_STATUS.STATUS_SUCCESS)
break;
else
throw new Exception("Failed to retrieve system handle information.");
}
int handleCount = IntPtr.Size == 4 ? Marshal.ReadInt32(ptr) : (int) Marshal.ReadInt64(ptr);
int offset = IntPtr.Size;
int size = Marshal.SizeOf(typeof(SystemHandleEntry));
for (int i = 0; i < handleCount; i++)
{
var struc = (SystemHandleEntry) Marshal.PtrToStructure((IntPtr) ((int) ptr + offset), typeof(SystemHandleEntry));
yield return new HandleInfo(struc.OwnerProcessId, struc.Handle, struc.GrantedAccess, struc.ObjectTypeNumber);
offset += size;
}
}
finally
{
if (ptr != IntPtr.Zero)
Marshal.FreeHGlobal(ptr);
}
}
[StructLayout(LayoutKind.Sequential)]
private struct SystemHandleEntry
{
public int OwnerProcessId;
public byte ObjectTypeNumber;
public byte Flags;
public ushort Handle;
public IntPtr Object;
public int GrantedAccess;
}
}
enum NT_STATUS
{
STATUS_SUCCESS = 0x00000000,
STATUS_BUFFER_OVERFLOW = unchecked((int) 0x80000005L),
STATUS_INFO_LENGTH_MISMATCH = unchecked((int) 0xC0000004L)
}
enum SYSTEM_INFORMATION_CLASS
{
SystemBasicInformation = 0,
SystemPerformanceInformation = 2,
SystemTimeOfDayInformation = 3,
SystemProcessInformation = 5,
SystemProcessorPerformanceInformation = 8,
SystemHandleInformation = 16,
SystemInterruptInformation = 23,
SystemExceptionInformation = 33,
SystemRegistryQuotaInformation = 37,
SystemLookasideInformation = 45
}
enum OBJECT_INFORMATION_CLASS
{
ObjectBasicInformation = 0,
ObjectNameInformation = 1,
ObjectTypeInformation = 2,
ObjectAllTypesInformation = 3,
ObjectHandleInformation = 4
}
static class NativeMethods
{
[DllImport("ntdll.dll")]
internal static extern NT_STATUS NtQuerySystemInformation(
[In] SYSTEM_INFORMATION_CLASS SystemInformationClass,
[In] IntPtr SystemInformation,
[In] int SystemInformationLength,
[Out] out int ReturnLength);
[DllImport("ntdll.dll")]
internal static extern NT_STATUS NtQueryObject(
[In] IntPtr Handle,
[In] OBJECT_INFORMATION_CLASS ObjectInformationClass,
[In] IntPtr ObjectInformation,
[In] int ObjectInformationLength,
[Out] out int ReturnLength);
[DllImport("kernel32.dll")]
internal static extern IntPtr GetCurrentProcess();
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr OpenProcess(
[In] int dwDesiredAccess,
[In, MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
[In] int dwProcessId);
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool CloseHandle(
[In] IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DuplicateHandle(
[In] IntPtr hSourceProcessHandle,
[In] IntPtr hSourceHandle,
[In] IntPtr hTargetProcessHandle,
[Out] out IntPtr lpTargetHandle,
[In] int dwDesiredAccess,
[In, MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
[In] int dwOptions);
}
}
This is probably irrelevant and if it is please someone comment but there was a work-around I've used in explorer before to get around file locks.
If a file was locked by a process that had died Windows often wouldn't let you delete it but if you created a new file of the same name somewhere else, moved it to the folder it would succeed. You could then delete the new file and all was well.
To use this for your app you'd have to be able to read the file and hold it in memory before you did this then you write it back out after you'd got rid of the old one.
Maybe it will help, maybe not but it's worth trying.
Try Unlocker. If you try and delete the file that is locked by another process, it will list the process(es) that have the file locked. You can then unlock the file by shutting down those processes.
foreach (var process in Process.GetProcessesByName("excel")) //whatever you need to close
{
if (process.MainWindowTitle.Contains("test.xlsx"))
{
process.Kill();
break;
}
}
or
foreach (var process in Process.GetProcesses())
{
if (process.MainWindowTitle.Contains("test.dat"))
{
process.Kill();
break;
}
}
I believe that you need code running in kernel mode to completely answer the question (but I haven't looked at the restart manager API).
You can enumerate all processes and their modules - so if the file you're looking for is a module (DLL, EXE, OCX...), you're good to go. But if it's a text file for example, you have to look at the kernel handle table which you cannot see from user mode. Handle.exe has a kernel driver in order to do that.
I rewrote the GetProcessesLockingFile() method in the solution. The code was not working.
For example, you have a folder "C:\folder1\folder2" and a process in folder2 (process1). If the process was running, GetProcessesLockingFile() was returning "C:\folder1\folder2". So the condition if (files.Contains(filePath)) => if ("C:\folder1\folder2".contains("C:\folder1\folder2\process1")) was never true.
So this is my solution:
public static List<Process> GetProcessesLockingFile(FileInfo file)
{
var procs = new List<Process>();
var processListSnapshot = Process.GetProcesses();
foreach (var process in processListSnapshot)
{
if (process.Id <= 4) { continue; } // system processes
List<string> paths = GetFilesLockedBy(process);
foreach (string path in paths)
{
string pathDirectory = path;
if (!pathDirectory.EndsWith(Constants.DOUBLE_BACKSLASH))
{
pathDirectory = pathDirectory + Constants.DOUBLE_BACKSLASH;
}
string lastFolderName = Path.GetFileName(Path.GetDirectoryName(pathDirectory));
if (file.FullName.Contains(lastFolderName))
{
procs.Add(process);
}
}
}
return procs;
}
Or with a string parameter:
public static List<Process> GetProcessesLockingFile(string filePath)
{
var procs = new List<Process>();
var processListSnapshot = Process.GetProcesses();
foreach (var process in processListSnapshot)
{
if (process.Id <= 4) { continue; } // system processes
List<string> paths = GetFilesLockedBy(process);
foreach (string path in paths)
{
string pathDirectory = path;
if (!pathDirectory.EndsWith(Constants.DOUBLE_BACKSLASH))
{
pathDirectory = pathDirectory + Constants.DOUBLE_BACKSLASH;
}
string lastFolderName = Path.GetFileName(Path.GetDirectoryName(pathDirectory));
if (filePath.Contains(lastFolderName))
{
procs.Add(process);
}
}
}
return procs;
}
You absolutely don't need to run in Kernel mode (!!!)
It's a Win32 FAQ since Windows 95 (!) (in C, Google groups, Win32) : read the handle table, from User mode of course, and get the PID from the File handle ...
Using dotnet core (net6) I solved this problem by using the win32 restart manager (as others have also mentioned). However some of the linked articles have elaborate code importing DLLs and calling those.
After finding an app to kill processes that lock a file written by meziantou. I found out that he publishes .Net wrappers for win32 dlls (including the restart manager).
Leveraging his work, I was able to fix this problem with the following code:
using Meziantou.Framework.Win32;
public static IEnumerable<Process> GetProcessesLockingFile(string filePath)
{
using var session = RestartManager.CreateSession();
session.RegisterFile(filePath);
return session.GetProcessesLockingResources();
}
simpler with linq:
public void KillProcessesAssociatedToFile(string file)
{
GetProcessesAssociatedToFile(file).ForEach(x =>
{
x.Kill();
x.WaitForExit(10000);
});
}
public List<Process> GetProcessesAssociatedToFile(string file)
{
return Process.GetProcesses()
.Where(x => !x.HasExited
&& x.Modules.Cast<ProcessModule>().ToList()
.Exists(y => y.FileName.ToLowerInvariant() == file.ToLowerInvariant())
).ToList();
}
I've seen several of answers about using Handle or Process Monitor, but I would like to be able to find out in my own code (C#)
which process is locking a file.
I have a nasty feeling that I'm going to have to spelunk around in the win32 API, but if anyone has already done this and can put me on the right track, I'd really appreciate the help.
Update
Links to similar questions
How does one figure out what process locked a file using c#?
Command line tool
Across a Network
Locking a USB device
Unit test fails with locked file
deleting locked file
Long ago it was impossible to reliably get the list of processes locking a file because Windows simply did not track that information. To support the Restart Manager API, that information is now tracked.
I put together code that takes the path of a file and returns a List<Process> of all processes that are locking that file.
using System.Runtime.InteropServices;
using System.Diagnostics;
using System;
using System.Collections.Generic;
static public class FileUtil
{
[StructLayout(LayoutKind.Sequential)]
struct RM_UNIQUE_PROCESS
{
public int dwProcessId;
public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime;
}
const int RmRebootReasonNone = 0;
const int CCH_RM_MAX_APP_NAME = 255;
const int CCH_RM_MAX_SVC_NAME = 63;
enum RM_APP_TYPE
{
RmUnknownApp = 0,
RmMainWindow = 1,
RmOtherWindow = 2,
RmService = 3,
RmExplorer = 4,
RmConsole = 5,
RmCritical = 1000
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct RM_PROCESS_INFO
{
public RM_UNIQUE_PROCESS Process;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_APP_NAME + 1)]
public string strAppName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_SVC_NAME + 1)]
public string strServiceShortName;
public RM_APP_TYPE ApplicationType;
public uint AppStatus;
public uint TSSessionId;
[MarshalAs(UnmanagedType.Bool)]
public bool bRestartable;
}
[DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
static extern int RmRegisterResources(uint pSessionHandle,
UInt32 nFiles,
string[] rgsFilenames,
UInt32 nApplications,
[In] RM_UNIQUE_PROCESS[] rgApplications,
UInt32 nServices,
string[] rgsServiceNames);
[DllImport("rstrtmgr.dll", CharSet = CharSet.Auto)]
static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, string strSessionKey);
[DllImport("rstrtmgr.dll")]
static extern int RmEndSession(uint pSessionHandle);
[DllImport("rstrtmgr.dll")]
static extern int RmGetList(uint dwSessionHandle,
out uint pnProcInfoNeeded,
ref uint pnProcInfo,
[In, Out] RM_PROCESS_INFO[] rgAffectedApps,
ref uint lpdwRebootReasons);
/// <summary>
/// Find out what process(es) have a lock on the specified file.
/// </summary>
/// <param name="path">Path of the file.</param>
/// <returns>Processes locking the file</returns>
/// <remarks>See also:
/// http://msdn.microsoft.com/en-us/library/windows/desktop/aa373661(v=vs.85).aspx
/// http://wyupdate.googlecode.com/svn-history/r401/trunk/frmFilesInUse.cs (no copyright in code at time of viewing)
///
/// </remarks>
static public List<Process> WhoIsLocking(string path)
{
uint handle;
string key = Guid.NewGuid().ToString();
List<Process> processes = new List<Process>();
int res = RmStartSession(out handle, 0, key);
if (res != 0) throw new Exception("Could not begin restart session. Unable to determine file locker.");
try
{
const int ERROR_MORE_DATA = 234;
uint pnProcInfoNeeded = 0,
pnProcInfo = 0,
lpdwRebootReasons = RmRebootReasonNone;
string[] resources = new string[] { path }; // Just checking on one resource.
res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null);
if (res != 0) throw new Exception("Could not register resource.");
//Note: there's a race condition here -- the first call to RmGetList() returns
// the total number of process. However, when we call RmGetList() again to get
// the actual processes this number may have increased.
res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons);
if (res == ERROR_MORE_DATA)
{
// Create an array to store the process results
RM_PROCESS_INFO[] processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded];
pnProcInfo = pnProcInfoNeeded;
// Get the list
res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons);
if (res == 0)
{
processes = new List<Process>((int)pnProcInfo);
// Enumerate all of the results and add them to the
// list to be returned
for (int i = 0; i < pnProcInfo; i++)
{
try
{
processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId));
}
// catch the error -- in case the process is no longer running
catch (ArgumentException) { }
}
}
else throw new Exception("Could not list processes locking resource.");
}
else if (res != 0) throw new Exception("Could not list processes locking resource. Failed to get size of result.");
}
finally
{
RmEndSession(handle);
}
return processes;
}
}
Using from Limited Permission (e.g. IIS)
This call accesses the registry. If the process does not have permission to do so, you will get ERROR_WRITE_FAULT, meaning An operation was unable to read or write to the registry. You could selectively grant permission to your restricted account to the necessary part of the registry. It is more secure though to have your limited access process set a flag (e.g. in the database or the file system, or by using an interprocess communication mechanism such as queue or named pipe) and have a second process call the Restart Manager API.
Granting other-than-minimal permissions to the IIS user is a security risk.
This question had an original answer that is now over 7 years old. That code is preserved at https://gist.github.com/i-e-b/2290426
This old version might work for you if you need to use Windows XP for some reason.
A much better answer is at How to check for file lock?
I've replicated Eric J's answer below (with using statements added, and class & method names to match the old code that was here) Please note that the comments to this answer may be out-of-date.
Research by user 'Walkman' is ongoing to improve the older code, as there are some conditions where the Restart Manager does not list all locks. See Github repo: https://github.com/Walkman100/FileLocks
Use like:
List<Process> locks = Win32Processes.GetProcessesLockingFile(#"C:\Hello.docx");
Code:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace FileLockInfo
{
public static class Win32Processes
{
/// <summary>
/// Find out what process(es) have a lock on the specified file.
/// </summary>
/// <param name="path">Path of the file.</param>
/// <returns>Processes locking the file</returns>
/// <remarks>See also:
/// http://msdn.microsoft.com/en-us/library/windows/desktop/aa373661(v=vs.85).aspx
/// http://wyupdate.googlecode.com/svn-history/r401/trunk/frmFilesInUse.cs (no copyright in code at time of viewing)
/// </remarks>
public static List<Process> GetProcessesLockingFile(string path)
{
uint handle;
string key = Guid.NewGuid().ToString();
int res = RmStartSession(out handle, 0, key);
if (res != 0) throw new Exception("Could not begin restart session. Unable to determine file locker.");
try
{
const int MORE_DATA = 234;
uint pnProcInfoNeeded, pnProcInfo = 0, lpdwRebootReasons = RmRebootReasonNone;
string[] resources = {path}; // Just checking on one resource.
res = RmRegisterResources(handle, (uint) resources.Length, resources, 0, null, 0, null);
if (res != 0) throw new Exception("Could not register resource.");
//Note: there's a race condition here -- the first call to RmGetList() returns
// the total number of process. However, when we call RmGetList() again to get
// the actual processes this number may have increased.
res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons);
if (res == MORE_DATA)
{
return EnumerateProcesses(pnProcInfoNeeded, handle, lpdwRebootReasons);
}
else if (res != 0) throw new Exception("Could not list processes locking resource. Failed to get size of result.");
}
finally
{
RmEndSession(handle);
}
return new List<Process>();
}
[StructLayout(LayoutKind.Sequential)]
public struct RM_UNIQUE_PROCESS
{
public int dwProcessId;
public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime;
}
const int RmRebootReasonNone = 0;
const int CCH_RM_MAX_APP_NAME = 255;
const int CCH_RM_MAX_SVC_NAME = 63;
public enum RM_APP_TYPE
{
RmUnknownApp = 0,
RmMainWindow = 1,
RmOtherWindow = 2,
RmService = 3,
RmExplorer = 4,
RmConsole = 5,
RmCritical = 1000
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct RM_PROCESS_INFO
{
public RM_UNIQUE_PROCESS Process;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_APP_NAME + 1)] public string strAppName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_SVC_NAME + 1)] public string strServiceShortName;
public RM_APP_TYPE ApplicationType;
public uint AppStatus;
public uint TSSessionId;
[MarshalAs(UnmanagedType.Bool)] public bool bRestartable;
}
[DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
static extern int RmRegisterResources(uint pSessionHandle, uint nFiles, string[] rgsFilenames,
uint nApplications, [In] RM_UNIQUE_PROCESS[] rgApplications, uint nServices,
string[] rgsServiceNames);
[DllImport("rstrtmgr.dll", CharSet = CharSet.Auto)]
static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, string strSessionKey);
[DllImport("rstrtmgr.dll")]
static extern int RmEndSession(uint pSessionHandle);
[DllImport("rstrtmgr.dll")]
static extern int RmGetList(uint dwSessionHandle, out uint pnProcInfoNeeded,
ref uint pnProcInfo, [In, Out] RM_PROCESS_INFO[] rgAffectedApps,
ref uint lpdwRebootReasons);
private static List<Process> EnumerateProcesses(uint pnProcInfoNeeded, uint handle, uint lpdwRebootReasons)
{
var processes = new List<Process>(10);
// Create an array to store the process results
var processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded];
var pnProcInfo = pnProcInfoNeeded;
// Get the list
var res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons);
if (res != 0) throw new Exception("Could not list processes locking resource.");
for (int i = 0; i < pnProcInfo; i++)
{
try
{
processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId));
}
catch (ArgumentException) { } // catch the error -- in case the process is no longer running
}
return processes;
}
}
}
It is very complex to invoke Win32 from C#.
You should use the tool Handle.exe.
After that your C# code have to be the following:
string fileName = #"c:\aaa.doc";//Path to locked file
Process tool = new Process();
tool.StartInfo.FileName = "handle.exe";
tool.StartInfo.Arguments = fileName+" /accepteula";
tool.StartInfo.UseShellExecute = false;
tool.StartInfo.RedirectStandardOutput = true;
tool.Start();
tool.WaitForExit();
string outputTool = tool.StandardOutput.ReadToEnd();
string matchPattern = #"(?<=\s+pid:\s+)\b(\d+)\b(?=\s+)";
foreach(Match match in Regex.Matches(outputTool, matchPattern))
{
Process.GetProcessById(int.Parse(match.Value)).Kill();
}
One of the good things about handle.exe is that you can run it as a subprocess and parse the output.
We do this in our deployment script - works like a charm.
The code I found here,
https://vmccontroller.svn.codeplex.com/svn/VmcController/VmcServices/DetectOpenFiles.cs
Works for me much better than the code provided by Iain. Iain's code seemed to be acquiring a lock of its own. Here is my slightly modified version of the code above modified to return the string path of the files locked instead of the FileSystemInfo object,
using System;
using System.Collections.Generic;
//using System.EnterpriseServices;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using Microsoft.Win32.SafeHandles;
namespace Crmc.Core.BuildTasks
{
using System.Diagnostics;
using System.Linq;
#region ENUMs
internal enum NT_STATUS
{
STATUS_SUCCESS = 0x00000000,
STATUS_BUFFER_OVERFLOW = unchecked((int)0x80000005L),
STATUS_INFO_LENGTH_MISMATCH = unchecked((int)0xC0000004L)
}
internal enum SYSTEM_INFORMATION_CLASS
{
SystemBasicInformation = 0,
SystemPerformanceInformation = 2,
SystemTimeOfDayInformation = 3,
SystemProcessInformation = 5,
SystemProcessorPerformanceInformation = 8,
SystemHandleInformation = 16,
SystemInterruptInformation = 23,
SystemExceptionInformation = 33,
SystemRegistryQuotaInformation = 37,
SystemLookasideInformation = 45
}
internal enum OBJECT_INFORMATION_CLASS
{
ObjectBasicInformation = 0,
ObjectNameInformation = 1,
ObjectTypeInformation = 2,
ObjectAllTypesInformation = 3,
ObjectHandleInformation = 4
}
[Flags]
internal enum ProcessAccessRights
{
PROCESS_DUP_HANDLE = 0x00000040
}
[Flags]
internal enum DuplicateHandleOptions
{
DUPLICATE_CLOSE_SOURCE = 0x1,
DUPLICATE_SAME_ACCESS = 0x2
}
#endregion
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
internal sealed class SafeObjectHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private SafeObjectHandle()
: base(true)
{ }
internal SafeObjectHandle(IntPtr preexistingHandle, bool ownsHandle)
: base(ownsHandle)
{
base.SetHandle(preexistingHandle);
}
protected override bool ReleaseHandle()
{
return NativeMethods.CloseHandle(base.handle);
}
}
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
internal sealed class SafeProcessHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private SafeProcessHandle()
: base(true)
{ }
internal SafeProcessHandle(IntPtr preexistingHandle, bool ownsHandle)
: base(ownsHandle)
{
base.SetHandle(preexistingHandle);
}
protected override bool ReleaseHandle()
{
return NativeMethods.CloseHandle(base.handle);
}
}
#region Native Methods
internal static class NativeMethods
{
[DllImport("ntdll.dll")]
internal static extern NT_STATUS NtQuerySystemInformation(
[In] SYSTEM_INFORMATION_CLASS SystemInformationClass,
[In] IntPtr SystemInformation,
[In] int SystemInformationLength,
[Out] out int ReturnLength);
[DllImport("ntdll.dll")]
internal static extern NT_STATUS NtQueryObject(
[In] IntPtr Handle,
[In] OBJECT_INFORMATION_CLASS ObjectInformationClass,
[In] IntPtr ObjectInformation,
[In] int ObjectInformationLength,
[Out] out int ReturnLength);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern SafeProcessHandle OpenProcess(
[In] ProcessAccessRights dwDesiredAccess,
[In, MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
[In] int dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool DuplicateHandle(
[In] IntPtr hSourceProcessHandle,
[In] IntPtr hSourceHandle,
[In] IntPtr hTargetProcessHandle,
[Out] out SafeObjectHandle lpTargetHandle,
[In] int dwDesiredAccess,
[In, MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
[In] DuplicateHandleOptions dwOptions);
[DllImport("kernel32.dll")]
internal static extern IntPtr GetCurrentProcess();
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern int GetProcessId(
[In] IntPtr Process);
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool CloseHandle(
[In] IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern int QueryDosDevice(
[In] string lpDeviceName,
[Out] StringBuilder lpTargetPath,
[In] int ucchMax);
}
#endregion
//[ComVisible(true), EventTrackingEnabled(true)]
public class DetectOpenFiles// : ServicedComponent
{
private static Dictionary<string, string> deviceMap;
private const string networkDevicePrefix = "\\Device\\LanmanRedirector\\";
private const int MAX_PATH = 260;
private enum SystemHandleType
{
OB_TYPE_UNKNOWN = 0,
OB_TYPE_TYPE = 1,
OB_TYPE_DIRECTORY,
OB_TYPE_SYMBOLIC_LINK,
OB_TYPE_TOKEN,
OB_TYPE_PROCESS,
OB_TYPE_THREAD,
OB_TYPE_UNKNOWN_7,
OB_TYPE_EVENT,
OB_TYPE_EVENT_PAIR,
OB_TYPE_MUTANT,
OB_TYPE_UNKNOWN_11,
OB_TYPE_SEMAPHORE,
OB_TYPE_TIMER,
OB_TYPE_PROFILE,
OB_TYPE_WINDOW_STATION,
OB_TYPE_DESKTOP,
OB_TYPE_SECTION,
OB_TYPE_KEY,
OB_TYPE_PORT,
OB_TYPE_WAITABLE_PORT,
OB_TYPE_UNKNOWN_21,
OB_TYPE_UNKNOWN_22,
OB_TYPE_UNKNOWN_23,
OB_TYPE_UNKNOWN_24,
//OB_TYPE_CONTROLLER,
//OB_TYPE_DEVICE,
//OB_TYPE_DRIVER,
OB_TYPE_IO_COMPLETION,
OB_TYPE_FILE
};
private const int handleTypeTokenCount = 27;
private static readonly string[] handleTypeTokens = new string[] {
"", "", "Directory", "SymbolicLink", "Token",
"Process", "Thread", "Unknown7", "Event", "EventPair", "Mutant",
"Unknown11", "Semaphore", "Timer", "Profile", "WindowStation",
"Desktop", "Section", "Key", "Port", "WaitablePort",
"Unknown21", "Unknown22", "Unknown23", "Unknown24",
"IoCompletion", "File"
};
[StructLayout(LayoutKind.Sequential)]
private struct SYSTEM_HANDLE_ENTRY
{
public int OwnerPid;
public byte ObjectType;
public byte HandleFlags;
public short HandleValue;
public int ObjectPointer;
public int AccessMask;
}
/// <summary>
/// Gets the open files enumerator.
/// </summary>
/// <param name="processId">The process id.</param>
/// <returns></returns>
public static IEnumerable<String> GetOpenFilesEnumerator(int processId)
{
return new OpenFiles(processId);
}
public static List<Process> GetProcessesUsingFile(string fName)
{
List<Process> result = new List<Process>();
foreach (var p in Process.GetProcesses())
{
try
{
if (DetectOpenFiles.GetOpenFilesEnumerator(p.Id).Contains(fName))
{
result.Add(p);
}
}
catch { }//some processes will fail
}
return result;
}
private sealed class OpenFiles : IEnumerable<String>
{
private readonly int processId;
internal OpenFiles(int processId)
{
this.processId = processId;
}
#region IEnumerable<FileSystemInfo> Members
public IEnumerator<String> GetEnumerator()
{
NT_STATUS ret;
int length = 0x10000;
// Loop, probing for required memory.
do
{
IntPtr ptr = IntPtr.Zero;
RuntimeHelpers.PrepareConstrainedRegions();
try
{
RuntimeHelpers.PrepareConstrainedRegions();
try { }
finally
{
// CER guarantees that the address of the allocated
// memory is actually assigned to ptr if an
// asynchronous exception occurs.
ptr = Marshal.AllocHGlobal(length);
}
int returnLength;
ret = NativeMethods.NtQuerySystemInformation(SYSTEM_INFORMATION_CLASS.SystemHandleInformation, ptr, length, out returnLength);
if (ret == NT_STATUS.STATUS_INFO_LENGTH_MISMATCH)
{
// Round required memory up to the nearest 64KB boundary.
length = ((returnLength + 0xffff) & ~0xffff);
}
else if (ret == NT_STATUS.STATUS_SUCCESS)
{
int handleCount = Marshal.ReadInt32(ptr);
int offset = sizeof(int);
int size = Marshal.SizeOf(typeof(SYSTEM_HANDLE_ENTRY));
for (int i = 0; i < handleCount; i++)
{
SYSTEM_HANDLE_ENTRY handleEntry = (SYSTEM_HANDLE_ENTRY)Marshal.PtrToStructure((IntPtr)((int)ptr + offset), typeof(SYSTEM_HANDLE_ENTRY));
if (handleEntry.OwnerPid == processId)
{
IntPtr handle = (IntPtr)handleEntry.HandleValue;
SystemHandleType handleType;
if (GetHandleType(handle, handleEntry.OwnerPid, out handleType) && handleType == SystemHandleType.OB_TYPE_FILE)
{
string devicePath;
if (GetFileNameFromHandle(handle, handleEntry.OwnerPid, out devicePath))
{
string dosPath;
if (ConvertDevicePathToDosPath(devicePath, out dosPath))
{
if (File.Exists(dosPath))
{
yield return dosPath; // return new FileInfo(dosPath);
}
else if (Directory.Exists(dosPath))
{
yield return dosPath; // new DirectoryInfo(dosPath);
}
}
}
}
}
offset += size;
}
}
}
finally
{
// CER guarantees that the allocated memory is freed,
// if an asynchronous exception occurs.
Marshal.FreeHGlobal(ptr);
//sw.Flush();
//sw.Close();
}
}
while (ret == NT_STATUS.STATUS_INFO_LENGTH_MISMATCH);
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
#region Private Members
private static bool GetFileNameFromHandle(IntPtr handle, int processId, out string fileName)
{
IntPtr currentProcess = NativeMethods.GetCurrentProcess();
bool remote = (processId != NativeMethods.GetProcessId(currentProcess));
SafeProcessHandle processHandle = null;
SafeObjectHandle objectHandle = null;
try
{
if (remote)
{
processHandle = NativeMethods.OpenProcess(ProcessAccessRights.PROCESS_DUP_HANDLE, true, processId);
if (NativeMethods.DuplicateHandle(processHandle.DangerousGetHandle(), handle, currentProcess, out objectHandle, 0, false, DuplicateHandleOptions.DUPLICATE_SAME_ACCESS))
{
handle = objectHandle.DangerousGetHandle();
}
}
return GetFileNameFromHandle(handle, out fileName, 200);
}
finally
{
if (remote)
{
if (processHandle != null)
{
processHandle.Close();
}
if (objectHandle != null)
{
objectHandle.Close();
}
}
}
}
private static bool GetFileNameFromHandle(IntPtr handle, out string fileName, int wait)
{
using (FileNameFromHandleState f = new FileNameFromHandleState(handle))
{
ThreadPool.QueueUserWorkItem(new WaitCallback(GetFileNameFromHandle), f);
if (f.WaitOne(wait))
{
fileName = f.FileName;
return f.RetValue;
}
else
{
fileName = string.Empty;
return false;
}
}
}
private class FileNameFromHandleState : IDisposable
{
private ManualResetEvent _mr;
private IntPtr _handle;
private string _fileName;
private bool _retValue;
public IntPtr Handle
{
get
{
return _handle;
}
}
public string FileName
{
get
{
return _fileName;
}
set
{
_fileName = value;
}
}
public bool RetValue
{
get
{
return _retValue;
}
set
{
_retValue = value;
}
}
public FileNameFromHandleState(IntPtr handle)
{
_mr = new ManualResetEvent(false);
this._handle = handle;
}
public bool WaitOne(int wait)
{
return _mr.WaitOne(wait, false);
}
public void Set()
{
try
{
_mr.Set();
}
catch{}
}
#region IDisposable Members
public void Dispose()
{
if (_mr != null)
_mr.Close();
}
#endregion
}
private static void GetFileNameFromHandle(object state)
{
FileNameFromHandleState s = (FileNameFromHandleState)state;
string fileName;
s.RetValue = GetFileNameFromHandle(s.Handle, out fileName);
s.FileName = fileName;
s.Set();
}
private static bool GetFileNameFromHandle(IntPtr handle, out string fileName)
{
IntPtr ptr = IntPtr.Zero;
RuntimeHelpers.PrepareConstrainedRegions();
try
{
int length = 0x200; // 512 bytes
RuntimeHelpers.PrepareConstrainedRegions();
try { }
finally
{
// CER guarantees the assignment of the allocated
// memory address to ptr, if an ansynchronous exception
// occurs.
ptr = Marshal.AllocHGlobal(length);
}
NT_STATUS ret = NativeMethods.NtQueryObject(handle, OBJECT_INFORMATION_CLASS.ObjectNameInformation, ptr, length, out length);
if (ret == NT_STATUS.STATUS_BUFFER_OVERFLOW)
{
RuntimeHelpers.PrepareConstrainedRegions();
try { }
finally
{
// CER guarantees that the previous allocation is freed,
// and that the newly allocated memory address is
// assigned to ptr if an asynchronous exception occurs.
Marshal.FreeHGlobal(ptr);
ptr = Marshal.AllocHGlobal(length);
}
ret = NativeMethods.NtQueryObject(handle, OBJECT_INFORMATION_CLASS.ObjectNameInformation, ptr, length, out length);
}
if (ret == NT_STATUS.STATUS_SUCCESS)
{
fileName = Marshal.PtrToStringUni((IntPtr)((int)ptr + 8), (length - 9) / 2);
return fileName.Length != 0;
}
}
finally
{
// CER guarantees that the allocated memory is freed,
// if an asynchronous exception occurs.
Marshal.FreeHGlobal(ptr);
}
fileName = string.Empty;
return false;
}
private static bool GetHandleType(IntPtr handle, int processId, out SystemHandleType handleType)
{
string token = GetHandleTypeToken(handle, processId);
return GetHandleTypeFromToken(token, out handleType);
}
private static bool GetHandleType(IntPtr handle, out SystemHandleType handleType)
{
string token = GetHandleTypeToken(handle);
return GetHandleTypeFromToken(token, out handleType);
}
private static bool GetHandleTypeFromToken(string token, out SystemHandleType handleType)
{
for (int i = 1; i < handleTypeTokenCount; i++)
{
if (handleTypeTokens[i] == token)
{
handleType = (SystemHandleType)i;
return true;
}
}
handleType = SystemHandleType.OB_TYPE_UNKNOWN;
return false;
}
private static string GetHandleTypeToken(IntPtr handle, int processId)
{
IntPtr currentProcess = NativeMethods.GetCurrentProcess();
bool remote = (processId != NativeMethods.GetProcessId(currentProcess));
SafeProcessHandle processHandle = null;
SafeObjectHandle objectHandle = null;
try
{
if (remote)
{
processHandle = NativeMethods.OpenProcess(ProcessAccessRights.PROCESS_DUP_HANDLE, true, processId);
if (NativeMethods.DuplicateHandle(processHandle.DangerousGetHandle(), handle, currentProcess, out objectHandle, 0, false, DuplicateHandleOptions.DUPLICATE_SAME_ACCESS))
{
handle = objectHandle.DangerousGetHandle();
}
}
return GetHandleTypeToken(handle);
}
finally
{
if (remote)
{
if (processHandle != null)
{
processHandle.Close();
}
if (objectHandle != null)
{
objectHandle.Close();
}
}
}
}
private static string GetHandleTypeToken(IntPtr handle)
{
int length;
NativeMethods.NtQueryObject(handle, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, IntPtr.Zero, 0, out length);
IntPtr ptr = IntPtr.Zero;
RuntimeHelpers.PrepareConstrainedRegions();
try
{
RuntimeHelpers.PrepareConstrainedRegions();
try { }
finally
{
ptr = Marshal.AllocHGlobal(length);
}
if (NativeMethods.NtQueryObject(handle, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, ptr, length, out length) == NT_STATUS.STATUS_SUCCESS)
{
return Marshal.PtrToStringUni((IntPtr)((int)ptr + 0x60));
}
}
finally
{
Marshal.FreeHGlobal(ptr);
}
return string.Empty;
}
private static bool ConvertDevicePathToDosPath(string devicePath, out string dosPath)
{
EnsureDeviceMap();
int i = devicePath.Length;
while (i > 0 && (i = devicePath.LastIndexOf('\\', i - 1)) != -1)
{
string drive;
if (deviceMap.TryGetValue(devicePath.Substring(0, i), out drive))
{
dosPath = string.Concat(drive, devicePath.Substring(i));
return dosPath.Length != 0;
}
}
dosPath = string.Empty;
return false;
}
private static void EnsureDeviceMap()
{
if (deviceMap == null)
{
Dictionary<string, string> localDeviceMap = BuildDeviceMap();
Interlocked.CompareExchange<Dictionary<string, string>>(ref deviceMap, localDeviceMap, null);
}
}
private static Dictionary<string, string> BuildDeviceMap()
{
string[] logicalDrives = Environment.GetLogicalDrives();
Dictionary<string, string> localDeviceMap = new Dictionary<string, string>(logicalDrives.Length);
StringBuilder lpTargetPath = new StringBuilder(MAX_PATH);
foreach (string drive in logicalDrives)
{
string lpDeviceName = drive.Substring(0, 2);
NativeMethods.QueryDosDevice(lpDeviceName, lpTargetPath, MAX_PATH);
localDeviceMap.Add(NormalizeDeviceName(lpTargetPath.ToString()), lpDeviceName);
}
localDeviceMap.Add(networkDevicePrefix.Substring(0, networkDevicePrefix.Length - 1), "\\");
return localDeviceMap;
}
private static string NormalizeDeviceName(string deviceName)
{
if (string.Compare(deviceName, 0, networkDevicePrefix, 0, networkDevicePrefix.Length, StringComparison.InvariantCulture) == 0)
{
string shareName = deviceName.Substring(deviceName.IndexOf('\\', networkDevicePrefix.Length) + 1);
return string.Concat(networkDevicePrefix, shareName);
}
return deviceName;
}
#endregion
}
}
Not very straightforward, but on Windows Vista and above you can use the Restart Manager APIs to see who is using a file. Internet Explorer caches settings includes details on using this to detect which process has iexplore.exe open.
Omitting a lot of detail:
// Start an RM session
RmStartSession(&sessionHandle, 0, sessionKey);
// Register the file you are checking
RmRegisterResources(sessionHandle, 1, filePathArray, 0, NULL, 0, NULL);
// Get all processes that have that file open.
RmGetList(sessionHAndle, &nProcInfoNeeded, &nProcInfo, processes, &rebootReason);
RmEndSession(sessionHandle);
Handle, from Windows Sysinternals. This is a free command-line utility provided by Microsoft.
You could run it, and parse the result.
I had issues with stefan's solution. Below is a modified version which seems to work well.
using System;
using System.Collections;
using System.Diagnostics;
using System.Management;
using System.IO;
static class Module1
{
static internal ArrayList myProcessArray = new ArrayList();
private static Process myProcess;
public static void Main()
{
string strFile = "c:\\windows\\system32\\msi.dll";
ArrayList a = getFileProcesses(strFile);
foreach (Process p in a)
{
Debug.Print(p.ProcessName);
}
}
private static ArrayList getFileProcesses(string strFile)
{
myProcessArray.Clear();
Process[] processes = Process.GetProcesses();
int i = 0;
for (i = 0; i <= processes.GetUpperBound(0) - 1; i++)
{
myProcess = processes[i];
//if (!myProcess.HasExited) //This will cause an "Access is denied" error
if (myProcess.Threads.Count > 0)
{
try
{
ProcessModuleCollection modules = myProcess.Modules;
int j = 0;
for (j = 0; j <= modules.Count - 1; j++)
{
if ((modules[j].FileName.ToLower().CompareTo(strFile.ToLower()) == 0))
{
myProcessArray.Add(myProcess);
break;
// TODO: might not be correct. Was : Exit For
}
}
}
catch (Exception exception)
{
//MsgBox(("Error : " & exception.Message))
}
}
}
return myProcessArray;
}
}
UPDATE
If you just want to know which process(es) are locking a particular DLL, you can execute and parse the output of tasklist /m YourDllName.dll. Works on Windows XP and later. See
What does this do? tasklist /m "mscor*"
This works for DLLs locked by other processes. This routine will not find out for example that a text file is locked by a word process.
C#:
using System.Management;
using System.IO;
static class Module1
{
static internal ArrayList myProcessArray = new ArrayList();
private static Process myProcess;
public static void Main()
{
string strFile = "c:\\windows\\system32\\msi.dll";
ArrayList a = getFileProcesses(strFile);
foreach (Process p in a) {
Debug.Print(p.ProcessName);
}
}
private static ArrayList getFileProcesses(string strFile)
{
myProcessArray.Clear();
Process[] processes = Process.GetProcesses;
int i = 0;
for (i = 0; i <= processes.GetUpperBound(0) - 1; i++) {
myProcess = processes(i);
if (!myProcess.HasExited) {
try {
ProcessModuleCollection modules = myProcess.Modules;
int j = 0;
for (j = 0; j <= modules.Count - 1; j++) {
if ((modules.Item(j).FileName.ToLower.CompareTo(strFile.ToLower) == 0)) {
myProcessArray.Add(myProcess);
break; // TODO: might not be correct. Was : Exit For
}
}
}
catch (Exception exception) {
}
//MsgBox(("Error : " & exception.Message))
}
}
return myProcessArray;
}
}
VB.Net:
Imports System.Management
Imports System.IO
Module Module1
Friend myProcessArray As New ArrayList
Private myProcess As Process
Sub Main()
Dim strFile As String = "c:\windows\system32\msi.dll"
Dim a As ArrayList = getFileProcesses(strFile)
For Each p As Process In a
Debug.Print(p.ProcessName)
Next
End Sub
Private Function getFileProcesses(ByVal strFile As String) As ArrayList
myProcessArray.Clear()
Dim processes As Process() = Process.GetProcesses
Dim i As Integer
For i = 0 To processes.GetUpperBound(0) - 1
myProcess = processes(i)
If Not myProcess.HasExited Then
Try
Dim modules As ProcessModuleCollection = myProcess.Modules
Dim j As Integer
For j = 0 To modules.Count - 1
If (modules.Item(j).FileName.ToLower.CompareTo(strFile.ToLower) = 0) Then
myProcessArray.Add(myProcess)
Exit For
End If
Next j
Catch exception As Exception
'MsgBox(("Error : " & exception.Message))
End Try
End If
Next i
Return myProcessArray
End Function
End Module
The following was produced based on Iain Ballard's code dump. It is broken: it will occasionally lock up when you retrieve the handle name. This code doesn't contain any work-arounds for that issue, and .NET leaves few options: Thread.Abort can no longer abort a thread that's currently in a native method.
So, with that disclaimer, here is the code to retrieve handles which has been adapted to work (apart from the occasional lock-up) both in 32 and 64 bit modes:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
namespace BrokenHandleRetrieval
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Enumerates open handles.");
Console.WriteLine("This *will* lock up on calling HandleInfo.Name from time to time. Thread.Abort() won't help.");
foreach (var hi in HandleUtil.GetHandles().Where(hi => hi.Type == HandleType.File))
Console.WriteLine("pid: " + hi.ProcessId + ", name: " + hi.Name);
}
}
public enum HandleType
{
Unknown,
Other,
File, Directory, SymbolicLink, Key,
Process, Thread, Job, Session, WindowStation,
Timer, Desktop, Semaphore, Token,
Mutant, Section, Event, KeyedEvent, IoCompletion, IoCompletionReserve,
TpWorkerFactory, AlpcPort, WmiGuid, UserApcReserve,
}
public class HandleInfo
{
public int ProcessId { get; private set; }
public ushort Handle { get; private set; }
public int GrantedAccess { get; private set; }
public byte RawType { get; private set; }
public HandleInfo(int processId, ushort handle, int grantedAccess, byte rawType)
{
ProcessId = processId;
Handle = handle;
GrantedAccess = grantedAccess;
RawType = rawType;
}
private static Dictionary<byte, string> _rawTypeMap = new Dictionary<byte, string>();
private string _name, _typeStr;
private HandleType _type;
public string Name { get { if (_name == null) initTypeAndName(); return _name; } }
public string TypeString { get { if (_typeStr == null) initType(); return _typeStr; } }
public HandleType Type { get { if (_typeStr == null) initType(); return _type; } }
private void initType()
{
if (_rawTypeMap.ContainsKey(RawType))
{
_typeStr = _rawTypeMap[RawType];
_type = HandleTypeFromString(_typeStr);
}
else
initTypeAndName();
}
bool _typeAndNameAttempted = false;
private void initTypeAndName()
{
if (_typeAndNameAttempted)
return;
_typeAndNameAttempted = true;
IntPtr sourceProcessHandle = IntPtr.Zero;
IntPtr handleDuplicate = IntPtr.Zero;
try
{
sourceProcessHandle = NativeMethods.OpenProcess(0x40 /* dup_handle */, true, ProcessId);
// To read info about a handle owned by another process we must duplicate it into ours
// For simplicity, current process handles will also get duplicated; remember that process handles cannot be compared for equality
if (!NativeMethods.DuplicateHandle(sourceProcessHandle, (IntPtr) Handle, NativeMethods.GetCurrentProcess(), out handleDuplicate, 0, false, 2 /* same_access */))
return;
// Query the object type
if (_rawTypeMap.ContainsKey(RawType))
_typeStr = _rawTypeMap[RawType];
else
{
int length;
NativeMethods.NtQueryObject(handleDuplicate, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, IntPtr.Zero, 0, out length);
IntPtr ptr = IntPtr.Zero;
try
{
ptr = Marshal.AllocHGlobal(length);
if (NativeMethods.NtQueryObject(handleDuplicate, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, ptr, length, out length) != NT_STATUS.STATUS_SUCCESS)
return;
_typeStr = Marshal.PtrToStringUni((IntPtr) ((int) ptr + 0x58 + 2 * IntPtr.Size));
_rawTypeMap[RawType] = _typeStr;
}
finally
{
Marshal.FreeHGlobal(ptr);
}
}
_type = HandleTypeFromString(_typeStr);
// Query the object name
if (_typeStr != null && GrantedAccess != 0x0012019f && GrantedAccess != 0x00120189 && GrantedAccess != 0x120089) // don't query some objects that could get stuck
{
int length;
NativeMethods.NtQueryObject(handleDuplicate, OBJECT_INFORMATION_CLASS.ObjectNameInformation, IntPtr.Zero, 0, out length);
IntPtr ptr = IntPtr.Zero;
try
{
ptr = Marshal.AllocHGlobal(length);
if (NativeMethods.NtQueryObject(handleDuplicate, OBJECT_INFORMATION_CLASS.ObjectNameInformation, ptr, length, out length) != NT_STATUS.STATUS_SUCCESS)
return;
_name = Marshal.PtrToStringUni((IntPtr) ((int) ptr + 2 * IntPtr.Size));
}
finally
{
Marshal.FreeHGlobal(ptr);
}
}
}
finally
{
NativeMethods.CloseHandle(sourceProcessHandle);
if (handleDuplicate != IntPtr.Zero)
NativeMethods.CloseHandle(handleDuplicate);
}
}
public static HandleType HandleTypeFromString(string typeStr)
{
switch (typeStr)
{
case null: return HandleType.Unknown;
case "File": return HandleType.File;
case "IoCompletion": return HandleType.IoCompletion;
case "TpWorkerFactory": return HandleType.TpWorkerFactory;
case "ALPC Port": return HandleType.AlpcPort;
case "Event": return HandleType.Event;
case "Section": return HandleType.Section;
case "Directory": return HandleType.Directory;
case "KeyedEvent": return HandleType.KeyedEvent;
case "Process": return HandleType.Process;
case "Key": return HandleType.Key;
case "SymbolicLink": return HandleType.SymbolicLink;
case "Thread": return HandleType.Thread;
case "Mutant": return HandleType.Mutant;
case "WindowStation": return HandleType.WindowStation;
case "Timer": return HandleType.Timer;
case "Semaphore": return HandleType.Semaphore;
case "Desktop": return HandleType.Desktop;
case "Token": return HandleType.Token;
case "Job": return HandleType.Job;
case "Session": return HandleType.Session;
case "IoCompletionReserve": return HandleType.IoCompletionReserve;
case "WmiGuid": return HandleType.WmiGuid;
case "UserApcReserve": return HandleType.UserApcReserve;
default: return HandleType.Other;
}
}
}
public static class HandleUtil
{
public static IEnumerable<HandleInfo> GetHandles()
{
// Attempt to retrieve the handle information
int length = 0x10000;
IntPtr ptr = IntPtr.Zero;
try
{
while (true)
{
ptr = Marshal.AllocHGlobal(length);
int wantedLength;
var result = NativeMethods.NtQuerySystemInformation(SYSTEM_INFORMATION_CLASS.SystemHandleInformation, ptr, length, out wantedLength);
if (result == NT_STATUS.STATUS_INFO_LENGTH_MISMATCH)
{
length = Math.Max(length, wantedLength);
Marshal.FreeHGlobal(ptr);
ptr = IntPtr.Zero;
}
else if (result == NT_STATUS.STATUS_SUCCESS)
break;
else
throw new Exception("Failed to retrieve system handle information.");
}
int handleCount = IntPtr.Size == 4 ? Marshal.ReadInt32(ptr) : (int) Marshal.ReadInt64(ptr);
int offset = IntPtr.Size;
int size = Marshal.SizeOf(typeof(SystemHandleEntry));
for (int i = 0; i < handleCount; i++)
{
var struc = (SystemHandleEntry) Marshal.PtrToStructure((IntPtr) ((int) ptr + offset), typeof(SystemHandleEntry));
yield return new HandleInfo(struc.OwnerProcessId, struc.Handle, struc.GrantedAccess, struc.ObjectTypeNumber);
offset += size;
}
}
finally
{
if (ptr != IntPtr.Zero)
Marshal.FreeHGlobal(ptr);
}
}
[StructLayout(LayoutKind.Sequential)]
private struct SystemHandleEntry
{
public int OwnerProcessId;
public byte ObjectTypeNumber;
public byte Flags;
public ushort Handle;
public IntPtr Object;
public int GrantedAccess;
}
}
enum NT_STATUS
{
STATUS_SUCCESS = 0x00000000,
STATUS_BUFFER_OVERFLOW = unchecked((int) 0x80000005L),
STATUS_INFO_LENGTH_MISMATCH = unchecked((int) 0xC0000004L)
}
enum SYSTEM_INFORMATION_CLASS
{
SystemBasicInformation = 0,
SystemPerformanceInformation = 2,
SystemTimeOfDayInformation = 3,
SystemProcessInformation = 5,
SystemProcessorPerformanceInformation = 8,
SystemHandleInformation = 16,
SystemInterruptInformation = 23,
SystemExceptionInformation = 33,
SystemRegistryQuotaInformation = 37,
SystemLookasideInformation = 45
}
enum OBJECT_INFORMATION_CLASS
{
ObjectBasicInformation = 0,
ObjectNameInformation = 1,
ObjectTypeInformation = 2,
ObjectAllTypesInformation = 3,
ObjectHandleInformation = 4
}
static class NativeMethods
{
[DllImport("ntdll.dll")]
internal static extern NT_STATUS NtQuerySystemInformation(
[In] SYSTEM_INFORMATION_CLASS SystemInformationClass,
[In] IntPtr SystemInformation,
[In] int SystemInformationLength,
[Out] out int ReturnLength);
[DllImport("ntdll.dll")]
internal static extern NT_STATUS NtQueryObject(
[In] IntPtr Handle,
[In] OBJECT_INFORMATION_CLASS ObjectInformationClass,
[In] IntPtr ObjectInformation,
[In] int ObjectInformationLength,
[Out] out int ReturnLength);
[DllImport("kernel32.dll")]
internal static extern IntPtr GetCurrentProcess();
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr OpenProcess(
[In] int dwDesiredAccess,
[In, MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
[In] int dwProcessId);
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool CloseHandle(
[In] IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DuplicateHandle(
[In] IntPtr hSourceProcessHandle,
[In] IntPtr hSourceHandle,
[In] IntPtr hTargetProcessHandle,
[Out] out IntPtr lpTargetHandle,
[In] int dwDesiredAccess,
[In, MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
[In] int dwOptions);
}
}
This is probably irrelevant and if it is please someone comment but there was a work-around I've used in explorer before to get around file locks.
If a file was locked by a process that had died Windows often wouldn't let you delete it but if you created a new file of the same name somewhere else, moved it to the folder it would succeed. You could then delete the new file and all was well.
To use this for your app you'd have to be able to read the file and hold it in memory before you did this then you write it back out after you'd got rid of the old one.
Maybe it will help, maybe not but it's worth trying.
Try Unlocker. If you try and delete the file that is locked by another process, it will list the process(es) that have the file locked. You can then unlock the file by shutting down those processes.
foreach (var process in Process.GetProcessesByName("excel")) //whatever you need to close
{
if (process.MainWindowTitle.Contains("test.xlsx"))
{
process.Kill();
break;
}
}
or
foreach (var process in Process.GetProcesses())
{
if (process.MainWindowTitle.Contains("test.dat"))
{
process.Kill();
break;
}
}
I believe that you need code running in kernel mode to completely answer the question (but I haven't looked at the restart manager API).
You can enumerate all processes and their modules - so if the file you're looking for is a module (DLL, EXE, OCX...), you're good to go. But if it's a text file for example, you have to look at the kernel handle table which you cannot see from user mode. Handle.exe has a kernel driver in order to do that.
I rewrote the GetProcessesLockingFile() method in the solution. The code was not working.
For example, you have a folder "C:\folder1\folder2" and a process in folder2 (process1). If the process was running, GetProcessesLockingFile() was returning "C:\folder1\folder2". So the condition if (files.Contains(filePath)) => if ("C:\folder1\folder2".contains("C:\folder1\folder2\process1")) was never true.
So this is my solution:
public static List<Process> GetProcessesLockingFile(FileInfo file)
{
var procs = new List<Process>();
var processListSnapshot = Process.GetProcesses();
foreach (var process in processListSnapshot)
{
if (process.Id <= 4) { continue; } // system processes
List<string> paths = GetFilesLockedBy(process);
foreach (string path in paths)
{
string pathDirectory = path;
if (!pathDirectory.EndsWith(Constants.DOUBLE_BACKSLASH))
{
pathDirectory = pathDirectory + Constants.DOUBLE_BACKSLASH;
}
string lastFolderName = Path.GetFileName(Path.GetDirectoryName(pathDirectory));
if (file.FullName.Contains(lastFolderName))
{
procs.Add(process);
}
}
}
return procs;
}
Or with a string parameter:
public static List<Process> GetProcessesLockingFile(string filePath)
{
var procs = new List<Process>();
var processListSnapshot = Process.GetProcesses();
foreach (var process in processListSnapshot)
{
if (process.Id <= 4) { continue; } // system processes
List<string> paths = GetFilesLockedBy(process);
foreach (string path in paths)
{
string pathDirectory = path;
if (!pathDirectory.EndsWith(Constants.DOUBLE_BACKSLASH))
{
pathDirectory = pathDirectory + Constants.DOUBLE_BACKSLASH;
}
string lastFolderName = Path.GetFileName(Path.GetDirectoryName(pathDirectory));
if (filePath.Contains(lastFolderName))
{
procs.Add(process);
}
}
}
return procs;
}
You absolutely don't need to run in Kernel mode (!!!)
It's a Win32 FAQ since Windows 95 (!) (in C, Google groups, Win32) : read the handle table, from User mode of course, and get the PID from the File handle ...
Using dotnet core (net6) I solved this problem by using the win32 restart manager (as others have also mentioned). However some of the linked articles have elaborate code importing DLLs and calling those.
After finding an app to kill processes that lock a file written by meziantou. I found out that he publishes .Net wrappers for win32 dlls (including the restart manager).
Leveraging his work, I was able to fix this problem with the following code:
using Meziantou.Framework.Win32;
public static IEnumerable<Process> GetProcessesLockingFile(string filePath)
{
using var session = RestartManager.CreateSession();
session.RegisterFile(filePath);
return session.GetProcessesLockingResources();
}
simpler with linq:
public void KillProcessesAssociatedToFile(string file)
{
GetProcessesAssociatedToFile(file).ForEach(x =>
{
x.Kill();
x.WaitForExit(10000);
});
}
public List<Process> GetProcessesAssociatedToFile(string file)
{
return Process.GetProcesses()
.Where(x => !x.HasExited
&& x.Modules.Cast<ProcessModule>().ToList()
.Exists(y => y.FileName.ToLowerInvariant() == file.ToLowerInvariant())
).ToList();
}