Comment supprimer un fichier verrouillé par un autre processus en C #?

Je cherche un moyen de supprimer un fichier qui est verrouillé par un autre processus en utilisant C #. Je soupçonne que la méthode doit être capable de trouver quel processus verrouille le fichier (peut-être en suivant les descripteurs, même si je ne sais pas comment faire en C #), puis ferme le processus avant de pouvoir supprimer le fichier en utilisant File.Delete() .

Tuer d’autres processus n’est pas une chose saine à faire. Si votre scénario implique quelque chose comme la désinstallation, vous pouvez utiliser la fonction API MoveFileEx pour marquer le fichier à supprimer lors du prochain redémarrage.

S’il apparaît que vous avez vraiment besoin de supprimer un fichier utilisé par un autre processus, je vous recommande de réexaminer le problème réel avant d’envisager des solutions.

La méthode typique est la suivante. Vous avez dit que vous vouliez faire ça en C # alors voilà …

  1. Si vous ne savez pas quel processus a le fichier verrouillé, vous devrez examiner la liste de descripteurs de chaque processus et interroger chaque descripteur pour déterminer s’il identifie le fichier verrouillé. Faire ceci en C # nécessitera probablement P / Invoke ou un intermédiaire C ++ / CLI pour appeler les API natives dont vous aurez besoin.
  2. Une fois que vous avez compris quels processus ont le fichier verrouillé, vous devez injecter en toute sécurité une petite DLL native dans le processus (vous pouvez également injecter une DLL gérée, mais ceci est plus compliqué, car vous devez alors démarrer ou joindre au runtime .NET).
  3. Cette DLL de bootstrap ferme alors le handle en utilisant CloseHandle, etc.

Essentiellement: le moyen de déverrouiller un fichier “verrouillé” consiste à injecter un fichier DLL dans l’espace adresse du processus incriminé et à le fermer vous-même. Vous pouvez le faire en utilisant du code natif ou géré. Quoi qu’il en soit, vous aurez besoin d’une petite quantité de code natif ou au moins P / Invoke dans le même.

Liens utiles:

  • Trois façons d’injecter votre code dans un autre processus
  • Injection de code .NET

Bonne chance!

Si vous voulez le faire par programmation. Je ne suis pas sûr … et je le déconseillerais vraiment. Si vous ne faites que dépanner des choses sur votre propre ordinateur , SysInternals Process Explorer peut vous aider

Exécutez-le, utilisez la commande Find Handle (je pense que c’est soit dans le menu find ou handle), et recherchez le nom de votre fichier. Une fois la ou les poignées trouvées, vous pouvez les fermer de force.

Vous pouvez alors supprimer le fichier, etc.

Attention , cela risque de provoquer un comportement étrange du programme propriétaire des poignées, car vous venez de retirer le tapis proverbial sous lui, mais cela fonctionne bien lorsque vous déboguez votre propre code errant, ou lorsque Visual Studio / Windows Explorer est la merde et ne libère pas les poignées de fichier même si vous leur avez dit de fermer le fichier il y a longtemps … soupir 🙂

Vous pouvez utiliser ce programme, Handle , pour trouver quel processus a le verrou sur votre fichier. C’est un outil de ligne de commande, donc je suppose que vous utilisez la sortie de ce … Je ne suis pas sûr de le trouver par programmation.

Si la suppression du fichier peut attendre, vous pouvez la spécifier pour la suppression au prochain démarrage de votre ordinateur:

  1. Démarrez REGEDT32 (W2K) ou REGEDIT (WXP) et accédez à:

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

    • W2K:
      modifier
      Ajouter de la valeur…
      Type de données: REG_MULTI_SZ
      Nom de valeur: PendingFileRenameOperations
      D'accord

    • WXP:
      modifier
      Nouveau
      Valeur multi-chaîne
      entrer
      PendingFileRenameOperations

  3. Dans la zone Données, entrez "\??\" + filename du "\??\" + filename à supprimer. Les LFN peuvent être saisies sans être incluses dans des guillemets. Pour supprimer C:\Long Directory Name\Long File Name.exe , entrez les données suivantes:

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

    Puis appuyez sur OK .

  4. Le “nom du fichier de destination” est une chaîne nulle (zéro). Il est entré comme suit:

    • W2K:
      modifier
      Binaire
      select Format de données: Hex
      cliquez à la fin de la chaîne hexadécimale
      entrez 0000 (quatre zéros)
      D'accord

    • WXP:
      Cliquez avec le bouton droit sur la valeur
      choisissez “Modifier les données binarys”
      cliquez à la fin de la chaîne hexadécimale
      entrez 0000 (quatre zéros)
      D'accord

  5. Fermez REGEDT32/REGEDIT et redémarrez pour supprimer le fichier.

(Volé sans vergogne dans un forum au hasard , pour la postérité.)

Utilisation des conseils Orion Edwards J’ai téléchargé Sysinternals Process Explorer, ce qui m’a permis de découvrir que le fichier que j’avais des difficultés à supprimer n’était en fait pas détenu par l’object Excel.Applications je pensais, mais plutôt que mon code C # code avait créé un object Attachment qui laissait un handle à ce fichier ouvert.

Une fois que j’ai vu cela, j’ai assez simple d’appeler la méthode dispos de l’object Attachment, et le handle a été libéré.

L’explorateur Sysinternals m’a permis de découvrir cela en conjonction avec le débogueur Visual Studio 2005.

Je recommande fortement cet outil!

Oh, un gros bidouillage que j’ai employé il ya des années, c’est que Windows ne vous laissera pas supprimer des fichiers, mais cela vous permet de les déplacer .

Pseudo-sorte de code:

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

Lorsque les applications ont redémarré (notez que nous n’avons pas eu besoin de redémarrer l’ordinateur), elles ont chargé le nouveau mfc42.dll , et tout s’est bien passé. Cela, associé à PendingFileOperations pour supprimer l’ancien lors du prochain redémarrage du système, a plutôt bien fonctionné.

Cela semble prometteur. Une façon de tuer le descripteur de fichier ….

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

Vous pouvez utiliser le code que vous fournissez le chemin de fichier complet, et il retournera une List de tout ce qui verrouille ce fichier:

 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 ssortingng strAppName; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_SVC_NAME + 1)] public ssortingng 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, ssortingng[] rgsFilenames, UInt32 nApplications, [In] RM_UNIQUE_PROCESS[] rgApplications, UInt32 nServices, ssortingng[] rgsServiceNames); [DllImport("rstrtmgr.dll", CharSet = CharSet.Auto)] static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, ssortingng 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); ///  /// Find out what process(es) have a lock on the specified file. ///  /// Path of the file. /// Processes locking the file /// 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) /// ///  static public List WhoIsLocking(ssortingng path) { uint handle; ssortingng key = Guid.NewGuid().ToSsortingng(); List processes = new List(); 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; ssortingng[] resources = new ssortingng[] { 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((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; } } 

Ensuite, répétez la liste des processus et fermez-les et supprimez les fichiers:

  ssortingng[] files = Directory.GetFiles(target_dir); List lstProcs = new List(); foreach (ssortingng 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); } 

Et selon que le fichier est sur l'ordinateur local:

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

ou un ordinateur en réseau:

 public static void remoteProcessKill(ssortingng computerName, ssortingng fullUserName, ssortingng pword, ssortingng 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(); } } } 

Les références:
Comment puis-je savoir quel processus verrouille un fichier en utilisant .NET?

Supprimer un répertoire où quelqu'un a ouvert un fichier