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


154

Ho visto diverse risposte sull'uso 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ò andare in giro nell'API win32, ma se qualcuno lo ha già fatto e può mettermi sulla buona strada, apprezzerei molto l'aiuto.

Aggiornare

Collegamenti a domande simili


Risposte:


37

Una delle cose positive di questo handle.exeè che puoi eseguirlo come sottoprocesso e analizzare l'output.

Lo facciamo nel nostro script di distribuzione - funziona come un incantesimo.


21
ma handle.exe non può essere distribuito con il tuo software
torpederos

1
Buon punto. Questo non è stato un problema con lo script di distribuzione (utilizzato internamente), ma sarebbe in altri scenari.
orip

2
qualche esempio di codice sorgente completo in C #? valido anche per ottenere il processo sta bloccando una cartella?
Kiquenet,

3
Scopri la mia risposta per una soluzione che non richiede handle.exe stackoverflow.com/a/20623311/141172
Eric J.

"Devi avere i privilegi di amministratore per eseguire Handle."
Uwe Keim,

135

Molto tempo fa era impossibile ottenere in modo affidabile l'elenco dei processi che bloccavano un file perché Windows semplicemente non registrava tali informazioni. Per supportare l' API Restart Manager , tali informazioni vengono ora monitorate.

Metto insieme il codice che prende il percorso di un file e restituisce uno List<Process>di tutti i processi che bloccano quel 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;
    }
}

Utilizzo da un'autorizzazione limitata (ad es. IIS)

Questa chiamata accede al registro. Se il processo non dispone dell'autorizzazione per farlo, otterrai ERROR_WRITE_FAULT, che significa An operation was unable to read or write to the registry . È possibile concedere in modo selettivo l'autorizzazione al proprio account limitato alla parte necessaria del registro. È più sicuro, tuttavia, 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 che un secondo processo chiami l'API Restart Manager.

Concedere autorizzazioni diverse dal minimo all'utente IIS è un rischio per la sicurezza.


Qualcuno l'ha provato, sembra che potrebbe davvero funzionare (per Windows sopra Vista e SRV 2008)
Daniel Mošmondor,

1
@Blagoh: non credo che Restart Manager sia disponibile su Windows XP. Dovresti ricorrere a uno degli altri metodi meno precisi pubblicati qui.
Eric J.

4
@Blagoh: se vuoi solo sapere chi sta bloccando una specifica DLL, puoi usare tasklist /m YourDllName.dlle analizzare l'output. Vedi stackoverflow.com/questions/152506/…
Eric J.

19
Solo soluzione che non richiede strumenti di terze parti o chiamate API non documentate. Dovrebbe essere la risposta accettata.
IIspettabile il

4
L'ho provato (e funziona) su Windows 2008R2, Windows 2012R2, Windows 7 e Windows 10. Ho scoperto che doveva essere eseguito con privilegi elevati in molte circostanze, altrimenti fallisce quando si tenta di ottenere l'elenco di elabora il blocco di un file.
Jay,

60

È molto complesso invocare Win32 da C #.

È necessario utilizzare lo strumento Handle.exe .

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

1
bell'esempio, ma per quanto ne so, handle.exe ora mostra un brutto prompt per accettare alcune condizioni quando lo si esegue su un computer client per la prima volta, che a mio avviso lo squalifica
Arsen Zahray il

13
@Arsen Zahray: puoi accettare l'eula automaticamente passando un'opzione della riga di comando di /accepteula. Ho aggiornato la risposta di Gennady con la modifica.
Jon Cage,

Quale versione di Handle.exe hai utilizzato? Il più recente V4 sembra essere cambiato in modo rotto. / accepteula e Nome file non sono più supportati
Venson,

3
Non è possibile ridistribuirehandle.exe
Basic

4
Non sono d'accordo - non ha complessità quando si invoca win32 api da c #.
Idan,

10

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 stanno bloccando una determinata DLL, puoi eseguire e analizzare l'output di tasklist /m YourDllName.dll. Funziona su Windows XP e versioni successive. Vedere

Cosa fa questo? elenco attività / m "mscor *"


Non riesco davvero a capire perché myProcessArraysia un membro della classe (ma in realtà è tornato da getFileProcesses ()? Lo stesso vale per myProcess.
Oskar Berggren,

7

Funziona con DLL bloccate da altri processi. Questa routine non scoprirà ad esempio che un file di testo è bloccato da un processo di word.

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

Nel mio esempio utilizzo msi.dll che non è una DLL .Net.
Stefan,

0

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

sembra ridisegnare la stessa eccezione
Sinaestetico il

Dare l'errore. un processo a 32 bit non può accedere al modulo del processo a 64 bit.
ajinkya
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.