¿Cómo elimino un archivo que está bloqueado por otro proceso en C#?
file-locking (8)
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 hacer esto en C #) y luego cerrar ese proceso antes de poder completar la eliminación del archivo usando
File.Delete()
.
El método típico es el siguiente. Has dicho que quieres hacer esto en C #, así que aquí va ...
- 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á P / Invoke o un C ++ / CLI intermediario para llamar a las API nativas que necesitará.
- Una vez que haya descubierto qué procesos tienen 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 complicado, ya que luego debe comenzar o adjuntar al tiempo de ejecución .NET).
- 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 ofensivo 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 / Invocar en el mismo.
Enlaces Útiles:
¡Buena suerte!
Esto parece prometedor. Una forma de matar el identificador de archivos ...
http://www.timstall.com/2009/02/killing-file-handles-but-not-process.html
Matar otros procesos no es algo saludable.
Si su escenario implica algo como la desinstalación, puede usar la
función API
MoveFileEx
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.
Oh, un gran truco que empleé hace años, es que Windows no te permite eliminar archivos, pero sí te permite moverlos .
Pseudo-tipo-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 estaba bien.
Eso, junto con
PendingFileOperations
para eliminar el anterior la próxima vez que se reiniciara todo el sistema, funcionó bastante bien.
Puede usar el código al que proporciona la ruta completa del archivo, y devolverá una
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 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 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?
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 usa 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:
-
Inicie
REGEDT32 (W2K)
oREGEDIT (WXP)
y navegue hasta:HKEY_LOCAL_MACHINE/System/CurrentControlSet/Control/Session Manager
-
W2K y WXP
-
W2K:
Editar
Añadir valor...
Tipo de datos:REG_MULTI_SZ
Nombre de valor:PendingFileRenameOperations
Okay -
WXP:
Editar
Nuevo
Valor de múltiples cadenas
entrar
PendingFileRenameOperations
-
-
En el área de Datos, ingrese
"/??/" + filename
de"/??/" + filename
que se eliminará. Los LFN se pueden ingresar sin incluirlos entre comillas. Para eliminarC:/Long Directory Name/Long File Name.exe
, ingrese los siguientes datos:/??/C:/Long Directory Name/Long File Name.exe
Luego presione OK .
-
El "nombre del archivo de destino" es una cadena nula (cero). Se ingresa de la siguiente manera:
-
W2K:
Editar
Binario
seleccione Formato de datos: Hex
haga clic al final de la cadena hexadecimal
ingrese 0000 (cuatro ceros)
Okay -
WXP:
Haga clic derecho en el valor
elija "Modificar datos binarios"
haga clic al final de la cadena hexadecimal
ingrese 0000 (cuatro ceros)
Okay
-
-
Cierre
REGEDT32/REGEDIT
y reinicie para eliminar el archivo.
(Desvergonzadamente robado de algún foro aleatorio , por el bien de la posteridad).
Si quieres hacerlo programáticamente. No estoy seguro ... y realmente lo recomendaría en contra. 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ú find o handle) y busque el nombre de su archivo. Una vez que se encuentran los identificadores, puede cerrarlos a la fuerza.
Luego puede eliminar el archivo, etc.
Tenga cuidado , esto puede hacer que el programa que posee los controladores se comporte de manera extraña, ya que acaba de sacar la alfombra proverbial, pero funciona bien cuando está depurando su propio código errante, o cuando Visual Studio / Windows Explorer está siendo una mierda y no está liberando identificadores de archivos a pesar de que les dijiste que cerraran el archivo hace mucho tiempo ... suspiro :-)
Utilizando el consejo de Orion Edwards, descargué el Sysinternals
Process Explorer
que a su vez me permitió descubrir que el archivo que tenía dificultades para eliminar no estaba en realidad en el objeto
Excel.Applications
que pensé, sino en el hecho de que mi código C # envía un correo El código había creado un objeto Adjunto que dejaba abierto un identificador para este archivo.
Una vez que vi esto, llamé al método de eliminación del objeto Adjunto, y se soltó el identificador.
El explorador Sysinternals me permitió descubrir esto usado junto con el depurador de Visual Studio 2005.
¡Recomiendo esta herramienta!