Come faccio a sapere quale processo sta bloccando un file usando .NET?

Ho visto molte delle risposte sull’utilizzo di Handle o Process Monitor , ma vorrei essere in grado di scoprire nel mio codice (C #) quale processo sta bloccando un file.

Ho la brutta sensazione che dovrò fare il giro dell’API win32, ma se qualcuno lo ha già fatto e può mettermi sulla strada giusta, apprezzerei molto l’aiuto.

Aggiornare

Link a domande simili

  • Come si fa a capire quale processo ha bloccato un file usando c #?
  • Strumento da riga di comando
  • Attraverso una rete
  • Blocco di un dispositivo USB
  • Il test dell’unità fallisce con il file bloccato
  • eliminazione del file bloccato

Uno degli handle.exe di handle.exe è che è ansible eseguirlo come sottoprocesso e analizzare l’output.

Lo facciamo nel nostro script di implementazione: funziona come un fascino.

Molto tempo fa era imansible ottenere in modo affidabile l’elenco dei processi che bloccano un file perché Windows semplicemente non ha tracciato tali informazioni. Per supportare l’ API di Gestione riavvio , tali informazioni vengono ora tracciate.

Metto insieme il codice che prende il percorso di un file e restituisce un List di tutti i processi che bloccano quel file.

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

Utilizzo da authorization limitata (ad es. IIS)

Questa chiamata accede al registro. Se il processo non ha il permesso di farlo, otterrai ERROR_WRITE_FAULT, che significa che An operation was unable to read or write to the registry . È ansible concedere selettivamente l'authorization all'account limitato alla parte necessaria del registro. Tuttavia, è più sicuro che il processo di accesso limitato imposti un flag (ad esempio nel database o nel file system o utilizzando un meccanismo di comunicazione tra processi come coda o named pipe) e abbia una seconda chiamata di processo all'API di Restart Manager.

Garantire autorizzazioni diverse da quelle minime all'utente IIS è un rischio per la sicurezza.

È molto complesso richiamare Win32 da C #.

È necessario utilizzare lo strumento Handle.exe da http://technet.microsoft.com/en-us/sysinternals/bb896655.aspx

Dopo che il tuo codice C # deve essere il seguente:

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

Ho avuto problemi con la soluzione di stefan . Di seguito è una versione modificata che sembra funzionare bene.

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

AGGIORNARE

Se vuoi solo sapere quali processi (es) stanno bloccando una particolare DLL, puoi eseguire e analizzare l'output di tasklist /m YourDllName.dll . Funziona su Windows XP e versioni successive. Vedere

Cosa fa questo? tasklist / m "mscor *"

Questo funziona per DLL bloccate da altri processi. Questa routine non scoprirà ad esempio che un file di testo è bloccato da un word processor.

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 

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