¿Cómo elimino un archivo que está bloqueado por otro proceso en C #?

votos
51

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 (tal vez mediante el seguimiento de los identificadores, aunque no estoy seguro de cómo hacerlo en C #) y luego cierre ese proceso antes de poder completar el borrado del archivo File.Delete().

Publicado el 04/08/2008 a las 06:45
fuente por usuario
En otros idiomas...                            


8 respuestas

votos
34

Matar a otros procesos no es algo saludable que hacer. Si su escenario implica algo así como la desinstalación, puede usar la MoveFileExfunción API 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.

Respondida el 04/08/2008 a las 07:01
fuente por usuario

votos
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 ha bloqueado el archivo, deberá examinar la lista de manejo 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, necesitará insertar de manera segura una pequeña DLL nativa en el proceso (también puede inyectar una DLL administrada, pero esto es más complicado, ya que luego debe comenzar o adjuntarlo al tiempo de ejecución .NET).
  3. Ese archivo DLL de arranque luego cierra el manejador usando CloseHandle, etc.

Esencialmente: la forma de desbloquear un archivo "bloqueado" es inyectar un archivo DLL en el espacio de direcciones del proceso ofensivo y cerrarlo usted 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!

Respondida el 04/08/2008 a las 07:15
fuente por usuario

votos
7

Si quieres hacerlo programáticamente. No estoy seguro ... y realmente recomendaría no hacerlo. Si solo está solucionando problemas en su propia máquina, SysInternals Process Explorer puede ayudarlo

Ejecútelo, use el comando Find Handle (creo que está en el menú de buscar o manejar) y busque el nombre de su archivo. Una vez que se encuentra el (los) mango (s), puede cerrarlos a la fuerza.

A continuación, puede eliminar el archivo y así sucesivamente.

Cuidado , hacer esto puede hacer que el programa que posee los controles se comporte de manera extraña, ya que sacaste el tapete proverbial de debajo, pero funciona bien cuando estás depurando tu 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 :-)

Respondida el 04/08/2008 a las 07:12
fuente por usuario

votos
4

Utilizando el consejo de Orion Edwards, descargué Sysinternals Process Explorer, que a su vez me permitió descubrir que el archivo que estaba teniendo dificultades para eliminar estaba en realidad retenido no por el Excel.Applicationsobjeto que yo creía, sino por el hecho de que mi código C # un objeto de Adjunto que dejó un identificador a este archivo abierto.

Una vez que vi esto, simplemente llamé al método dispose del objeto Attachment, y se lanzó el handle.

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

¡Recomiendo esta herramienta!

Respondida el 04/03/2009 a las 14:43
fuente por usuario

votos
4

Puede usar este programa, Manejar , 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, puede especificarlo para su eliminación cuando se inicie su próxima computadora:

  1. Comience REGEDT32 (W2K)o REGEDIT (WXP)y navegue a:

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

    • W2K:
      Editar
      Agregar valor ...
      Tipo de datos: REG_MULTI_SZ
      Nombre del valor:PendingFileRenameOperations
      OK

    • WXP:
      Editar
      nuevo
      valor de cadenas múltiples
      enter
      PendingFileRenameOperations

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

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

    Luego presiona OK.

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

    • W2K:
      Edit
      Binary
      select Formato de datos: Hex
      clic al final de la cadena hex,
      ingrese 0000 (cuatro ceros)
      OK

    • WXP:
      haga clic con el botón derecho en el valor,
      elija "Modificar datos binarios",
      haga clic en al final de la cadena hexadecimal
      ingrese 0000 (cuatro ceros)
      OK

  5. Cierre REGEDT32/REGEDITy reinicie para eliminar el archivo.

(Desvergonzadamente robado de algún foro al azar , por el bien de la posteridad).

Respondida el 04/08/2008 a las 06:59
fuente por usuario

votos
3

Esto parece prometedor. Una forma de matar el archivo manejar ....

http://www.timstall.com/2009/02/killing-file-handles-but-not-process.html

Respondida el 25/02/2009 a las 18:41
fuente por usuario

votos
3

Oh, un gran truco que empleé hace años, es que Windows no le permitirá eliminar archivos, pero sí le permite moverlos .

Pseudo-sort-of-code:

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 se reiniciaron (tenga en cuenta que no necesitábamos reiniciar la máquina), cargaron la nueva mfc42.dlly todo estuvo bien. Eso, junto con PendingFileOperationseliminar el anterior la próxima vez que se reinició todo el sistema, funcionó bastante bien.

Respondida el 04/08/2008 a las 07:14
fuente por usuario

votos
2

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

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, recorrer 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:
¿Cómo puedo saber qué proceso es el bloqueo de un archivo usando .NET?

Eliminar un directorio en la que alguien ha abierto un archivo

Respondida el 27/01/2017 a las 17:45
fuente por usuario

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more