C# 如何使用 .NET 找出哪个进程正在锁定文件?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/317071/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do I find out which process is locking a file using .NET?
提问by AJ.
I've seen several of answers about using Handleor Process Monitor, but I would like to be able to find out in my own code (C#) which process is locking a file.
我已经看到了几个关于使用Handle或Process Monitor的答案,但我希望能够在我自己的代码 (C#) 中找出哪个进程正在锁定文件。
I have a nasty feeling that I'm going to have to spelunk around in the win32 API, but if anyone has already done this and can put me on the right track, I'd really appreciate the help.
我有一种讨厌的感觉,我将不得不深入研究 win32 API,但如果有人已经这样做并且可以让我走上正轨,我将非常感谢您的帮助。
Update
更新
Links to similar questions
类似问题的链接
采纳答案by orip
One of the good things about handle.exe
is that you can run it as a subprocess and parse the output.
好处之一handle.exe
是您可以将其作为子进程运行并解析输出。
We do this in our deployment script - works like a charm.
我们在我们的部署脚本中这样做 - 就像一个魅力。
回答by Stefan
This works for DLLs locked by other processes. This routine will not find out for example that a text file is locked by a word process.
这适用于被其他进程锁定的 DLL。例如,此例程不会发现文本文件被文字处理锁定。
C#:
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:
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
回答by user137604
I had issues with stefan's solution. Below is a modified version which seems to work well.
我对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;
}
}
UPDATE
更新
If you just want to know which process(es) are locking a particular DLL, you can execute and parse the output of tasklist /m YourDllName.dll
. Works on Windows XP and later. See
如果您只想知道哪个进程正在锁定特定的 DLL,您可以执行并解析tasklist /m YourDllName.dll
. 适用于 Windows XP 及更高版本。看
回答by user137604
It is very complex to invoke Win32 from C#.
从 C# 调用 Win32 非常复杂。
You should use the tool Handle.exe.
您应该使用工具Handle.exe。
After that your C# code have to be the following:
之后,您的 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();
}
回答by Eric J.
Long ago it was impossible to reliably get the list of processes locking a file because Windows simply did not track that information. To support the Restart Manager API, that information is now tracked.
很久以前,不可能可靠地获得锁定文件的进程列表,因为 Windows 根本没有跟踪该信息。为了支持Restart Manager API,现在会跟踪该信息。
I put together code that takes the path of a file and returns a List<Process>
of all processes that are locking that file.
我将获取文件路径的代码放在一起,并返回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;
}
}
Using from Limited Permission (e.g. IIS)
从有限权限使用(例如 IIS)
This call accesses the registry. If the process does not have permission to do so, you will get ERROR_WRITE_FAULT, meaningAn operation was unable to read or write to the registry
. You couldselectively grant permission to your restricted account to the necessary part of the registry. It is more secure though to have your limited access process set a flag (e.g. in the database or the file system, or by using an interprocess communication mechanism such as queue or named pipe) and have a second process call the Restart Manager API.
此调用访问注册表。如果进程没有权限这样做,你会得到 ERROR_WRITE_FAULT,意思是An operation was unable to read or write to the registry
. 您可以有选择地向您的受限帐户授予对注册表必要部分的权限。尽管让您的受限访问进程设置一个标志(例如在数据库或文件系统中,或者通过使用诸如队列或命名管道之类的进程间通信机制)并让第二个进程调用重新启动管理器 API,但更安全。
Granting other-than-minimal permissions to the IIS user is a security risk.
向 IIS 用户授予非最低权限是一种安全风险。
回答by Gabriele Gindro
simpler with linq:
使用 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();
}