如何找出使用.NET 锁定文件的进程?

我已经看到了一些关于使用 把手过程监察的答案,但我希望能够在我自己的代码(C #)中找到答案 哪个进程正在锁定一个文件。

我有一种不祥的预感,我将不得不在 win32 API 中四处探索,但是如果有人已经这样做了,并且能够把我带到正确的轨道上,我将非常感谢您的帮助。

更新

类似问题的链接

156588 次浏览

handle.exe的优点之一是可以将其作为子进程运行并解析输出。

我们在部署脚本中这样做,效果非常好。

这适用于其他进程锁定的 DLL。例如,这个例程不会发现文本文件被文字处理锁定。

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 的解决方案。以下是一个修改版本,似乎工作得很好。

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

更新

如果只想知道哪个进程正在锁定特定的 DLL,可以执行和解析 tasklist /m YourDllName.dll的输出。适用于 WindowsXP 及更高版本。你看

这是做什么的? tasklist/m“ mscor *”

从 C # 调用 Win32非常复杂。

您应该使用工具 Handle.exe

之后,你的 C # 代码必须如下:

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

很久以前,可靠地获取锁定文件的进程列表是不可能的,因为 Windows 根本不跟踪这些信息。为了支持 重新启动管理器 API,现在可以跟踪该信息。

我将获取文件路径的代码放在一起,并返回锁定该文件的所有进程的 List<Process>

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

从有限许可使用(例如 IIS)

此调用访问注册表。如果进程没有这样做的权限,您将得到 ERROR _ WRITE _ FAULT,意义 An operation was unable to read or write to the registry可以选择性地将受限帐户的权限授予注册表的必要部分。不过,更安全的做法是让您的有限访问进程设置一个标志(例如在数据库或文件系统中,或者使用进程间通信机制,例如队列或命名管道) ,并让第二个进程调用 Restart Manager API。

向 IIS 用户授予最小权限以外的权限是一种安全风险。

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