Wie lösche ich eine Datei, die von einem anderen Prozess in C# gesperrt ist?

StackOverflow https://stackoverflow.com/questions/1040

  •  08-06-2019
  •  | 
  •  

Frage

Ich suche nach einer Möglichkeit, eine Datei zu löschen, die von einem anderen Prozess mit C# gesperrt wurde.Ich vermute, dass die Methode in der Lage sein muss, herauszufinden, welcher Prozess die Datei sperrt (vielleicht durch Verfolgen der Handles, obwohl ich nicht sicher bin, wie das in C# geht) und diesen Prozess dann zu schließen, bevor das Löschen der Datei mit abgeschlossen werden kann File.Delete().

War es hilfreich?

Lösung

Das Beenden anderer Prozesse ist keine gesunde Sache.Wenn Ihr Szenario so etwas wie eine Deinstallation beinhaltet, können Sie die verwenden MoveFileEx API-Funktion um die Datei beim nächsten Neustart zum Löschen zu markieren.

Wenn es den Anschein hat, dass Sie eine Datei, die von einem anderen Prozess verwendet wird, wirklich löschen müssen, würde ich empfehlen, das eigentliche Problem noch einmal zu prüfen, bevor Sie über Lösungen nachdenken.

Andere Tipps

Die typische Methode ist wie folgt.Sie haben gesagt, dass Sie dies in C# tun möchten, also geht es los ...

  1. Wenn Sie nicht wissen, welcher Prozess die Datei gesperrt hat, müssen Sie die Handle-Liste jedes Prozesses untersuchen und jedes Handle abfragen, um festzustellen, ob es die gesperrte Datei identifiziert.Um dies in C# zu tun, ist wahrscheinlich P/Invoke oder eine zwischengeschaltete C++/CLI erforderlich, um die benötigten nativen APIs aufzurufen.
  2. Sobald Sie herausgefunden haben, bei welchen Prozessen die Datei gesperrt ist, müssen Sie sicher eine kleine native DLL in den Prozess einfügen (Sie können auch eine verwaltete DLL einfügen, aber das ist komplizierter, da Sie dann starten müssen oder an die .NET-Laufzeit anhängen).
  3. Diese Bootstrap-DLL schließt dann das Handle mithilfe von CloseHandle usw.

Im Wesentlichen:Der Weg, eine „gesperrte“ Datei zu entsperren, besteht darin, eine DLL-Datei in den Adressraum des betreffenden Prozesses einzufügen und ihn selbst zu schließen.Sie können dies mit nativem oder verwaltetem Code tun.Egal was passiert, Sie benötigen eine kleine Menge nativen Code oder zumindest P/Invoke in denselben.

Hilfreiche Links:

Viel Glück!

Wenn Sie es programmgesteuert tun möchten.Ich bin mir nicht sicher...und ich würde wirklich davon abraten.Wenn Sie nur Probleme auf Ihrem eigenen Computer beheben, SysInternals-Prozess-Explorer kann dir helfen

Führen Sie es aus, verwenden Sie den Befehl „Handle suchen“ (ich glaube, er befindet sich entweder im Menü „Suchen“ oder im Menü „Handle“) und suchen Sie nach dem Namen Ihrer Datei.Sobald der/die Griff(e) gefunden wurde(n), können Sie ihn/sie zwangsweise schließen.

Anschließend können Sie die Datei löschen usw.

In acht nehmen, Dies kann dazu führen, dass sich das Programm, dem die Handles gehören, seltsam verhält, da Sie ihm gerade den sprichwörtlichen Boden unter den Füßen weggezogen haben. Es funktioniert jedoch gut, wenn Sie Ihren eigenen fehlerhaften Code debuggen oder wenn Visual Studio/Windows Explorer dies tut Es ist Mist und gibt keine Datei-Handles frei, obwohl Sie ihnen schon vor langer Zeit gesagt haben, sie sollen die Datei schließen ...seufzen :-)

Sie können dieses Programm verwenden, Handhaben, um herauszufinden, welcher Prozess Ihre Datei gesperrt hat.Da es sich um ein Befehlszeilentool handelt, verwenden Sie vermutlich die Ausgabe davon ...Ich bin mir nicht sicher, ob ich es programmgesteuert finden soll.

Wenn das Löschen der Datei warten kann, können Sie sie beim nächsten Start Ihres Computers zum Löschen angeben:

  1. Start REGEDT32 (W2K) oder REGEDIT (WXP) und navigieren Sie zu:

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

    • W2K:
      Bearbeiten
      Mehrwert...
      Datentyp: REG_MULTI_SZ
      Wertname: PendingFileRenameOperations
      OK

    • WXP:
      Bearbeiten
      Neu
      Multi-String-Wert
      eingeben
      PendingFileRenameOperations

  3. Geben Sie im Bereich Daten ein "\??\" + filename gelöscht werden.LFNs können eingegeben werden, ohne in Zitate eingebettet zu werden.Löschen C:\Long Directory Name\Long File Name.exe, Geben Sie folgende Daten ein:

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

    Dann drücken OK.

  4. Der „Zieldateiname“ ist eine Nullzeichenfolge.Es wird wie folgt eingegeben:

    • W2K:
      Bearbeiten
      Binär
      Wählen Sie Datenformat:Verhexen
      Klicken Sie auf das Ende der Hex-Zeichenfolge
      Geben Sie 0000 (vier Nullen) ein.
      OK

    • WXP:
      Klicken Sie mit der rechten Maustaste auf den Wert
      Wählen Sie „Binärdaten ändern“
      Klicken Sie auf das Ende der Hex-Zeichenfolge
      Geben Sie 0000 (vier Nullen) ein.
      OK

  5. Schließen REGEDT32/REGEDIT und starten Sie neu, um die Datei zu löschen.

(Schamlos gestohlen von irgendein zufälliges Forum, der Nachwelt zuliebe.)

Den Ratschlägen von Orion Edwards folgend habe ich die Sysinternals heruntergeladen Process Explorer Dadurch konnte ich wiederum feststellen, dass die Datei, die ich nur schwer löschen konnte, tatsächlich nicht im Besitz von war Excel.Applications Objekt, dachte ich, sondern vielmehr die Tatsache, dass mein C#-Code zum Senden von E-Mails ein Anhangsobjekt erstellt hatte, das ein Handle für diese Datei offen ließ.

Als ich das sah, rief ich ganz einfach die dispose-Methode des Attachment-Objekts auf und das Handle wurde freigegeben.

Der Sysinternals-Explorer ermöglichte es mir, dies in Verbindung mit dem Visual Studio 2005-Debugger zu entdecken.

Ich kann dieses Tool nur wärmstens empfehlen!

Oh, ein großer Hack, den ich vor Jahren angewendet habe, ist, dass Windows das nicht zulässt löschen Dateien, aber es lässt Sie bewegen ihnen.

Pseudo-Code:

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

Als die Anwendungen neu gestartet wurden (beachten Sie, dass wir den Computer nicht neu starten mussten), haben sie die neuen geladen mfc42.dll, und alles war gut.Das, gepaart mit PendingFileOperations Das alte beim nächsten Neustart des gesamten Systems zu löschen, hat ziemlich gut funktioniert.

Das sieht vielversprechend aus.Eine Möglichkeit, das Dateihandle zu töten ...

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

Sie können Code verwenden, zu dem Sie den vollständigen Dateipfad angeben, und dieser gibt einen zurück List<Processes> von irgendetwas, das diese Datei sperrt:

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;
    }
}

Anschließend durchlaufen Sie die Liste der Prozesse, schließen sie und löschen die Dateien:

    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);
    }

Und je nachdem, ob sich die Datei auf dem lokalen Computer befindet:

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

oder ein Netzwerkcomputer:

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();
        }
    }
}

Verweise:
Wie finde ich heraus, welcher Prozess eine Datei mit .NET sperrt?

Löschen Sie ein Verzeichnis, in dem jemand eine Datei geöffnet hat

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top