如何删除被 C# 中的另一个进程锁定的文件?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1040/
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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-07-31 12:10:53  来源:igfitidea点击:

How do I delete a file which is locked by another process in C#?

提问by Dean Bates

I'm looking for a way to delete a file which is locked by another process using C#. I suspect the method must be able to find which process is locking the file (perhaps by tracking the handles, although I'm not sure how to do this in C#) then close that process before being able to complete the file delete using File.Delete().

我正在寻找一种方法来删除被另一个进程使用 C# 锁定的文件。我怀疑该方法必须能够找到锁定文件的进程(可能通过跟踪句柄,尽管我不确定如何在 C# 中执行此操作)然后关闭该进程,然后才能使用File.Delete().

采纳答案by Ishmaeel

Killing other processes is not a healthy thing to do. If your scenario involves something like uninstallation, you could use the MoveFileExAPI functionto mark the file for deletion upon next reboot.

杀死其他进程不是一件健康的事情。如果您的方案涉及卸载之类的事情,您可以使用MoveFileExAPI 函数将文件标记为在下次重新启动时删除。

If it appears that you really need to delete a file in use by another process, I'd recommend re-considering the actual problem before considering any solutions.

如果您确实需要删除另一个进程正在使用的文件,我建议您在考虑任何解决方案之前重新考虑实际问题。

回答by Ryan Fox

You can use this program, Handle, to find which process has the lock on your file. It's a command-line tool, so I guess you use the output from that. I'm not sure about finding it programmatically.

您可以使用这个程序Handle来查找哪个进程锁定了您的文件。这是一个命令行工具,所以我猜你会使用它的输出。我不确定以编程方式找到它。

If deleting the file can wait, you could specify it for deletion when your computer next starts up:

如果删除文件可以等待,您可以指定在您的计算机下次启动时将其删除:

  1. Start REGEDT32 (W2K)or REGEDIT (WXP)and navigate to:

    HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager
    
  2. W2K and WXP

    • W2K:
      Edit
      Add Value...
      Data Type: REG_MULTI_SZ
      Value Name: PendingFileRenameOperations
      OK

    • WXP:
      Edit
      New
      Multi-String Value
      enter
      PendingFileRenameOperations

  3. In the Data area, enter "\??\" + filenameto be deleted. LFNs may be entered without being embedded in quotes. To delete C:\Long Directory Name\Long File Name.exe, enter the following data:

    \??\C:\Long Directory Name\Long File Name.exe
    

    Then press OK.

  4. The "destination file name" is a null (zero) string. It is entered as follows:

    • W2K:
      Edit
      Binary
      select Data Format: Hex
      click at the end of the hex string
      enter 0000 (four zeros)
      OK

    • WXP:
      Right-click the value
      choose "Modify Binary Data"
      click at the end of the hex string
      enter 0000 (four zeros)
      OK

  5. Close REGEDT32/REGEDITand reboot to delete the file.

  1. 启动REGEDT32 (W2K)REGEDIT (WXP)导航至:

    HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager
    
  2. W2K 和 WXP

    • W2K:
      编辑
      添加值...
      数据类型:REG_MULTI_SZ
      值名称:PendingFileRenameOperations
      OK

    • WXP:
      编辑
      新的
      多字符串值
      enter
      PendingFileRenameOperations

  3. 在数据区,输入"\??\" + filename要删除。LFN 可以在不嵌入引号的情况下输入。要删除C:\Long Directory Name\Long File Name.exe,请输入以下数据:

    \??\C:\Long Directory Name\Long File Name.exe
    

    然后按OK

  4. “目标文件名”是一个空(零)字符串。输入如下:

    • W2K:
      编辑
      二进制
      选择数据格式:十六进制
      单击十六进制字符串的末尾
      输入 0000(四个零)
      OK

    • WXP:
      右键单击值
      选择“修改二进制数据”
      单击十六进制字符串的末尾
      输入 0000(四个零)
      OK

  5. 关闭REGEDT32/REGEDIT并重新启动以删除文件。

(Shamelessly stolen from some random forum, for posterity's sake.)

(为了后人的缘故,无耻地从某个随机论坛偷走。)

回答by Orion Edwards

If you want to do it programmatically. I'm not sure... and I'd really recommend against it. If you're just troubleshooting stuff on your own machine, SysInternals Process Explorercan help you

如果您想以编程方式进行。我不确定......我真的建议反对它。如果您只是在自己的机器上进行故障排除,SysInternals Process Explorer可以帮助您

Run it, use the Find Handle command (I think it's either in the find or handle menu), and search for the name of your file. Once the handle(s) is found, you can forcibly close them.

运行它,使用 Find Handle 命令(我认为它在 find 或 handle 菜单中),然后搜索文件的名称。找到手柄后,您可以强行关闭它们。

You can then delete the file and so on.

然后您可以删除该文件等。

Beware, doing this may cause the program which owns the handles to behave strangely, as you've just pulled the proverbial rug out from under it, but it works well when you are debugging your own errant code, or when visual studio/windows explorer is being crapped and not releasing file handles even though you told them to close the file ages ago... sigh :-)

请注意,这样做可能会导致拥有句柄的程序行为异常,因为您刚刚从它下面拉出了众所周知的地毯,但是当您调试自己的错误代码时,或者在 Visual Studio/Windows 资源管理器时,它运行良好即使您很久以前就告诉他们关闭文件,也正在被垃圾处理并且不释放文件句柄......叹息:-)

回答by Orion Edwards

Oh, one big hack I employed years ago, is that Windows won't let you deletefiles, but it does let you movethem.

哦,我多年前使用的一个大技巧是,Windows 不允许您删除文件,但它允许您移动它们。

Pseudo-sort-of-code:

伪代码排序:

mv %WINDIR%\System32\mfc42.dll %WINDIR\System32\mfc42.dll.old
Install new mfc42.dll
Tell user to save work and restart applications

When the applications restarted (note we didn't need to reboot the machine), they loaded the new mfc42.dll, and all was well. That, coupled with PendingFileOperationsto delete the old one the next time the whole system restarted, worked pretty well.

当应用程序重新启动时(注意我们不需要重新启动机器),它们加载了新的mfc42.dll,一切都很好。再加上PendingFileOperations下次整个系统重新启动时删除旧的,效果很好。

回答by Orion Edwards

The typical method is as follows. You've said you want to do this in C# so here goes...

典型的方法如下。你说过你想在 C# 中做到这一点,所以这里是......

  1. If you don't know which process has the file locked, you'll need to examine each process's handle list, and query each handle to determine if it identifies the locked file. Doing this in C# will likely require P/Invoke or an intermediary C++/CLI to call the native APIs you'll need.
  2. Once you've figured out which process(es) have the file locked, you'll need to safely inject a small native DLL into the process (you can also inject a managed DLL, but this is messier, as you then have to start or attach to the .NET runtime).
  3. That bootstrap DLL then closes the handle using CloseHandle, etc.
  1. 如果您不知道哪个进程锁定了文件,则需要检查每个进程的句柄列表,并查询每个句柄以确定它是否标识了锁定的文件。在 C# 中执行此操作可能需要 P/Invoke 或中间 C++/CLI 来调用您需要的本机 API。
  2. 一旦你弄清楚哪个进程锁定了文件,你需要安全地将一个小的本地 DLL 注入到进程中(你也可以注入一个托管的 DLL,但这更麻烦,因为你必须开始或附加到 .NET 运行时)。
  3. 该引导程序 DLL 然后使用 CloseHandle 等关闭句柄。

Essentially: the way to unlock a "locked" file is to inject a DLL file into the offending process's address space and close it yourself. You can do this using native or managed code. No matter what, you're going to need a small amount of native code or at least P/Invoke into the same.

本质上:解锁“锁定”文件的方法是将 DLL 文件注入违规进程的地址空间并自行关闭。您可以使用本机或托管代码执行此操作。无论如何,您将需要少量本机代码或至少 P/Invoke 到相同的代码。

Helpful links:

有用的网址:

Good luck!

祝你好运!

回答by solrevdev

This looks promising. A way of killing the file handle....

这看起来很有希望。一种杀死文件句柄的方法....

http://www.timstall.com/2009/02/killing-file-handles-but-not-process.html

http://www.timstall.com/2009/02/killing-file-handles-but-not-process.html

回答by solrevdev

Using Orion Edwards advice I downloaded the Sysinternals Process Explorerwhich in turn allowed me to discover that the file I was having difficulties deleting was in fact being held not by the Excel.Applicationsobject I thought, but rather the fact that my C# code send mail code had created an Attachment object that left a handle to this file open.

使用 Orion Edwards 的建议,我下载了 Sysinternals Process Explorer,这反过来让我发现我在删除时遇到困难的文件实际上不是由Excel.Applications我认为的对象持有,而是由我的 C# 代码发送邮件代码创建的事实一个打开这个文件的句柄的附件对象。

Once I saw this, I quite simple called on the dispose method of the Attachment object, and the handle was released.

看到这个,我很简单的调用了Attachment对象的dispose方法,handle就被释放了。

The Sysinternals explorer allowed me to discover this used in conjunction with the Visual Studio 2005 debugger.

Sysinternals 资源管理器让我发现它与 Visual Studio 2005 调试器结合使用。

I highly recommend this tool!

我强烈推荐这个工具!

回答by vapcguy

You can use code that you supply the full file path to, and it will return a List<Processes>of anything locking that file:

您可以使用提供完整文件路径的代码,它将返回List<Processes>锁定该文件的任何内容:

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

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

Then, iterate the list of processes and close them and delete the files:

然后,迭代进程列表并关闭它们并删除文件:

    string[] files = Directory.GetFiles(target_dir);
    List<Process> lstProcs = new List<Process>();

    foreach (string file in files)
    {
        lstProcs = ProcessHandler.WhoIsLocking(file);
        if (lstProcs.Count > 0) // deal with the file lock
        {
            foreach (Process p in lstProcs)
            {
                if (p.MachineName == ".")
                    ProcessHandler.localProcessKill(p.ProcessName);
                else
                    ProcessHandler.remoteProcessKill(p.MachineName, txtUserName.Text, txtPassword.Password, p.ProcessName);
            }
            File.Delete(file);
        }
        else
            File.Delete(file);
    }

And depending on if the file is on the local computer:

并取决于文件是否在本地计算机上:

public static void localProcessKill(string processName)
{
    foreach (Process p in Process.GetProcessesByName(processName))
    {
        p.Kill();
    }
}

or a network computer:

或网络计算机:

public static void remoteProcessKill(string computerName, string fullUserName, string pword, string processName)
{
    var connectoptions = new ConnectionOptions();
    connectoptions.Username = fullUserName;  // @"YourDomainName\UserName";
    connectoptions.Password = pword;

    ManagementScope scope = new ManagementScope(@"\" + computerName + @"\root\cimv2", connectoptions);

    // WMI query
    var query = new SelectQuery("select * from Win32_process where name = '" + processName + "'");

    using (var searcher = new ManagementObjectSearcher(scope, query))
    {
        foreach (ManagementObject process in searcher.Get()) 
        {
            process.InvokeMethod("Terminate", null);
            process.Dispose();
        }
    }
}

References:
How do I find out which process is locking a file using .NET?

参考资料:
如何使用 .NET 找出哪个进程正在锁定文件?

Delete a directory where someone has opened a file

删除某人打开文件的目录