【发布时间】: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 团队成员正在查看日志、打开配置文件等。
标签: c# wpf process directory handle