2017-01-25 1 views
3

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

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

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

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

+0

Вы могли бы выход из системы зарегистрированных пользователей. Вы уже являетесь добрым «грубым» :), убивая explorer.exe, поэтому регистрация пользователя отключит все ваши проблемы. Это лучше, чем перезагрузка с тех пор, как вы рискуете, что сервер не вернется по какой-либо причине. – SledgeHammer

+0

Да, к сожалению, у нас есть десятки тысяч пользователей в домене, и только небольшое подмножество будет пользователем этого приложения. Я думаю, что могу безопасно убить explorer.exe (так как он перезапустится, и люди могут легко открывать папки) ...даже несмотря на то, что наша политика заключается в том, что каждый пользователь должен выйти из системы, когда они уйдут, и мы фактически перезагружаем компьютеры при установке обновлений, я не уверен, что отладка в домене - это тот подход, который нам нужен, но может оказаться целесообразным перейти на наш пользовательский стол в нашей базе данных, чтобы найти их. Можем ли мы сделать такой избирательный подход? Как вы привязываете пользователей к входным компьютерам? – vapcguy

+0

Подождите .. приложение работает на MachineX и пользователи RDP в MachineX? Или они запускают приложение удаленно с помощью UNC? Или приложение работает на клиентских компьютерах и некоторые компоненты указывают на MachineX? – SledgeHammer

ответ

1

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

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

Ссылка:How to shut down the computer from 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 узел в нем, а остальную часть моего олицетворения класса, который основан на мое собственное исполнение нескольких разных (но похожих) блоков кода того, как они говорят, чтобы сделать это. Если кому-то нужен, что, вот несколько ссылок на некоторые хорошие ответы:

Need Impersonation when accessing shared network drive

copy files with authentication in c#

Связанный:Cannot delete directory with Directory.Delete(path, true)