C# Problem: Dateien in Benutzung

lynxx

Lt. Junior Grade
Registriert
Feb. 2005
Beiträge
436
Hallo,

ich habe immer wieder das Problem das Dateien unter Win7 angeblich "in Benutzung" sind und deshalb nicht gelöscht/überschrieben werden können, hauptsächlich stört mich das im Microsoft Visual C# 2010 Express.

Damals (lang, lang ists her :evillol:) auf dem Amiga hatte ich mir einen Patch geschrieben der zu löschende Dateien nicht wirklich löscht sondern in einen Ordner "KAN" verschiebt, also so eine Art automatischer Papierkorb.

Also habe ich als erst ein kleines Programm "Cleanup" geschrieben das einfach alle Dateien im übergebenen Verzeichnis nach X:\KAN\ verschiebt und dann löscht.
In Microsoft Visual C# 2010 Express trage ich dann bei Projekten als Präbuild:
Cleanup "$(TargetDir)" ein, und das funktioniert auch wunderbar, keine Probleme mehr mit Neukompilierungsfehlern wegen "in Benutzung".

Aber ich hätte lieber eine globalere Lösung für das Problem, also habe ich ein Tool geschrieben das die DeleteFileA & DeleteFileW-Funktionen der kernel32.dll patch, ehergesagt umleitet - dort wird genau das selbe gemacht, Datei erst nach KAN verschieben und danach löschen (wenn deletemode = true).
Auch das Tool funktioniert wunderbar, Problem ist nur das in Win7 alle Threads eine "eigene" kernel32.dll haben, dieser Patch also nur für das aktuelle Programm aktiv ist.

Jetzt weiss ich nicht weiter, soll ich versuchen den Code im Ring0 (kernelmode) ausführen zu lassen (ich weiss, ist nicht trivial), oder hat sich schonmal jemand mit Layered Filter Drivers beschäftigt, imho kann man damit das selbe erreichen. Oder hat jemand eine andere Idee woher dieser "in Benutzung"-Bug kommt ? Indexer ist auf Projekt-Verzeichnis ausgeschaltet, Virenscanner ebenfalls. Im Sysinternals-Processmonitor sieht man nur das System oft ewig die Dateien offen hält, weswegen Delete nicht funktioniert.

Ich habe schonmal das Windows Driver Kit Version 7.1.0 heruntergeladen - damit kann man wohl Kerneltreiber erstellen, leider sind keine Samples für C# dabei.

Hier mein nur für den aktuellen Thread aktive KANSystem: (Kommentare sind weiter rechts. :D)
PHP:
// Patches kernel32.dll DeleteFileA & DeleteFileW to new functions, which move the file first to X:\KAN\.. and then delete it.
// Done by Holger 'Lynxx' Hippenstiel in Sep.2011
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;

namespace KANSystem {
  static class KANSystem {
    [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
    static extern IntPtr LoadLibrary(string strFileName);
    [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
    static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
    [DllImport("kernel32.dll", EntryPoint = "MoveFileW", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
    static extern bool MoveFile(string lpExistingFileName, string lpNewFileName);
    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool DeleteFileA([MarshalAs(UnmanagedType.LPStr)]string lpFileName);
    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool DeleteFileW([MarshalAs(UnmanagedType.LPWStr)]string lpFileName);
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool VirtualProtect(IntPtr lpAddress, uint dwSize, uint flNewProtect, out uint lpflOldProtect);
    [DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
    static extern IntPtr memcpy(IntPtr dest, IntPtr src, int count);
    
    [Flags]
    public enum Prot {                                                                        // Possible Protectionflags for VirtualProtect
      PAGE_NOACCESS = 0x01,
      PAGE_READONLY = 0x02,
      PAGE_READWRITE = 0x04,
      PAGE_WRITECOPY = 0x08,
      PAGE_EXECUTE = 0x10,
      PAGE_EXECUTE_READ = 0x20,
      PAGE_EXECUTE_READWRITE = 0x40,
      PAGE_EXECUTE_WRITECOPY = 0x80,
      PAGE_GUARD = 0x100,
      PAGE_NOCACHE = 0x200,
      PAGE_WRITECOMBINE = 0x400
    }

    static bool debug = false;                                                                // Use local stack or FPO in Assemblercode
    static bool deletemode = false;                                                           // Delete after moving?
    static byte[] sourcearray = { 0x90, 0x90, 0x90, 0x90, 0x90, 0x8B, 0xFF };                 // Original Code in Functions should be: 5x nop + mov edi, edi

    static byte[] deleteFileACodeDebugger = {                                                 // Call got own Local stack
      0x8b, 0x45, 0x8,                                                                        // mov eax, [ebp+8]
      0x90,                                                                                   // nop (to have same offset as in NoDebug-Code)
      0x89, 0x5, 0,0,0,0,                                                                     // mov [4byte-adr], eax - store Argumentptr in deleteFileAArg
      0xE8, 0,0,0,0,                                                                          // call 4-byte adr - points to NewDeleteFileA
      0xC9,                                                                                   // leave
      0xC3                                                                                    // ret
    };

    static byte[] deleteFileACodeNoDebug = {                                                  // "Frame Pointer Omission" active
      0x8b, 0x44, 0x24, 0x8,                                                                  // mov eax,[esp+8]
      0x89, 0x5, 0,0,0,0,                                                                     // mov [4byte-adr], eax - store Argumentptr in deleteFileAArg
      0xE8, 0,0,0,0,                                                                          // call 4-byte adr - points to NewDeleteFileA
      0xC9,                                                                                   // leave
      0xC3                                                                                    // ret
    };

    static byte[] deleteFileWCodeDebugger = {                                                 // Call got own Local stack
      0x8b, 0x45, 0x8,                                                                        // mov eax, [ebp+8]
      0x90,                                                                                   // nop (to have same offset as in NoDebug-Code)
      0x89, 0x5, 0,0,0,0,                                                                     // mov [4byte-adr], eax - store Argumentptr in deleteFileWArg
      0xE8, 0,0,0,0,                                                                          // call 4-byte adr - points to NewDeleteFileW
      0xC9,                                                                                   // leave
      0xC3                                                                                    // ret
    };

    static byte[] deleteFileWCodeNoDebug = {                                                  // "Frame Pointer Omission" active
      0x8b, 0x44, 0x24, 0x8,                                                                  // mov eax,[esp+8]
      0x89, 0x5, 0,0,0,0,                                                                     // mov [4byte-adr], eax - store Argumentptr in deleteFileWArg
      0xE8, 0,0,0,0,                                                                          // call 4-byte adr - points to NewDeleteFileW
      0xC9,                                                                                   // leave
      0xC3                                                                                    // ret
    };

    static byte[] deleteFileACode;                                                            // Used code depends if Debugger attached/Frameworkversion
    static byte[] deleteFileWCode;                                                            // Used code depends if Debugger attached/Frameworkversion
    static byte[] deleteFileAArg = new byte[4];                                               // Argument for DeleteFileA
    static byte[] deleteFileWArg = new byte[4];                                               // Argument for DeleteFileW
    static long oldDeleteFileA = 0;                                                           // Old DeleteFileA-Function
    static long oldDeleteFileW = 0;                                                           // Old DeleteFileW-Function

    [STAThread]
    static void Main(String[] mainargs) {
      debug = Debugger.IsAttached;                                                            // Use FPO-Assembler when no Debugger is attached
      if (Environment.Version.Major == 2) debug = false;                                      // .NET 2.0 always calls with FPO

      deleteFileWCode = debug ? deleteFileWCodeDebugger : deleteFileWCodeNoDebug;             // Debugger / NoDebug contains different code
      deleteFileACode = debug ? deleteFileACodeDebugger : deleteFileACodeNoDebug;             // Debugger / NoDebug contains different code

      bool bError = false;
      // Set new Functions for DeleteFileA & DeleteFileW
      oldDeleteFileA = HotPatch((NewDeleteFileADelegate)NewDeleteFileA, deleteFileACode, deleteFileAArg, "DeleteFileA");
      oldDeleteFileW = HotPatch((NewDeleteFileWDelegate)NewDeleteFileW, deleteFileWCode, deleteFileWArg, "DeleteFileW");
      if (oldDeleteFileW != 0 && oldDeleteFileA != 0) {
        Print("Functions patched, testing functionality ...");

        bool bOrgdeletemode = deletemode;                                                     // Store old deletemode-setting
        deletemode = false;                                                                   // Switch of deleting for test
        String strTempFile = Path.GetTempFileName();                                          // Create a testfile
        Print("Testfile created: '" + strTempFile + "', will now 'Delete' it.");
        File.Delete(strTempFile);                                                             // And "delete" it
        
        String strDrive = Path.GetDirectoryName(strTempFile);                                 // Drive of testfile
        strDrive = Path.Combine(strDrive.Substring(0, strDrive.IndexOf(":\\") + 2), "KAN");   // X:\KAN
        String strKANName = Path.Combine(strDrive, Path.GetFileName(strTempFile));            // X:\KAN\tempfile
        bError = !File.Exists(strKANName);                                                    // File should now be in X:\KAN\...
        if (!bError) {                                                                        // No error should occurred
          Print("Deleting: '" + strKANName + "'");
          Function(oldDeleteFileW, strKANName);                                               // Delete the testfile
        }
        deletemode = bOrgdeletemode;                                                          // Restore old deletemode-setting
      }
      else
        bError = true;

      if (bError) Print("Error patching.");                                                   // Either functions didnt match source, or move to KAN failed
      else {
        Print("Successfully patched.\nPress Return ...");
        Console.In.ReadLine();
      }

      if (oldDeleteFileA != 0) UnPatch("DeleteFileA");                                        // Remove patches ..
      if (oldDeleteFileW != 0) UnPatch("DeleteFileW");
      Print("Patches removed.");
    }

    delegate bool CFuncDelegate(IntPtr strFileName);                                          // Call original Assembler-Function
    static bool Function(long CFunc, string strFileName) {
      CFuncDelegate func = (CFuncDelegate)Marshal.GetDelegateForFunctionPointer((IntPtr)CFunc, typeof(CFuncDelegate));
      char[] cFilename = strFileName.ToCharArray();
      return func(Marshal.UnsafeAddrOfPinnedArrayElement(cFilename, 0));
    }

    delegate bool NewDeleteFileADelegate();                                                   // Declare delegate -- defines required signature
    static bool NewDeleteFileA() {                                                            // Move to X:\KAN, then delete
      bool retVal = false;
      String strFileName = getFileName(deleteFileAArg);                                       // Get stored IntelPtr in deleteFileAArg
      if (strFileName != null) {
        Print("NewDeleteFileA called: '" + strFileName + "'");
        String strNewName = MoveFileToKAN(strFileName);                                       // Either move the File to KAN and remove new name or return old name
        if (deletemode)
          retVal = Function(oldDeleteFileA, strNewName);                                      // Delete after moving, or in case it couldn't be moved - delete original
        else {
          if (strNewName == strFileName)                                                      // Filename same as old name = couldn't be moved
            retVal = Function(oldDeleteFileA, strFileName);                                   // Delete original
        }
      }
      else
        Print("NewDeleteFileA: Error No filename");

      return retVal;
    }

    delegate bool NewDeleteFileWDelegate();                                                   // Declare delegate -- defines required signature
    static bool NewDeleteFileW() {                                                            // Move to X:\KAN, then delete
      bool retVal = false;
      String strFileName = getFileName(deleteFileWArg);                                       // Get stored IntelPtr in deleteFileWArg
      if (strFileName != null) {
        Print("NewDeleteFileW called: '" + strFileName + "'");
        String strNewName = MoveFileToKAN(strFileName);                                       // Either move the File to KAN and remove new name or return old name
        if (deletemode)
          retVal = Function(oldDeleteFileW, strNewName);                                      // Delete after moving, or in case it couldn't be moved - delete original
        else {
          if (strNewName == strFileName)                                                      // Filename same as old name = couldn't be moved
            retVal = Function(oldDeleteFileW, strFileName);                                   // Delete original
        }
      }
      else
        Print("NewDeleteFileW: Error No filename");

      return retVal;
    }

    static String getFileName(byte[] ptr) {                                                   // Get String from IntelPtr
      long ptrFileName = GetIntelPtr(ptr);
      String strFileName = null;
      if (ptrFileName != 0) {
        unsafe {
          byte* ptrFile = (byte*)ptrFileName;
          int len = 0; while (ptrFile[len] != 0)  len += 2;
          char[] b = new char[len / 2];
          for (int i = 0, j = 0; i < len / 2; i++, j += 2)
            b[i] = (char)ptrFile[j];

          strFileName = new String(b);
        }
      }
      return strFileName;
    }

    static String MoveFileToKAN(String strOldName) {                                          // Move file to X:\KAN, then delete it
      strOldName = Path.GetFullPath(strOldName);                                              // In case relative path used ..
      if (strOldName.IndexOf(":\\") == -1) return strOldName;                                 // Error no Drive in Name ..
      String strTempFile = Path.GetFileName(strOldName);                                      // Original filename
      String strDriveName = strOldName.Substring(0, strOldName.IndexOf(":\\") + 2);           // Drive of filename
      String strKANName = Path.Combine(strDriveName, "KAN");                                  // X:\KAN
      if (!Directory.Exists(strKANName)) Directory.CreateDirectory(strKANName);               // If KAN-Directory doesn't exist, create it
      if (File.Exists(Path.Combine(strKANName, strTempFile))) {                               // File exists in KAN ?
        String strNewTempFile = Path.GetTempFileName();                                       // Get a new unique Name
        Function(oldDeleteFileW, strNewTempFile);                                             // Delete the tempfile
        strTempFile = Path.GetFileNameWithoutExtension(strNewTempFile) + "_" + strTempFile;   // New name should be TMPNAME_orgname.orgextension
      }
      String strNewName = Path.Combine(strKANName, strTempFile);                              // X:\KAN\filename
      if (!MoveFile(strOldName, strNewName)) {                                                // Move file to KAN
        Print("Move failed, old: '" + strOldName + "' new: '" + strNewName + "'");
        return strOldName;                                                                    // Couldn't move, return old name
      }
      Print("File moved from: '" + strOldName + "' to: '" + strNewName + "'");
      return strNewName;                                                                      // Return new name
    }

    // Redirect Kernel-functioncall strFunctionName to array, which redirects to NewFunctionDelegate
    static long HotPatch(Delegate NewFunctionDelegate, byte[] array, byte[] arg, String strFunctionName) {
      long OldFunction = getKernelFunction(strFunctionName);                                  // Function from kernel32.dll
      if (OldFunction == 0) return 0;
      long codePtr = (long)Marshal.UnsafeAddrOfPinnedArrayElement(array, 0);                  // Additional code to get argument from [ebp+8] or [esp+8]
      long argPtr = (long)Marshal.UnsafeAddrOfPinnedArrayElement(arg, 0);                     // Argument will be stored here
      long NewFunction = (long)Marshal.GetFunctionPointerForDelegate(NewFunctionDelegate);    // New Function call
      long lpNewOldFunctionPointer = OldFunction - 5;                                         // -5 because of the nop instructions

      if (debug) {                                                                            // Dump out hex-ptrs
        Print("Old" + strFunctionName, lpNewOldFunctionPointer);
        Print("New" + strFunctionName, NewFunction);
        Print("Code" + strFunctionName, codePtr);
        Print("Arg" + strFunctionName, argPtr);
      }

      uint dwOldProtV, dwNewProtV;
      VirtualProtect((IntPtr)argPtr, 4, (uint)Prot.PAGE_READWRITE, out dwOldProtV);           // Make argument writeable
      VirtualProtect((IntPtr)codePtr, (uint)array.Length, (uint)Prot.PAGE_EXECUTE_READWRITE, out dwOldProtV); // Make code writeable & executable
      VirtualProtect((IntPtr)lpNewOldFunctionPointer, 7, (uint)Prot.PAGE_EXECUTE_READWRITE, out dwOldProtV); // Make old function writeable
      bool bSrcMatches = CheckMatchSource(lpNewOldFunctionPointer);                           // Check if Source matches 5x nop + mov edi, edi

      if (bSrcMatches) {
        long lpCallAdr = StoreIntelPtr(argPtr, codePtr + 6);                                  // set adr for mov [4byte-adr], eax
        long lpNewCallDestination = NewFunction - lpCallAdr - 5;                              // Calculating the call destination (-5 because of the call size) - RELATIVE Ptr
        StoreIntelPtr(lpNewCallDestination, lpCallAdr + 1);                                   // Store 4byte-Ptr in reverse order

        long lpCodeCallDestination = codePtr - lpNewOldFunctionPointer - 5;                   // Calculating the call destination (-5 because of the call size) - RELATIVE Ptr
        long workmem = StoreAsmBytes(new byte[] { 0xE8 }, lpNewOldFunctionPointer);           // x86-asm CALL
        workmem = StoreIntelPtr(lpCodeCallDestination, workmem);                              // Store 4byte-Ptr in reverse order
        StoreAsmBytes(new byte[] { 0xEB, 0xF9 }, workmem);                                    // x86-asm JMP -5
        if (CheckMatchSource(lpNewOldFunctionPointer)) bSrcMatches = false;                   // After patching, should NOT Match now
      }

      VirtualProtect((IntPtr)lpNewOldFunctionPointer, 7, dwOldProtV, out dwNewProtV);         // Set back the old Protection
      if (!bSrcMatches) { Print("Can't patch: " + strFunctionName); return 0; }
      return OldFunction + 2;                                                                 // Reentry-Point
    }

    static long StoreAsmBytes(byte[] bytes, long mem) {                                       // Store Bytearray at mem
      memcpy((IntPtr)mem, Marshal.UnsafeAddrOfPinnedArrayElement(bytes, 0), bytes.Length);
      return mem + bytes.Length;
    }

    static long StoreIntelPtr(long ptr, long mem) {                                           // Store ptr as IntelPtr (reverse order) at mem
      byte[] array = new byte[4];
      SetIntelPtr(ptr, array);
      return StoreAsmBytes(array, mem);
    }

    static void SetIntelPtr(long ptr, byte[] array) {                                         // Store ptr as IntelPtr (reverse order) in array
      array[0] = (byte)ptr;
      array[1] = (byte)(ptr >> 8);
      array[2] = (byte)(ptr >> 16);
      array[3] = (byte)(ptr >> 24);
    }

    static long GetIntelPtr(byte[] array) {                                                   // Get ptr as InterPtr (reverse order) from array
      return (array[0] | array[1] << 8 | array[2] << 16 | array[3] << 24);
    }

    static byte[] GetAsmBytes(byte[] bytes, long mem) {                                       // get n-bytes from mem
      IntPtr memPtr = Marshal.UnsafeAddrOfPinnedArrayElement(bytes, 0);
      memcpy(memPtr, (IntPtr)mem, bytes.Length);
      return bytes;
    }

    static bool CheckMatchSource(long mem) {                                                  // Check if mem matches sourcearray (5x nop + mov edi, edi)
      byte[] srcarray = GetAsmBytes(new byte[sourcearray.Length], mem);
      bool bSrcMatches = true;
      for (int i = 0; i < srcarray.Length; i++) {
        if (srcarray[i] != sourcearray[i])
          bSrcMatches = false;
      }
      return bSrcMatches;
    }

    static void UnPatch(String strFunctionName) {                                             // Restore function to (5x nop + mov edi, edi)
      long OldFunction = getKernelFunction(strFunctionName);                                  // Function from kernel32.dll
      IntPtr lpNewOldFunctionPointer = (IntPtr)(OldFunction - 5);                             // -5 because of the nop instructions
      uint dwOldProtV, dwNewProtV;
      VirtualProtect(lpNewOldFunctionPointer, 7, (uint)Prot.PAGE_EXECUTE_READWRITE, out dwOldProtV);  // Make writeable
      StoreAsmBytes(sourcearray, (long)lpNewOldFunctionPointer);                              // Write 5x nop + mov edi, edi
      VirtualProtect(lpNewOldFunctionPointer, 7, dwOldProtV, out dwNewProtV);                 // Set back the old Protection
    }

    static long getKernelFunction(String strFunctionName) {                                   // get functionptr of function in kernel
      return (long)GetProcAddress(LoadLibrary("kernel32.dll"), strFunctionName);
    }

    static void Print(String txt) {                                                           // Write "txt" to console
      Console.Out.WriteLine(txt);
    }

    static void Print(String txt, long ptr) {                                                 // Write "txt: ptrAShex" to console
      Print(txt + ": " + ptr.ToString("X"));
    }
  }
}

P.S: Wieso sind TABS hier so riesig. :o

Edit:
P.P.S: Wieso hat CODE-Tag kein Syntaxhighlightning? Hab stattdessen PHP genommen ..
 
Zuletzt bearbeitet:
Darf ich mal fragen, warum du nicht die .NET-Klassen/Methoden dafür nimmst?

Edit: Ok, hat lange gedauert überhaupt zu kapieren, was du machen willst... damit wird meine Frage wohl auch sinnlos...

Gibts dafür keine Möglichkeiten irgendwelche Hooks zu machen?
 
Zuletzt bearbeitet:
Zuletzt bearbeitet:
Ich bezog mich auf normale Datei-Operationen... aber glaube nicht, dass die dir helfen :-(
 
Zurück
Oben