¿Cómo averiguo qué proceso está bloqueando un archivo usando .NET?

He visto varias respuestas sobre el uso de Handle o Process Monitor , pero me gustaría poder averiguar en mi propio código (C #) qué proceso está bloqueando un archivo.

Tengo la desagradable sensación de que voy a tener que meterme en la API de win32, pero si alguien ya lo ha hecho y puede ponerme en el camino correcto, realmente agradecería la ayuda.

Actualizar

Enlaces a preguntas similares

  • ¿Cómo se puede averiguar qué proceso bloqueó un archivo usando c #?
  • Herramienta de línea de comando
  • A través de una red
  • Bloquear un dispositivo USB
  • La prueba de la unidad falla con el archivo bloqueado
  • borrando archivo bloqueado

Una de las cosas buenas de handle.exe es que puede ejecutarlo como un subproceso y analizar el resultado.

Hacemos esto en nuestra secuencia de comandos de implementación: funciona como un encanto.

Hace mucho tiempo, era imposible obtener de manera confiable la lista de procesos que bloqueaban un archivo porque Windows simplemente no rastreaba esa información. Para admitir la API de Restart Manager , ahora se realiza un seguimiento de esa información.

Puse el código que toma la ruta de un archivo y devuelve una List de todos los procesos que bloquean ese archivo.

 using System.Runtime.InteropServices; using System.Diagnostics; 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); ///  /// Find out what process(es) have a lock on the specified file. ///  /// Path of the file. /// Processes locking the file /// 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) /// ///  static public List WhoIsLocking(string path) { uint handle; string key = Guid.NewGuid().ToString(); List processes = new List(); 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((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; } } 

Uso de Permiso limitado (por ejemplo, IIS)

Esta llamada accede al registro. Si el proceso no tiene permiso para hacerlo, obtendrá ERROR_WRITE_FAULT, lo que significa que An operation was unable to read or write to the registry . Podría otorgar de forma selectiva permiso a su cuenta restringida a la parte necesaria del registro. Sin embargo, es más seguro que su proceso de acceso limitado establezca un indicador (por ejemplo, en la base de datos o el sistema de archivos, o mediante un mecanismo de comunicación entre procesos como la cola o canalizaciones con nombre) y un segundo proceso llame a Restart Manager API.

Otorgar permisos que no sean mínimos al usuario de IIS es un riesgo de seguridad.

Es muy complejo invocar Win32 desde C #.

Debería utilizar la herramienta Handle.exe desde http://technet.microsoft.com/en-us/sysinternals/bb896655.aspx

Después de eso, tu código C # debe ser el siguiente:

 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(); } 

Tuve problemas con la solución de Stefan . A continuación hay una versión modificada que parece funcionar bien.

 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; } } 

ACTUALIZAR

Si solo desea saber qué proceso (s) están bloqueando una DLL particular, puede ejecutar y analizar el resultado de la tasklist /m YourDllName.dll de tasklist /m YourDllName.dll . Funciona en Windows XP y posterior. Ver

¿Qué hace esto? lista de tareas / m "mscor *"

Esto funciona para dlls bloqueados por otros procesos. Esta rutina no descubrirá, por ejemplo, que un archivo de texto está bloqueado por un proceso de texto.

DO#:

 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 

más simple con linq:

 public void KillProcessesAssociatedToFile(string file) { GetProcessesAssociatedToFile(file).ForEach(x => { x.Kill(); x.WaitForExit(10000); }); } public List GetProcessesAssociatedToFile(string file) { return Process.GetProcesses() .Where(x => !x.HasExited && x.Modules.Cast().ToList() .Exists(y => y.FileName.ToLowerInvariant() == file.ToLowerInvariant()) ).ToList(); }