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


Estoy buscando una manera 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 controladores, aunque no estoy seguro de cómo hacer esto en C#) luego cerrar ese proceso antes de poder completar el archivo de eliminación utilizando File.Delete().

Author: ckittel, 2008-08-04

8 answers

Matar otros procesos no es algo saludable. Si su escenario implica algo como la desinstalación, podría usar el MoveFileEx Función API para marcar el archivo para su eliminación en el siguiente reinicio.

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

 34
Author: Ishmaeel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-08-22 07:15:09

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 controladores de cada proceso y consultar cada controlador para determinar si identifica el archivo bloqueado. Hacer esto en C# probablemente requerirá P/Invoke o un intermediario C++/CLI para llamar a las API nativas que necesitará.
  2. Una vez que haya averiguado qué proceso(es) tiene el archivo bloqueado, deberá inyectar de forma segura una pequeña DLL nativa en el proceso (también puede inyectar una DLL administrada, pero esto es más messier, ya que luego tiene que iniciar o adjuntar al tiempo de ejecución de.NET).
  3. Esa DLL de bootstrap cierra el handle usando CloseHandle, etc.

Esencialmente: la forma de desbloquear un archivo "bloqueado" es inyectar un archivo DLL en el espacio de direcciones del proceso infractor y cerrarlo usted mismo. Puede hacer esto utilizando código nativo o administrado. No importa qué, vas a necesitar una pequeña cantidad de código nativo o en menos P / Invoke en el mismo.

Enlaces útiles:

¡Buena suerte!

 15
Author: Peter Mortensen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-31 22:41:04

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

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

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

Cuidado, hacer esto puede causa que el programa que posee los manejadores se comporte de manera extraña, ya que acabas de sacar la alfombra proverbial de debajo de él, pero funciona bien cuando estás depurando tu propio código errante, o cuando visual studio / windows Explorer está siendo basura y no liberando manejadores de archivos a pesar de que les dijiste que cerraran el archivo hace años... suspiro :-)

 7
Author: Orion Edwards,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-08-04 06:12:11

Puede usar 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 la salida 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 computadora:

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

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

    • W2K:
      Editar
      Agregar Valor...
      Tipo de Datos: REG_MULTI_SZ
      Nombre de Valor: PendingFileRenameOperations
      OK

    • WXP:
      Editar
      Nuevo
      Multi-Cadena de Valor
      entrar
      PendingFileRenameOperations

  3. En el área de datos, introduzca "\??\" + filename para borrar. LFNs mayo ser introducido sin ser incrustado entre comillas. Para eliminar C:\Long Directory Name\Long File Name.exe, introduzca los siguientes datos:

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

    Luego presione OK .

  4. El "nombre de archivo de destino" es una cadena nula (cero). Se introduce como sigue:

    • W2K:
      Editar
      Binario
      seleccionar formato de datos: Hexadecimal
      haga clic al final de la cadena hexadecimal
      introduzca 0000 (cuatro ceros)
      OK

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

  5. Cierre REGEDT32/REGEDIT y reinicie para eliminar el archivo.

(Robado descaradamente de un foro aleatorio, por el bien de la posteridad.)

 4
Author: Ryan Fox,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-30 16:13:42

Usando el consejo de Orion Edwards descargué el Explorador de Procesos Sysinternals que a su vez me permitió descubrir que el archivo que estaba teniendo dificultades para eliminar no estaba retenido por el objeto Excel.Applications que pensé, sino más bien por el hecho de que mi código C# send mail code había creado un objeto Adjunto que dejaba abierto un identificador para este archivo.

Una vez que vi esto, simplemente llamé al método dispose del objeto Adjunto, y el mango se liberó.

El Sysinternals explorer me permitió descubrir esto usado en conjunto con el depurador de Visual Studio 2005.

Recomiendo encarecidamente esta herramienta!

 4
Author: Peter Mortensen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-31 22:44:03

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

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 se reiniciaron (nota que no necesitábamos reiniciar la máquina), cargaron el nuevo mfc42.dll, y todo estaba bien. Eso, junto con PendingFileOperations para eliminar el antiguo la próxima vez que se reinicie todo el sistema, funcionó bastante bien.

 3
Author: Orion Edwards,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-09-20 20:52:11

Esto parece prometedor. Una forma de matar al manejador de archivos....

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

 3
Author: solrevdev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-01-15 14:25:53

Puede usar el código al que proporciona la ruta completa del archivo, y devolverá un List<Processes> de cualquier cosa que bloquee 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;
    }
}

Luego, itere la lista de procesos y ciérrelos y elimine 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 ordenador 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 averiguar qué proceso está bloqueando un archivo usando. NET?

Eliminar un directorio donde alguien ha abierto un archivo

 3
Author: vapcguy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:17:58