Domanda

Sto cercando un modo per eliminare un file bloccato da un altro processo utilizzando C#.Sospetto che il metodo debba essere in grado di trovare quale processo sta bloccando il file (magari monitorando gli handle, anche se non sono sicuro di come farlo in C#) quindi chiudere il processo prima di poter completare l'eliminazione del file utilizzando File.Delete().

È stato utile?

Soluzione

Uccidere altri processi non è una cosa salutare da fare.Se il tuo scenario prevede qualcosa come la disinstallazione, puoi utilizzare il file MoveFileEx Funzione API per contrassegnare il file per l'eliminazione al prossimo riavvio.

Se sembra che tu abbia davvero bisogno di eliminare un file utilizzato da un altro processo, ti consiglio di riconsiderare il problema reale prima di considerare qualsiasi soluzione.

Altri suggerimenti

Il metodo tipico è il seguente.Hai detto che vuoi farlo in C# quindi ecco qui...

  1. Se non sai quale processo ha bloccato il file, dovrai esaminare l'elenco degli handle di ciascun processo e interrogare ciascun handle per determinare se identifica il file bloccato.Per eseguire questa operazione in C# sarà probabilmente necessario che P/Invoke o un C++/CLI intermediario chiami le API native di cui avrai bisogno.
  2. Una volta che hai capito quali processi hanno bloccato il file, dovrai iniettare in modo sicuro una piccola DLL nativa nel processo (puoi anche iniettare una DLL gestita, ma questo è più complicato, poiché devi iniziare o collegarlo al runtime .NET).
  3. Quella DLL di bootstrap chiude quindi l'handle utilizzando CloseHandle, ecc.

Essenzialmente:il modo per sbloccare un file "bloccato" è iniettare un file DLL nello spazio degli indirizzi del processo incriminato e chiuderlo da soli.Puoi farlo utilizzando codice nativo o gestito.Non importa cosa, avrai bisogno di una piccola quantità di codice nativo o almeno di P/Invoke nello stesso.

Collegamenti utili:

Buona fortuna!

Se vuoi farlo in modo programmatico.Non sono sicuro...e lo sconsiglio davvero.Se stai semplicemente risolvendo problemi sul tuo computer, Esplora processi SysInternals posso aiutarti

Eseguilo, usa il comando Trova handle (penso che sia nel menu Trova o Gestisci) e cerca il nome del tuo file.Una volta trovate le maniglie, è possibile chiuderle forzatamente.

È quindi possibile eliminare il file e così via.

Attenzione, ciò potrebbe far sì che il programma che possiede gli handle si comporti in modo strano, dato che hai appena tolto il proverbiale tappeto da sotto, ma funziona bene quando stai eseguendo il debug del tuo codice errante o quando Visual Studio / Windows Explorer è fare schifo e non rilasciare gli handle di file anche se hai detto loro di chiudere il file secoli fa...sospiro :-)

Puoi usare questo programma, Maniglia, per scoprire quale processo ha il blocco sul tuo file.È uno strumento da riga di comando, quindi immagino che tu usi l'output di quello...Non sono sicuro di trovarlo a livello di codice.

Se l'eliminazione del file può attendere, puoi specificarlo per l'eliminazione al successivo avvio del computer:

  1. Inizio REGEDT32 (W2K) O REGEDIT (WXP) e vai a:

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

    • W2K:
      Modificare
      Aggiungere valore...
      Tipo di dati: REG_MULTI_SZ
      Nome valore: PendingFileRenameOperations
      OK

    • WXP:
      Modificare
      Nuovo
      Valore multistringa
      accedere
      PendingFileRenameOperations

  3. Nell'area Dati, inserisci "\??\" + filename essere cancellato.Le LFN possono essere inserite senza essere incorporate nelle citazioni.Cancellare C:\Long Directory Name\Long File Name.exe, inserisci i seguenti dati:

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

    Quindi premere OK.

  4. Il "nome file di destinazione" è una stringa nulla (zero).Viene inserito come segue:

    • W2K:
      Modificare
      Binario
      seleziona Formato dati:Esadecimale
      fare clic alla fine della stringa esadecimale
      inserisci 0000 (quattro zeri)
      OK

    • WXP:
      Fare clic con il pulsante destro del mouse sul valore
      scegli "Modifica dati binari"
      fare clic alla fine della stringa esadecimale
      inserisci 0000 (quattro zeri)
      OK

  5. Vicino REGEDT32/REGEDIT e riavviare per eliminare il file.

(Rubato spudoratamente qualche forum casuale, per il bene dei posteri.)

Utilizzando i consigli di Orion Edwards ho scaricato Sysinternals Esplora processi il che a sua volta mi ha permesso di scoprire che il file che avevo difficoltà a eliminare in realtà non era detenuto da Excel.Applications object ho pensato, ma piuttosto il fatto che il mio codice C# di invio della posta aveva creato un oggetto Allegato che lasciava aperto un handle per questo file.

Una volta visto questo, ho semplicemente richiamato il metodo Dispose dell'oggetto Allegato e l'handle è stato rilasciato.

L'esploratore Sysinternals mi ha permesso di scoprirlo utilizzato insieme al debugger di Visual Studio 2005.

Consiglio vivamente questo strumento!

Oh, un grosso trucco che ho adottato anni fa è che Windows non te lo permette eliminare file, ma ti consente mossa loro.

Pseudo-tipo-di-codice:

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

Quando le applicazioni si sono riavviate (nota che non è stato necessario riavviare la macchina), hanno caricato il nuovo mfc42.dll, e tutto andava bene.Questo, insieme a PendingFileOperations eliminare quello vecchio al successivo riavvio dell'intero sistema, ha funzionato abbastanza bene.

Sembra promettente.Un modo per uccidere l'handle del file....

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

Puoi utilizzare il codice a cui fornisci il percorso completo del file e restituirà a List<Processes> di qualsiasi cosa blocchi quel file:

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

Quindi, esegui l'iterazione dell'elenco dei processi, chiudili ed elimina i file:

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

E a seconda se il file si trova sul computer locale:

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

o un computer di rete:

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

Riferimenti:
Come faccio a scoprire quale processo sta bloccando un file utilizzando .NET?

Elimina una directory in cui qualcuno ha aperto un file

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top