Я пытаюсь программно удалить и заменить содержимое приложения "Приложение А", используя программу "installer", которая является просто настраиваемым приложением WPF.exe, мы будем называть "Приложение B". (Мой вопрос касается кода в "приложении B".)
Настройка GUI (не особенно важно)
В приложении B есть графический интерфейс, в котором пользователь может выбрать имена компьютеров для копирования приложения А. Файл-подборщик используется администратором, чтобы заполнить путь к исходному каталогу на локальном компьютере, нажав "Приложение A.exe". Существуют также текстовые поля для имени пользователя и пароля, поэтому администратор может вводить свои учетные данные для целевого файлового сервера, на котором будет подаваться приложение A, - код олицетворяет пользователя этими полномочиями для предотвращения проблем с правами доступа. Кнопка "Копировать" запускает процедуру.
Убийственное приложение A, файловые процессы и удаление файлов
Процедура копирования начинается с уничтожения процесса "App A.exe" на всех компьютерах в домене, а также explorer.exe, если у них открыта папка проводника приложения. Очевидно, это будет сделано через часы, но кто-то все еще может оставить вещи открытыми и заблокировать их машину, прежде чем отправиться домой. И это действительно основа проблемы, которую я ищу, чтобы решить.
Прежде чем копировать обновленные файлы, мы хотим удалить весь старый каталог. Чтобы удалить каталог (и его подкаталоги), каждый файл внутри них должен быть удален. Но скажите, что у них есть файл, открытый из папки App A.. Код обнаруживает любой процесс блокировки в любом файле до его удаления (используя код от Eric J., отвечающий на Как узнать, какой процесс блокирует файл с помощью .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);
}
Я оставил вещи, которые я пробовал, прокомментировал в приведенном выше коде. Хотя я могу убивать процессы, прикрепленные к файлам, и удалять их, есть способ убить процессы, прикрепленные к папкам, чтобы удалить их?
Все в Интернете, которое я видел, пытается решить это, используя проверку цикла с задержкой. Здесь это не сработает. Мне нужно убить файл, который был открыт, что я и делаю, но также обеспечить, чтобы дескриптор был освобожден из папки, чтобы его также можно было удалить в конце. Есть ли способ сделать это?
Другой вариант, который я считал, не будет работать: Я думал, что могу просто заморозить процесс установки (копирования), отметив эту сетевую папку для удаления в реестре и запланировать программную перезагрузку файлового сервера, а затем снова запустить ее. Как удалить Thumbs.db(он используется другим процессом) дает этот код для этого:
[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).