Bagaimana cara mengetahui proses mana yang mengunci file menggunakan .NET?

154

Saya telah melihat beberapa jawaban tentang penggunaan Handle atau Process Monitor , tetapi saya ingin dapat mengetahui dalam kode saya sendiri (C #) proses mana yang mengunci file.

Saya memiliki perasaan tidak enak bahwa saya akan harus berputar-putar di win32 API, tetapi jika ada yang sudah melakukan ini dan dapat menempatkan saya di jalur yang benar, saya akan sangat menghargai bantuannya.

Memperbarui

Tautan ke pertanyaan serupa

AJ.
sumber

Jawaban:

37

Salah satu hal yang baik tentang handle.exeadalah Anda dapat menjalankannya sebagai proses dan parsing output.

Kami melakukan ini dalam skrip penerapan kami - berfungsi seperti pesona.

orip
sumber
21
tetapi handle.exe tidak dapat didistribusikan dengan perangkat lunak Anda
torpederos
1
Poin yang bagus. Ini bukan masalah dengan skrip penerapan (digunakan secara internal), tetapi akan ada dalam skenario lain.
orip
2
sampel kode sumber lengkap dalam C #? valid juga untuk proses get mengunci FOLDER?
Kiquenet
3
Lihat jawaban saya untuk solusi yang tidak memerlukan handle.exe stackoverflow.com/a/20623311/141172
Eric J.
"Anda harus memiliki hak administratif untuk menjalankan Handle."
Uwe Keim
135

Dahulu, mustahil untuk mendapatkan daftar proses mengunci file karena Windows tidak melacak informasi itu. Untuk mendukung API Restart Manager , informasi itu sekarang dilacak.

Saya mengumpulkan kode yang mengambil jalur file dan mengembalikan List<Process>semua proses yang mengunci file itu.

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

Menggunakan dari Izin Terbatas (mis. IIS)

Panggilan ini mengakses registri. Jika prosesnya tidak memiliki izin untuk melakukannya, Anda akan mendapatkan ERROR_WRITE_FAULT, artinya An operation was unable to read or write to the registry . Anda dapat secara selektif memberikan izin ke akun terbatas Anda ke bagian yang diperlukan dari registri. Meskipun lebih aman untuk memiliki proses akses terbatas Anda menetapkan bendera (misalnya dalam database atau sistem file, atau dengan menggunakan mekanisme komunikasi antarproses seperti antrian atau pipa bernama) dan memiliki proses kedua memanggil Restart Manager API.

Memberikan izin selain dari minimal kepada pengguna IIS adalah risiko keamanan.

Eric J.
sumber
Adakah yang mencobanya, kelihatannya benar-benar bisa berfungsi (untuk windows di atas Vista dan srv 2008)
Daniel Mošmondor
1
@ Blagoh: Saya tidak percaya Restart Manager tersedia di Windows XP. Anda perlu menggunakan salah satu dari yang lain, metode yang kurang akurat diposting di sini.
Eric J.
4
@ Blagoh: Jika Anda hanya ingin tahu siapa yang mengunci DLL tertentu, Anda bisa menggunakan tasklist /m YourDllName.dlldan mengurai output. Lihat stackoverflow.com/questions/152506/…
Eric J.
19
Hanya solusi yang tidak memerlukan alat pihak ke-3 atau panggilan API tidak berdokumen. Seharusnya jawaban yang diterima.
IInspectable
4
Saya sudah mencoba ini (dan berfungsi) pada Windows 2008R2, Windows 2012R2, Windows 7 dan Windows 10. Saya menemukan bahwa itu harus dijalankan dengan hak yang lebih tinggi dalam banyak keadaan jika tidak gagal ketika mencoba untuk mendapatkan daftar proses mengunci file.
Jay
60

Sangat kompleks untuk menjalankan Win32 dari C #.

Anda harus menggunakan alat Handle.exe .

Setelah itu kode C # Anda harus sebagai berikut:

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();
}
Uwe Keim
sumber
1
contoh yang bagus, tetapi sepengetahuan saya, handle.exe sekarang menunjukkan prompt yang tidak baik untuk menerima beberapa kondisi ketika Anda menjalankannya pada mesin klien untuk pertama kalinya, yang menurut saya, mendiskualifikasi itu
Arsen Zahray
13
@ Arsen Zahray: Anda dapat menerima eula secara otomatis dengan memasukkan opsi baris perintah /accepteula. Saya telah memperbarui jawaban Gennady dengan perubahan itu.
Jon Cage
Apa versi Handle.exe yang Anda gunakan? Yang terbaru V4 tampaknya akan diubah dengan cara Rusak. / accepteula dan Nama file tidak lagi didukung
Venson
3
Anda tidak dapat mendistribusikan ulanghandle.exe
Basic
4
Saya tidak setuju - tidak memiliki kerumitan saat menjalankan api win32 dari c #.
Idan
10

Saya punya masalah dengan solusi Stefan . Di bawah ini adalah versi modifikasi yang tampaknya berfungsi dengan baik.

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

MEMPERBARUI

Jika Anda hanya ingin tahu proses mana yang mengunci DLL tertentu, Anda dapat menjalankan dan mengurai output dari tasklist /m YourDllName.dll . Bekerja pada Windows XP dan yang lebih baru. Lihat

Apa fungsinya? daftar tugas / m "mscor *"

pengguna137604
sumber
Saya sangat gagal melihat mengapa myProcessArrayseorang anggota kelas (tetapi juga benar-benar kembali dari getFileProcesses ()? Hal yang sama juga berlaku myProcess.
Oskar Berggren
7

Ini berfungsi untuk DLL yang dikunci oleh proses lain. Rutin ini tidak akan menemukan misalnya file teks dikunci oleh proses kata.

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
Stefan
sumber
Dalam Contoh saya, saya menggunakan msi.dll yang bukan .Net DLL.
Stefan
0

lebih sederhana dengan 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();
    }
Gabriele Gindro
sumber
tampaknya hanya memikirkan kembali pengecualian yang sama
Sinaesthetic
Memberi kesalahan. proses 32 bit tidak dapat mengakses modul proses 64 bit.
ajinkya