Удалить каталог, в котором кто-то открыл файл

Я пытаюсь программно удалить и заменить содержимое приложения "Приложение А", используя программу "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).

Ответ 1

Итак, есть два сценария, которые я хотел обработать - оба из них запрещены для удаления:

1) Пользователь имеет файл, открытый на своем локальном компьютере, из папки приложения на файловом сервере.

2) Администратор имеет файл, открытый из папки приложения, который они будут видеть при удалении (RDP'ed) на сервер.

Я остановился на пути вперед. Если я столкнусь с этой проблемой, я думаю, что все, что я могу сделать, это либо:

1) Заморозьте процесс установки (копирования), просто заплатив программную перезагрузку файлового сервера в блоке IOException, если я действительно хочу сдуть папку (не идеальный и, возможно, перебор, но другие, работающие по этой же проблеме может быть вдохновлен этим вариантом). Установщик необходимо будет запустить снова, чтобы скопировать файлы после перезагрузки сервера.

[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!");
    }
}

Ссылка: Как отключить компьютер с С#

ИЛИ

2) Игнорируйте его вообще и выполняйте мою копию. Фактически файлы удаляются, и я обнаружил, что на самом деле нет проблем с наличием папки, которую я не могу удалить, до тех пор, пока я могу написать ей, что я могу. Итак, это тот, который я в итоге выбрал.

Итак, в блоке catch IOException:

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 node, а остальная часть моего класса олицетворения, основанная на моем собственном исполнении нескольких разных (но похожих) кода блоков, как они говорят, чтобы сделать это. Если кому-то это нужно, вот несколько ссылок на некоторые хорошие ответы:

Необходимость олицетворения при доступе к общему сетевому диску

скопировать файлы с аутентификацией в С#

Связанный: Не удается удалить каталог с помощью Directory.Delete(путь, истина)