删除某人打开文件的目录

Posted

技术标签:

【中文标题】删除某人打开文件的目录【英文标题】:Delete a directory where someone has opened a file 【发布时间】:2017-06-10 01:01:57 【问题描述】:

我正在尝试使用“安装程序”程序以编程方式删除和替换应用程序“应用程序 A”的内容,该程序只是一个自定义 WPF .exe 应用程序,我们将其称为“应用程序 B”。 (我的问题涉及“App B”中的代码。)

GUI 设置(不是特别重要) App B 有一个 GUI,用户可以在其中选择计算机名称以将 App A 复制到其中。管理员使用文件选择器通过单击“App A.exe”来填写本地计算机上的源目录路径。还有用于用户名和密码的文本框,因此管理员可以输入他们的凭据以获取应用程序 A 将在其中提供服务的目标文件服务器 - 代码使用这些模拟用户以防止出现权限问题。 “复制”按钮启动例程。

杀死 App A、文件进程并删除文件 复制例程首先终止域中所有计算机上的“App A.exe”进程以及 explorer.exe,以防它们打开 App A 的资源管理器文件夹。显然,这将在下班后完成,但有人可能仍然在回家前将东西打开并锁定了他们的机器。这确实是我要解决的问题的基础。

在复制更新文件之前,我们要删除整个旧目录。为了删除目录(及其子目录),必须删除其中的每个文件。 但是说他们从 App A 的文件夹中打开了一个文件。 该代码在删除文件之前会在任何文件上找到任何锁定过程(使用 Eric J. 在How do I find out which process is locking a file using .NET? 的回答中的代码),它会杀死该进程在它运行的任何计算机上。如果是本地的,它只使用:

public static void localProcessKill(string processName)

    foreach (Process p in Process.GetProcessesByName(processName))
    
        p.Kill();
    

如果是远程的,它使用 WMI:

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

然后它可以删除文件。一切都很好。

目录删除失败 在我下面的代码中,它正在递归删除文件,并且做得很好,直到Directory.Delete(),它会说The process cannot access the file '\\\\SERVER\\C$\\APP_A_DIR' because it is being used by another process,因为我试图删除目录,而我还有一个文件仍然打开从中(即使代码实际上能够删除物理文件-实例仍然打开)。

    public void DeleteDirectory(string target_dir)
    
        string[] files = Directory.GetFiles(target_dir);
        string[] dirs = Directory.GetDirectories(target_dir);
        List<Process> lstProcs = new List<Process>();

        foreach (string file in files)
        
            File.SetAttributes(file, FileAttributes.Normal);
            lstProcs = ProcessHandler.WhoIsLocking(file);
            if (lstProcs.Count == 0)
                File.Delete(file);
            else  // 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);
            
        

        foreach (string dir in dirs)
        
            DeleteDirectory(dir);
        

        //ProcessStartInfo psi = new ProcessStartInfo();
        //psi.Arguments = "/C choice /C Y /N /D Y /T 1 & Del " + target_dir;
        //psi.WindowStyle = ProcessWindowStyle.Hidden;
        //psi.CreateNoWindow = true;
        //psi.FileName = "cmd.exe";
        //Process.Start(psi);

        //ProcessStartInfo psi = new ProcessStartInfo();
        //psi.Arguments = "/C RMDIR /S /Q " + target_dir; 
        //psi.WindowStyle = ProcessWindowStyle.Hidden;
        //psi.CreateNoWindow = true;
        //psi.FileName = "cmd.exe";
        //Process.Start(psi);

        // This is where the failure occurs
        //FileSystem.DeleteDirectory(target_dir, DeleteDirectoryOption.DeleteAllContents);
        Directory.Delete(target_dir, false);
    

我在上面的代码中留下了我尝试过的注释。虽然我可以杀死附加到文件的进程并删除它们,有没有办法杀死附加到文件夹的进程以删除它们?

我在网上看到的所有内容都试图通过延迟循环检查来解决这个问题。这在这里不起作用。我需要杀死打开的文件——我这样做了——但还要确保从文件夹中释放句柄,以便最后也可以将其删除。有没有办法做到这一点?

另一个我认为行不通的选项: 我想我可能只是冻结“安装”(复制)过程,方法是在注册表中将该网络文件夹标记为删除,并安排文件服务器的编程重启,然后重新运行。 How to delete Thumbs.db (it is being used by another process) 给出了执行此操作的代码:

[DllImport("kernel32.dll")]
public static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, int dwFlags);

public const int MOVEFILE_DELAY_UNTIL_REBOOT = 0x4;

//Usage:
MoveFileEx(fileName, null, MOVEFILE_DELAY_UNTIL_REBOOT);

但是文档中有如果使用MOVEFILE_DELAY_UNTIL_REBOOT,“文件不能存在于远程共享上,因为延迟操作是在网络可用之前执行的。”那是假设它可能允许使用文件夹路径,而不是文件名。 (参考:https://msdn.microsoft.com/en-us/library/windows/desktop/aa365240(v=vs.85).aspx)。

【问题讨论】:

您可以注销已登录的用户。通过杀死 explorer.exe,您已经有点“粗鲁”:),因此注销用户将解决您的所有问题。这比重新启动要好,因为您冒着服务器因任何原因无法恢复的风险。 是的,不幸的是,我们在域中有数以万计的用户,并且只有一小部分用户是该应用程序的用户。我想我可以安全地杀死 explorer.exe(因为它会重新启动,并且人们可以轻松地重新打开文件夹)......即使我们的政策是每个人都应该在离开时注销,并且我们实际上在安装更新时重新启动机器,我不确定域范围的注销是否是我们想要的方法,但在我们数据库中的用户表中查找它们可能是可行的。我们可以做这种选择性的方法吗?您如何将用户映射到已登录的计算机? 等等.. 应用程序在 MachineX 上运行,用户 RDP 进入 MachineX?或者他们通过 UNC 远程运行应用程序?或者应用程序存在于客户端机器上并且某些组件指向 MachineX? 用户通过 UNC 路径连接到它。该应用程序将存储在文件服务器上。我们将在他们的桌面上有一个指向它的快捷方式。因此,他们可能不会担心他们会发现托管它的文件夹并从中打开一个文件,因为他们必须查看快捷方式的属性才能找到它.但有可能 IT 团队成员正在查看日志、打开配置文件等。 【参考方案1】:

所以我想处理两种情况 - 两种情况都是阻止删除文件夹的情况:

1) 用户在其本地计算机上从文件服务器上的应用程序文件夹中打开了一个文件。

2) 管理员从应用程序的文件夹中打开了一个文件,他们将在远程(RDP'ed)到服务器时看到该文件。

我已经确定了前进的方向。如果我遇到这个问题,我想我能做的就是: 1) 如果我真的想要删除文件夹(不理想并且可能矫枉过正,但是遇到相同问题的其他人可能会受到此选项的启发)。服务器重新启动后,需要再次运行安装程序以复制文件。

[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, out SafeTokenHandle phToken);

LogonUser(userName, domainName, password,
        LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
        out safeTokenHandle);

try

    using (WindowsIdentity newId = new WindowsIdentity(safeTokenHandle.DangerousGetHandle()))
    
        using (WindowsImpersonationContext impersonatedUser = newId.Impersonate())
        
            foreach (Computer pc in selectedList)  // selectedList is an ObservableCollection<Computer>
            
                string newDir = "//" + pc.Name + txtExtension.Text; // the textbox has /C$/APP_A_DIR in it
                if (Directory.Exists(newDir))  
                
                    DeleteDirectory(newDir);  // <-- this is where the exception happens
                
            
        
    

catch (IOException ex)

    string msg = "There was a file left open, thereby preventing a full deletion of the previous folder, though all contents have been removed.  Do you wish to proceed with installation, or reboot the server and begin again, in order to remove and replace the installation directory?";
    MessageBoxResult result = MessageBox.Show(msg, "Reboot File Server?", MessageBoxButton.OKCancel);
    if (result == MessageBoxResult.OK)
    
        var psi = new ProcessStartInfo("shutdown","/s /t 0");
        psi.CreateNoWindow = true;
        psi.UseShellExecute = false;
        Process.Start(psi);
    
    else
    
        MessageBox.Show("Copying files...");
        FileSystem.CopyDirectory(sourcePath, newDir);
        MessageBox.Show("Completed!");
    

参考:How to shut down the computer from C#

2)无论如何,完全忽略它并执行我的副本。这些文件实际上确实删除了,我发现有一个我不能删除的文件夹真的没有问题,只要我可以写入它,我可以。所以这是我最终选择的那个。

同样,在IOException catch 块中:

catch (IOException ex)

    if (ex.Message.Contains("The process cannot access the file") && 
        ex.Message.Contains("because it is being used by another process") )
    
        MessageBox.Show("Copying files...");
        FileSystem.CopyDirectory(sourcePath, newDir);
        MessageBox.Show("Completed!");
    
    else
    
        string err = "Issue when performing file copy: " + ex.Message;
        MessageBox.Show(err);
    

上面的代码省略了我的 Computer 模型,其中只有一个 Name 节点,以及我的 Impersonation 类的其余部分,它基于我自己对几个不同(但相似)代码块的再现他们怎么说去做。如果有人需要,这里有几个很好的答案的链接:

Need Impersonation when accessing shared network drive

copy files with authentication in c#

相关: Cannot delete directory with Directory.Delete(path, true)

【讨论】:

以上是关于删除某人打开文件的目录的主要内容,如果未能解决你的问题,请参考以下文章

批处理(bat)批量打开文件夹下的所有PDF文档

如何用bat打开本身目录下的文件?

c语言怎么打开一个文件夹?

无法删除部分子项。这可能是因为进程打开了文件或将其工作目录设置在目标目录中

操作系统王道考研 p53 文件的基本操作

常见的Dos命令