2008-08-04 41 views
50

Estoy buscando una forma de eliminar un archivo que está bloqueado por otro proceso usando C#. Sospecho que el método debe ser capaz de encontrar qué proceso está bloqueando el archivo (quizás siguiendo los identificadores, aunque no estoy seguro de cómo hacerlo en C#) y luego cerrar ese proceso antes de poder completar el archivo eliminar usando File.Delete().¿Cómo elimino un archivo que está bloqueado por otro proceso en C#?

+0

Relacionados - http://stackoverflow.com/questions/1760481/closing-open-files-using -c-sharp/41902677 # 41902677 – vapcguy

Respuesta

34

matar a otros procesos no es una cosa sana a hacer. Si su escenario implica algo así como la desinstalación, puede usar el MoveFileEx API function para marcar el archivo para su eliminación en el próximo reinicio.

Si parece que realmente necesita eliminar un archivo en uso por otro proceso, le recomiendo volver a considerar el problema real antes de considerar cualquier solución.

+6

Tenga en cuenta esta observación de MSDN: 'MOVEFILE_DELAY_UNTIL_REBOOT' -" ... Este valor se puede usar solo si el proceso está en el contexto de un usuario que pertenece al grupo de administradores o al Cuenta LocalSystem ... " –

4

Puede utilizar este programa, Handle, para encontrar qué proceso tiene el bloqueo en su archivo. Es una herramienta de línea de comandos, así que supongo que usas el resultado de eso ... No estoy seguro de encontrarlo programáticamente.

Si la eliminación del archivo puede esperar, se podría especificar que para su eliminación cuando el equipo se inicia inmediatamente superior:

  1. inicio REGEDT32 (W2K) o REGEDIT (WXP) y vaya a:

    HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager 
    
  2. W2K y WXP

    • W2K:

      Edición Agregar valor ...
      Tipo de datos: REG_MULTI_SZ
      Valor Nombre: PendingFileRenameOperations
      OK

    • WXP:
      Editar
      Nueva
      Valor de cadena múltiple
      entrar
      PendingFileRenameOperations

  3. En el área Datos, ingrese "\??\" + filename para eliminar. Los LFN pueden ingresarse sin estar incrustados entre comillas. Para eliminar C:\Long Directory Name\Long File Name.exe, introduzca los siguientes datos:

    \??\C:\Long Directory Name\Long File Name.exe 
    

    A continuación, pulse OK .

  4. El "nombre de archivo de destino" es una cadena nula (cero).Se entra de la siguiente manera:

    • W2K:
      Editar
      binario
      seleccione Formato de datos: Hex
      clic en el extremo de la cadena hexadecimal
      ingrese 0000 (cuatro ceros)
      OK

    • WXP:
      Haga clic en el valor
      elegir la opción "Modificar datos binarios"
      clic al final del hexagonal st
      anillo ingrese 0000 (cuatro ceros)
      OK

  5. Cerrar REGEDT32/REGEDIT y reiniciar el sistema para eliminar el archivo.

(robados Desvergonzadamente de some random forum, por el bien de la posteridad.)

7

Si quieres hacerlo programáticamente. No estoy seguro ... y realmente recomendaría no hacerlo. Si acaba de solucionar las cosas en su propia máquina, SysInternals Process Explorer puede ayudarle a

ejecutarlo, utiliza el Encuentra la manija de mando (creo que es ya sea en el hallazgo o manipular menú), y buscar el nombre de su archivo . Una vez que se encuentra el (los) mango (s), puede cerrarlos a la fuerza.

Puede eliminar el archivo, y así sucesivamente.

Cuidado, hacer esto puede hacer que el programa que posee las asas a comportarse de forma extraña, como usted ha acabado de jalar la alfombra proverbiales de debajo de ella, pero funciona bien cuando se está depurando su propio código errante, o cuando Visual Studio/Windows Explorer es una porquería y no libera manejadores de archivos a pesar de que les dijiste que cerraran el archivo hace siglos ... suspiro :-)

3

Oh, un gran pirateo que empleé hace años, es que Windows ganó Te dejo borrar archivos, pero te deja mover ellos.

Pseudo-clase-de-código:

mv %WINDIR%\System32\mfc42.dll %WINDIR\System32\mfc42.dll.old 
Install new mfc42.dll 
Tell user to save work and restart applications 

Cuando las aplicaciones renovadas (Tenga en cuenta que no era necesario reiniciar la máquina), cargaron el nuevo mfc42.dll, y todo estaba bien. Eso, junto con PendingFileOperations para eliminar el anterior la próxima vez que se reinició todo el sistema, funcionó bastante bien.

14

El método típico es el siguiente. Has dicho que quieres hacer esto en C# así que aquí va ...

  1. Si no sabe qué proceso tiene el archivo bloqueado, deberá examinar la lista de identificadores de cada proceso y consultar cada identificador para determinar si identifica el archivo bloqueado. Hacer esto en C# probablemente requiera que P/Invoke o un C++/CLI intermediario llame a las API nativas que necesitará.
  2. Una vez que haya descubierto qué proceso (es) tiene el archivo bloqueado, tendrá que inyectar con seguridad una pequeña DLL nativa en el proceso (también puede inyectar una DLL administrada, pero esto es más complicado, como entonces tiene que iniciarse o conectarse al tiempo de ejecución de .NET).
  3. Eso bootstrap DLL se cierra el mango usando CloseHandle, etc.

En esencia: la manera de desbloquear un archivo "bloqueado" es inyectar un archivo DLL en el espacio de direcciones del proceso infractor y cerrar por sí mismo. Puedes hacerlo usando código nativo o administrado. No importa qué, vas a necesitar una pequeña cantidad de código nativo o al menos P/Invocar en el mismo.

enlaces útiles:

buena suerte!

4

Consejo de utilización Orion Edwards he descargado los Sysinternals Process Explorer que a su vez me permitió descubrir que el archivo que estaba teniendo dificultades eliminación fue, de hecho, no se celebró por el objeto Excel.Applications, pensé, pero más bien el hecho de que mi código C# envíe el código de correo había creado un objeto Adjunto que dejaba abierto un asa a este archivo.

Una vez que vi esto, simplemente llamé al método de eliminación del objeto Attachment, y se soltó el identificador.

El explorador Sysinternals me permitió descubrir esto usado junto con el depurador Visual   Studio   2005.

¡Recomiendo esta herramienta!

+0

Este fue exactamente mi problema también. Gracias +1 – Fandango68

2

Puede utilizar el código que se proporciona la ruta completa del archivo a, y ha de devolver un List<Processes> de nada bloqueo ese archivo:

using System.Runtime.InteropServices; 
using System.Diagnostics; 

static public class FileUtil 
{ 
    [StructLayout(LayoutKind.Sequential)] 
    struct RM_UNIQUE_PROCESS 
    { 
     public int dwProcessId; 
     public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime; 
    } 

    const int RmRebootReasonNone = 0; 
    const int CCH_RM_MAX_APP_NAME = 255; 
    const int CCH_RM_MAX_SVC_NAME = 63; 

    enum RM_APP_TYPE 
    { 
     RmUnknownApp = 0, 
     RmMainWindow = 1, 
     RmOtherWindow = 2, 
     RmService = 3, 
     RmExplorer = 4, 
     RmConsole = 5, 
     RmCritical = 1000 
    } 

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 
    struct RM_PROCESS_INFO 
    { 
     public RM_UNIQUE_PROCESS Process; 

     [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_APP_NAME + 1)] 
     public string strAppName; 

     [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_SVC_NAME + 1)] 
     public string strServiceShortName; 

     public RM_APP_TYPE ApplicationType; 
     public uint AppStatus; 
     public uint TSSessionId; 
     [MarshalAs(UnmanagedType.Bool)] 
     public bool bRestartable; 
    } 

    [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)] 
    static extern int RmRegisterResources(uint pSessionHandle, 
              UInt32 nFiles, 
              string[] rgsFilenames, 
              UInt32 nApplications, 
              [In] RM_UNIQUE_PROCESS[] rgApplications, 
              UInt32 nServices, 
              string[] rgsServiceNames); 

    [DllImport("rstrtmgr.dll", CharSet = CharSet.Auto)] 
    static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, string strSessionKey); 

    [DllImport("rstrtmgr.dll")] 
    static extern int RmEndSession(uint pSessionHandle); 

    [DllImport("rstrtmgr.dll")] 
    static extern int RmGetList(uint dwSessionHandle, 
           out uint pnProcInfoNeeded, 
           ref uint pnProcInfo, 
           [In, Out] RM_PROCESS_INFO[] rgAffectedApps, 
           ref uint lpdwRebootReasons); 

    /// <summary> 
    /// Find out what process(es) have a lock on the specified file. 
    /// </summary> 
    /// <param name="path">Path of the file.</param> 
    /// <returns>Processes locking the file</returns> 
    /// <remarks>See also: 
    /// http://msdn.microsoft.com/en-us/library/windows/desktop/aa373661(v=vs.85).aspx 
    /// http://wyupdate.googlecode.com/svn-history/r401/trunk/frmFilesInUse.cs (no copyright in code at time of viewing) 
    /// 
    /// </remarks> 
    static public List<Process> WhoIsLocking(string path) 
    { 
     uint handle; 
     string key = Guid.NewGuid().ToString(); 
     List<Process> processes = new List<Process>(); 

     int res = RmStartSession(out handle, 0, key); 
     if (res != 0) throw new Exception("Could not begin restart session. Unable to determine file locker."); 

     try 
     { 
      const int ERROR_MORE_DATA = 234; 
      uint pnProcInfoNeeded = 0, 
       pnProcInfo = 0, 
       lpdwRebootReasons = RmRebootReasonNone; 

      string[] resources = new string[] { path }; // Just checking on one resource. 

      res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null); 

      if (res != 0) throw new Exception("Could not register resource.");          

      //Note: there's a race condition here -- the first call to RmGetList() returns 
      //  the total number of process. However, when we call RmGetList() again to get 
      //  the actual processes this number may have increased. 
      res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons); 

      if (res == ERROR_MORE_DATA) 
      { 
       // Create an array to store the process results 
       RM_PROCESS_INFO[] processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded]; 
       pnProcInfo = pnProcInfoNeeded; 

       // Get the list 
       res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons); 
       if (res == 0) 
       { 
        processes = new List<Process>((int)pnProcInfo); 

        // Enumerate all of the results and add them to the 
        // list to be returned 
        for (int i = 0; i < pnProcInfo; i++) 
        { 
         try 
         { 
          processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId)); 
         } 
         // catch the error -- in case the process is no longer running 
         catch (ArgumentException) { } 
        } 
       } 
       else throw new Exception("Could not list processes locking resource.");      
      } 
      else if (res != 0) throw new Exception("Could not list processes locking resource. Failed to get size of result.");      
     } 
     finally 
     { 
      RmEndSession(handle); 
     } 

     return processes; 
    } 
} 

A continuación, iterar la lista de procesos y cerca de ellos y eliminar los archivos :

string[] files = Directory.GetFiles(target_dir); 
    List<Process> lstProcs = new List<Process>(); 

    foreach (string file in files) 
    { 
     lstProcs = ProcessHandler.WhoIsLocking(file); 
     if (lstProcs.Count > 0) // 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); 
     } 
     else 
      File.Delete(file); 
    } 

Y dependiendo de si el archivo está en el equipo local:

public static void localProcessKill(string processName) 
{ 
    foreach (Process p in Process.GetProcessesByName(processName)) 
    { 
     p.Kill(); 
    } 
} 

o un equipo de red:

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(); 
     } 
    } 
} 

Referencias:
How do I find out which process is locking a file using .NET?

Delete a directory where someone has opened a file

Cuestiones relacionadas