Pregunta

Estoy buscando una manera de eliminar un archivo que está bloqueado por otro proceso usando C#.Sospecho que el método debe poder encontrar qué proceso está bloqueando el archivo (tal vez rastreando los identificadores, aunque no estoy seguro de cómo hacerlo en C#) y luego cerrar ese proceso antes de poder completar la eliminación del archivo usando File.Delete().

¿Fue útil?

Solución

Matar otros procesos no es algo saludable.Si su escenario implica algo así como la desinstalación, puede usar el MoveFileEx función API para marcar el archivo para su eliminación en el próximo reinicio.

Si parece que realmente necesita eliminar un archivo que está utilizando otro proceso, le recomiendo reconsiderar el problema real antes de considerar cualquier solución.

Otros consejos

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 requerirá que P/Invoke o un C++/CLI intermediario llame a las API nativas que necesitará.
  2. Una vez que haya descubierto qué procesos tienen el archivo bloqueado, necesitará inyectar de forma 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 tendrá que iniciar o adjuntarlo al tiempo de ejecución .NET).
  3. Esa DLL de arranque luego cierra el identificador 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 hacerlo utilizando código nativo o administrado.Pase lo que pase, necesitará una pequeña cantidad de código nativo o al menos P/Invoke en el mismo.

Enlaces Útiles:

¡Buena suerte!

Si quieres hacerlo programáticamente.No estoy seguro...y realmente lo desaconsejaría.Si sólo estás solucionando problemas en tu propia máquina, Explorador de procesos SysInternals puedo ayudarte

Ejecútelo, use el comando Buscar identificador (creo que está en el menú Buscar o Identificar) y busque el nombre de su archivo.Una vez que encuentre las manijas, puede cerrarlas a la fuerza.

Luego puede eliminar el archivo y así sucesivamente.

Tener cuidado, hacer esto puede causar que el programa que posee los identificadores se comporte de manera extraña, ya que acaba de quitarle la proverbial alfombra debajo de él, pero funciona bien cuando está depurando su propio código erróneo, o cuando Visual Studio/Windows Explorer está ser una mierda y no publicar identificadores de archivos a pesar de que les dijiste que cerraran el archivo hace mucho tiempo...suspiro :-)

Puedes utilizar 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 mediante programación.

Si la eliminación del archivo puede esperar, puede especificar que se elimine la próxima vez que se inicie su computadora:

  1. Comenzar REGEDT32 (W2K) o REGEDIT (WXP) y navega a:

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

    • W2K:
      Editar
      Añadir valor...
      Tipo de datos: REG_MULTI_SZ
      Nombre del valor: PendingFileRenameOperations
      DE ACUERDO

    • WXP:
      Editar
      Nuevo
      Valor de varias cadenas
      ingresar
      PendingFileRenameOperations

  3. En el área de datos, ingrese "\??\" + filename para ser eliminado.Los LFN se pueden ingresar sin ser incrustados en citas.Borrar C:\Long Directory Name\Long File Name.exe, introduce los siguientes datos:

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

    Entonces presione DE ACUERDO.

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

    • W2K:
      Editar
      Binario
      seleccione formato de datos:Maleficio
      haga clic al final de la cadena hexadecimal
      ingrese 0000 (cuatro ceros)
      DE ACUERDO

    • WXP:
      Haga clic derecho en el valor
      elija "Modificar datos binarios"
      haga clic al final de la cadena hexadecimal
      ingrese 0000 (cuatro ceros)
      DE ACUERDO

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

(Robado descaradamente de algún foro aleatorio, por el bien de la posteridad.)

Siguiendo los consejos de Orion Edwards, descargué Sysinternals Explorador de procesos lo que a su vez me permitió descubrir que el archivo que tenía dificultades para eliminar en realidad no estaba en manos del Excel.Applications objeto, pensé, sino más bien el hecho de que mi código C# de envío de correo había creado un objeto adjunto que dejaba abierto un identificador para este archivo.

Una vez que vi esto, simplemente invoqué el método de eliminación del objeto Adjunto y se liberó el identificador.

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

¡Recomiendo ampliamente esta herramienta!

Oh, un gran truco que utilicé hace años es que Windows no te permite borrar archivos, pero te permite mover a ellos.

Pseudotipo 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 (tenga en cuenta que no necesitábamos reiniciar la máquina), cargaron el nuevo mfc42.dll, y todo estuvo bien.Eso, aunado a PendingFileOperations Eliminar el anterior la próxima vez que se reinicie todo el sistema funcionó bastante bien.

Esto parece prometedor.Una forma de eliminar el identificador del archivo...

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

Puede utilizar 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, repita la lista de procesos, 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 la computadora local:

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

o una computadora en 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 está bloqueando un archivo usando .NET?

Eliminar un directorio donde alguien ha abierto un archivo

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top