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

Я пытаюсь программно удалить и заменить содержимое приложения "Приложение A", используя программу "Установщик", которая является просто пользовательским приложением WPF .exe, которое мы назовем "Приложение B". (Мой вопрос касается кода в "Приложении B".)

Настройка GUI (не особенно важно)
Приложение B имеет графический интерфейс, где пользователь может выбирать имена компьютеров для копирования приложения A. Средство выбора файлов используется администратором для заполнения пути к исходному каталогу на локальном компьютере, нажав кнопку "App A.exe". Существуют также текстовые поля для имени пользователя и пароля, поэтому администратор может ввести свои учетные данные для целевого файлового сервера, на котором будет обслуживаться приложение A - код имитирует пользователя, чтобы предотвратить проблемы с разрешениями. Кнопка "Копировать" запускает процедуру.

Уничтожение приложения A, файловые процессы и удаление файлов
Процедура копирования начинается с уничтожения процесса "App A.exe" на всех компьютерах в домене, а также с explorer.exe, если у них была открыта папка обозревателя приложения A. Очевидно, что это будет сделано в нерабочее время, но кто-то, возможно, все еще оставил вещи открытыми и заблокировал их машину, прежде чем идти домой. И это действительно основа проблемы, которую я хочу решить.

Прежде чем копировать обновленные файлы, мы хотим удалить весь старый каталог. Чтобы удалить каталог (и его подкаталоги), каждый файл в них должен быть удален. Но, скажем, у них был файл, открытый из папки приложения А. Код находит любой процесс блокировки любого файла перед его удалением (используя код из ответа Эрика Дж. " Как узнать, какой процесс блокирует файл с помощью.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 ответ

Решение

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

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

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

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

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

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

ИЛИ ЖЕ

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

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

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

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

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

Другие вопросы по тегам