Wie gehe ich mit Dateien um, deren Name länger als 259 Zeichen ist?


80

Ich arbeite an einer Anwendung, die jede Datei in einigen Verzeichnissen durchläuft und mit diesen Dateien einige Aktionen ausführt. Unter anderem muss ich die Dateigröße und das Datum abrufen, an dem diese Datei geändert wurde.

Da einige vollständige Dateinamen (Verzeichnis + Dateiname) zu lang sind, konnte ich .NET Framework nicht verwenden FileInfo, das auf MAX_PATH(260 Zeichen) beschränkt ist. In vielen Webquellen wurde empfohlen, native Win32-Funktionen über P / Invoke zu verwenden, um auf Dateien zuzugreifen, deren Namen zu lang sind.

Derzeit scheint genau das gleiche Problem bei Win32-Funktionen aufzutreten. Beispiel: GetFileAttributesEx(kernel32.dll) schlägt mit Win32-Fehler 3 ERROR_PATH_NOT_FOUND für den Pfad von 270 Byte fehl.

Dieselbe Datei kann erfolgreich über Notepad2 geöffnet und mit Windows Explorer erfolgreich angezeigt werden (Visual Studio 2010 kann sie jedoch beispielsweise aufgrund der Beschränkung auf 259 Zeichen nicht öffnen¹).

Was kann ich tun, um auf eine Datei zugreifen zu können, wenn der Dateipfad 270 Zeichen lang ist?

Anmerkungen:

  • Das Entfernen oder Ignorieren von Dateien mit einer Dateipfadlänge von mehr als 259 Zeichen ist keine Lösung.

  • Ich suche nur nach Unicode-kompatiblen Lösungen.

  • Die Anwendung wird unter Windows 2008 / Vista oder höher mit installiertem .NET Framework 4 ausgeführt.


¹ Überraschenderweise schlägt Microsoft Word 2007 fehl und beschwert sich, dass "die Diskette zu klein ist" auf einem Computer, der kein Diskettenlaufwerk hat, oder dass "RAM-Speicher niedrig ist", wenn noch 4 GB RAM vorhanden sind, oder schließlich "Antivirensoftware [...] muss aktualisiert werden". Werden sie eines Tages aufhören, solche dumm bedeutungslosen Fehler anzuzeigen, zumindest in Schlüsselprodukten wie Microsoft Office?


1
Ich glaube, auch heutzutage, dass jeder Dateiname einem Dateinamen im 8.3-Format zugeordnet ist. Können Sie das nicht verwenden? en.wikipedia.org/wiki/…
Grant Thomas

6
Selbst ein Dateiname im Format 8,3 kann 260 Zeichen überschreiten. Sie benötigen lediglich eine tiefe Ordnerverschachtelung.
David Heffernan

1
Beachten Sie, dass Sie die Erstellung von 8.3-Namen deaktivieren können (und dies möglicherweise möchten, da dadurch der E / A-Overhead erhöht wird). Nein, Sie können also nicht sicher sein, dass 8.3 vorhanden ist. Siehe fsutil.exe 8dot3name.
Bacon Bits

Antworten:


78

.NET 4.6.2 Lösung

Verwenden Sie die hier\\?\C:\Verrrrrrrrrrrry long path beschriebene Syntax .

.NET-Kernlösung

Es funktioniert nur, weil das Framework die Syntax für lange Pfade für Sie hinzufügt.

Pre .NET 4.6.2 Lösung

Verwenden Sie auch die Syntax für lange Pfade und die Unicode-Version der Win32-API-Funktion mit P / Invoke. Aus Dateien, Pfaden und Namespaces benennen :

Die Windows-API verfügt über viele Funktionen, die auch über Unicode-Versionen verfügen, um einen Pfad mit erweiterter Länge für eine maximale Gesamtpfadlänge von 32.767 Zeichen zu ermöglichen. Diese Art von Pfad besteht aus Komponenten, die durch Backslashes getrennt sind, jeweils bis zu dem Wert, der im Parameter lpMaximumComponentLength der Funktion GetVolumeInformation zurückgegeben wird (dieser Wert beträgt normalerweise 255 Zeichen). Verwenden Sie das \\?\Präfix, um einen Pfad mit erweiterter Länge anzugeben . Zum Beispiel \\?\D:\very long path.

Das Lesen dieser Microsoft Support-Seite könnte ebenfalls interessant sein.

Eine sehr ausführliche Erklärung in Long Paths in .NET von Kim Hamilton im BCL Team-Blog listet einige Probleme bei der Behandlung dieser Pfade auf, die seiner Ansicht nach der Grund dafür sind, dass diese Syntax in .NET immer noch nicht direkt unterstützt wird:

Es gibt mehrere Gründe, warum wir in der Vergangenheit nur ungern lange Wege hinzugefügt haben und warum wir immer noch vorsichtig damit sind <...>.

<...> das \\?\Präfix ermöglicht nicht nur lange Pfade; Dadurch wird der Pfad mit minimalen Änderungen durch die Windows-APIs an das Dateisystem übergeben. Eine Konsequenz ist, dass \\?\die von Windows-APIs durchgeführte Normalisierung von Dateinamen deaktiviert wird, einschließlich des Entfernens von nachgestellten Leerzeichen und des Erweiterns von '.' und '..', Konvertieren relativer Pfade in vollständige Pfade usw. <...>

<...> Lange Pfade mit dem \\?\Präfix können in den meisten dateibezogenen Windows-APIs verwendet werden, jedoch nicht in allen Windows-APIs. Beispielsweise schlägt LoadLibrary <...> fehl, wenn der Dateiname länger als MAX_PATH ist. <...> In allen Windows-APIs gibt es ähnliche Beispiele. Es gibt einige Problemumgehungen, die jedoch von Fall zu Fall erfolgen.

Ein weiterer Faktor <...> ist die Kompatibilität mit anderen Windows-basierten Anwendungen und der Windows-Shell selbst <...>

Da dieses Problem immer häufiger auftritt, bemüht sich Microsoft, es zu beheben. Tatsächlich werden Sie als zeitgemäßer Vista-Plug einige Änderungen bemerken, die die Wahrscheinlichkeit verringern, dass das MAX_PATH-Limit erreicht wird: Viele der speziellen Ordnernamen wurden verkürzt, und interessanterweise verwendet die Shell eine Funktion zum automatischen Verkleinern von Pfaden <...> um zu versuchen, sie in 260 Zeichen zusammenzufassen.


Warnung: Möglicherweise müssen Sie die Windows-APIs direkt aufrufen, da .NET Framework diese Art der Pfadsyntax möglicherweise nicht unterstützt.


Ja, 3.5 hat diesen Weg nicht unterstützt. Ich bezweifle, dass 4.0 es hinzugefügt hat.
Jamie Penney

3
Ja, Sie werden die Win32 - API - Funktionen P / Invoke müssen und rufen sie direkt von einer .NET - Anwendung. Die interne Installation von .NET (insbesondere eine PathHelperKlasse) überprüft den Pfad und löst eine Ausnahme aus, wenn sie mehr als MAX_PATH(260) Zeichen umfasst.
Cody Gray

1
Was ist, wenn Ihr Pfad länger als 32.767 Zeichen ist?
Amani Kilumanga

12
@AmaniKilumanga: Dann ist Ihr Weg im Grunde ein Aufsatz mit 6000 Wörtern und das System kann damit nicht umgehen.
user541686

1
@denahiro: Vielleicht können Sie sich die Freiheit nehmen, es zu bearbeiten ... wie auch die vorherige Person, die es dort abgelegt hat (ich habe es nicht getan) ...
user541686

27

Ich habe meine eigenen LongFileund LongDirectoryKlassen , um dieses Problem zu lösen. Ich benutze es, wann immer ich es normalerweise benutzen würde System.IO.File.

Es könnte Optimierungen usw. geben, aber es funktioniert seit Jahren gut.

public static class LongFile
{
    private const int MAX_PATH = 260;

    public static bool Exists(string path)
    {
        if (path.Length < MAX_PATH) return System.IO.File.Exists(path);
        var attr = NativeMethods.GetFileAttributesW(GetWin32LongPath(path));
        return (attr != NativeMethods.INVALID_FILE_ATTRIBUTES && ((attr & NativeMethods.FILE_ATTRIBUTE_ARCHIVE) == NativeMethods.FILE_ATTRIBUTE_ARCHIVE));
    }

    public static void Delete(string path)
    {
        if (path.Length < MAX_PATH) System.IO.File.Delete(path);
        else
        {
            bool ok = NativeMethods.DeleteFileW(GetWin32LongPath(path));
            if (!ok) ThrowWin32Exception();
        }
    }

    public static void AppendAllText(string path, string contents)
    {
        AppendAllText(path, contents, Encoding.Default);
    }

    public static void AppendAllText(string path, string contents, Encoding encoding)
    {
        if (path.Length < MAX_PATH)
        {
            System.IO.File.AppendAllText(path, contents, encoding);
        }
        else
        {
            var fileHandle = CreateFileForAppend(GetWin32LongPath(path));
            using (var fs = new System.IO.FileStream(fileHandle, System.IO.FileAccess.Write))
            {
                var bytes = encoding.GetBytes(contents);
                fs.Position = fs.Length;
                fs.Write(bytes, 0, bytes.Length);
            }
        }
    }

    public static void WriteAllText(string path, string contents)
    {
        WriteAllText(path, contents, Encoding.Default);
    }

    public static void WriteAllText(string path, string contents, Encoding encoding)
    {
        if (path.Length < MAX_PATH)
        {
            System.IO.File.WriteAllText(path, contents, encoding);
        }
        else
        {
            var fileHandle = CreateFileForWrite(GetWin32LongPath(path));

            using (var fs = new System.IO.FileStream(fileHandle, System.IO.FileAccess.Write))
            {
                var bytes = encoding.GetBytes(contents);
                fs.Write(bytes, 0, bytes.Length);
            }
        }
    }

    public static void WriteAllBytes(string path, byte[] bytes)
    {
        if (path.Length < MAX_PATH)
        {
            System.IO.File.WriteAllBytes(path, bytes);
        }
        else
        {
            var fileHandle = CreateFileForWrite(GetWin32LongPath(path));

            using (var fs = new System.IO.FileStream(fileHandle, System.IO.FileAccess.Write))
            {
                fs.Write(bytes, 0, bytes.Length);
            }
        }
    }

    public static void Copy(string sourceFileName, string destFileName)
    {
        Copy(sourceFileName, destFileName, false);
    }

    public static void Copy(string sourceFileName, string destFileName, bool overwrite)
    {
        if (sourceFileName.Length < MAX_PATH && (destFileName.Length < MAX_PATH)) System.IO.File.Copy(sourceFileName, destFileName, overwrite);
        else
        {
            var ok = NativeMethods.CopyFileW(GetWin32LongPath(sourceFileName), GetWin32LongPath(destFileName), !overwrite);
            if (!ok) ThrowWin32Exception();
        }
    }

    public static void Move(string sourceFileName, string destFileName)
    {
        if (sourceFileName.Length < MAX_PATH && (destFileName.Length < MAX_PATH)) System.IO.File.Move(sourceFileName, destFileName);
        else
        {
            var ok = NativeMethods.MoveFileW(GetWin32LongPath(sourceFileName), GetWin32LongPath(destFileName));
            if (!ok) ThrowWin32Exception();
        }
    }

    public static string ReadAllText(string path)
    {
        return ReadAllText(path, Encoding.Default);
    }

    public static string ReadAllText(string path, Encoding encoding)
    {
        if (path.Length < MAX_PATH) { return System.IO.File.ReadAllText(path, encoding); }
        var fileHandle = GetFileHandle(GetWin32LongPath(path));

        using (var fs = new System.IO.FileStream(fileHandle, System.IO.FileAccess.Read))
        {
            var data = new byte[fs.Length];
            fs.Read(data, 0, data.Length);
            return encoding.GetString(data);
        }
    }

    public static string[] ReadAllLines(string path)
    {
        return ReadAllLines(path, Encoding.Default);
    }

    public static string[] ReadAllLines(string path, Encoding encoding)
    {
        if (path.Length < MAX_PATH) { return System.IO.File.ReadAllLines(path, encoding); }
        var fileHandle = GetFileHandle(GetWin32LongPath(path));

        using (var fs = new System.IO.FileStream(fileHandle, System.IO.FileAccess.Read))
        {
            var data = new byte[fs.Length];
            fs.Read(data, 0, data.Length);
            var str = encoding.GetString(data);
            if (str.Contains("\r")) return str.Split(new[] { "\r\n" }, StringSplitOptions.None);
            return str.Split('\n');
        }
    }
    public static byte[] ReadAllBytes(string path)
    {
        if (path.Length < MAX_PATH) return System.IO.File.ReadAllBytes(path);
        var fileHandle = GetFileHandle(GetWin32LongPath(path));

        using (var fs = new System.IO.FileStream(fileHandle, System.IO.FileAccess.Read))
        {
            var data = new byte[fs.Length];
            fs.Read(data, 0, data.Length);
            return data;
        }
    }


    public static void SetAttributes(string path, FileAttributes attributes)
    {
        if (path.Length < MAX_PATH)
        {
            System.IO.File.SetAttributes(path, attributes);
        }
        else
        {
            var longFilename = GetWin32LongPath(path);
            NativeMethods.SetFileAttributesW(longFilename, (int)attributes);
        }
    }

    #region Helper methods

    private static SafeFileHandle CreateFileForWrite(string filename)
    {
        if (filename.Length >= MAX_PATH) filename = GetWin32LongPath(filename);
        SafeFileHandle hfile = NativeMethods.CreateFile(filename, (int)NativeMethods.FILE_GENERIC_WRITE, NativeMethods.FILE_SHARE_NONE, IntPtr.Zero, NativeMethods.CREATE_ALWAYS, 0, IntPtr.Zero);
        if (hfile.IsInvalid) ThrowWin32Exception();
        return hfile;
    }

    private static SafeFileHandle CreateFileForAppend(string filename)
    {
        if (filename.Length >= MAX_PATH) filename = GetWin32LongPath(filename);
        SafeFileHandle hfile = NativeMethods.CreateFile(filename, (int)NativeMethods.FILE_GENERIC_WRITE, NativeMethods.FILE_SHARE_NONE, IntPtr.Zero, NativeMethods.CREATE_NEW, 0, IntPtr.Zero);
        if (hfile.IsInvalid)
        {
            hfile = NativeMethods.CreateFile(filename, (int)NativeMethods.FILE_GENERIC_WRITE, NativeMethods.FILE_SHARE_NONE, IntPtr.Zero, NativeMethods.OPEN_EXISTING, 0, IntPtr.Zero);
            if (hfile.IsInvalid) ThrowWin32Exception();
        }
        return hfile;
    }

    internal static SafeFileHandle GetFileHandle(string filename)
    {
        if (filename.Length >= MAX_PATH) filename = GetWin32LongPath(filename);
        SafeFileHandle hfile = NativeMethods.CreateFile(filename, (int)NativeMethods.FILE_GENERIC_READ, NativeMethods.FILE_SHARE_READ, IntPtr.Zero, NativeMethods.OPEN_EXISTING, 0, IntPtr.Zero);
        if (hfile.IsInvalid) ThrowWin32Exception();
        return hfile;
    }

    internal static SafeFileHandle GetFileHandleWithWrite(string filename)
    {
        if (filename.Length >= MAX_PATH) filename = GetWin32LongPath(filename);
        SafeFileHandle hfile = NativeMethods.CreateFile(filename, (int)(NativeMethods.FILE_GENERIC_READ | NativeMethods.FILE_GENERIC_WRITE | NativeMethods.FILE_WRITE_ATTRIBUTES), NativeMethods.FILE_SHARE_NONE, IntPtr.Zero, NativeMethods.OPEN_EXISTING, 0, IntPtr.Zero);
        if (hfile.IsInvalid) ThrowWin32Exception();
        return hfile;
    }

    public static System.IO.FileStream GetFileStream(string filename, FileAccess access = FileAccess.Read)
    {
        var longFilename = GetWin32LongPath(filename);
        SafeFileHandle hfile;
        if (access == FileAccess.Write)
        {
            hfile = NativeMethods.CreateFile(longFilename, (int)(NativeMethods.FILE_GENERIC_READ | NativeMethods.FILE_GENERIC_WRITE | NativeMethods.FILE_WRITE_ATTRIBUTES), NativeMethods.FILE_SHARE_NONE, IntPtr.Zero, NativeMethods.OPEN_EXISTING, 0, IntPtr.Zero);
        }
        else
        {
            hfile = NativeMethods.CreateFile(longFilename, (int)NativeMethods.FILE_GENERIC_READ, NativeMethods.FILE_SHARE_READ, IntPtr.Zero, NativeMethods.OPEN_EXISTING, 0, IntPtr.Zero);
        }

        if (hfile.IsInvalid) ThrowWin32Exception();

        return new System.IO.FileStream(hfile, access);
    }


    [DebuggerStepThrough]
    public static void ThrowWin32Exception()
    {
        int code = Marshal.GetLastWin32Error();
        if (code != 0)
        {
            throw new System.ComponentModel.Win32Exception(code);
        }
    }

    public static string GetWin32LongPath(string path)
    {
        if (path.StartsWith(@"\\?\")) return path;

        if (path.StartsWith("\\"))
        {
            path = @"\\?\UNC\" + path.Substring(2);
        }
        else if (path.Contains(":"))
        {
            path = @"\\?\" + path;
        }
        else
        {
            var currdir = Environment.CurrentDirectory;
            path = Combine(currdir, path);
            while (path.Contains("\\.\\")) path = path.Replace("\\.\\", "\\");
            path = @"\\?\" + path;
        }
        return path.TrimEnd('.'); ;
    }

    private static string Combine(string path1, string path2)
    {
        return path1.TrimEnd('\\') + "\\" + path2.TrimStart('\\').TrimEnd('.'); ;
    }


    #endregion

    public static void SetCreationTime(string path, DateTime creationTime)
    {
        long cTime = 0;
        long aTime = 0;
        long wTime = 0;

        using (var handle = GetFileHandleWithWrite(path))
        {
            NativeMethods.GetFileTime(handle, ref cTime, ref aTime, ref wTime);
            var fileTime = creationTime.ToFileTimeUtc();
            if (!NativeMethods.SetFileTime(handle, ref fileTime, ref aTime, ref wTime))
            {
                throw new Win32Exception();
            }
        }
    }

    public static void SetLastAccessTime(string path, DateTime lastAccessTime)
    {
        long cTime = 0;
        long aTime = 0;
        long wTime = 0;

        using (var handle = GetFileHandleWithWrite(path))
        {
            NativeMethods.GetFileTime(handle, ref cTime, ref aTime, ref wTime);

            var fileTime = lastAccessTime.ToFileTimeUtc();
            if (!NativeMethods.SetFileTime(handle, ref cTime, ref fileTime, ref wTime))
            {
                throw new Win32Exception();
            }
        }
    }

    public static void SetLastWriteTime(string path, DateTime lastWriteTime)
    {
        long cTime = 0;
        long aTime = 0;
        long wTime = 0;

        using (var handle = GetFileHandleWithWrite(path))
        {
            NativeMethods.GetFileTime(handle, ref cTime, ref aTime, ref wTime);

            var fileTime = lastWriteTime.ToFileTimeUtc();
            if (!NativeMethods.SetFileTime(handle, ref cTime, ref aTime, ref fileTime))
            {
                throw new Win32Exception();
            }
        }
    }

    public static DateTime GetLastWriteTime(string path)
    {
        long cTime = 0;
        long aTime = 0;
        long wTime = 0;

        using (var handle = GetFileHandleWithWrite(path))
        {
            NativeMethods.GetFileTime(handle, ref cTime, ref aTime, ref wTime);

            return DateTime.FromFileTimeUtc(wTime);
        }
    }

}

Und eine entsprechende LongDirectory:

public class LongDirectory
{
    private const int MAX_PATH = 260;

    public static void CreateDirectory(string path)
    {
        if (string.IsNullOrWhiteSpace(path)) return;
        if (path.Length < MAX_PATH)
        {
            System.IO.Directory.CreateDirectory(path);
        }
        else
        {
            var paths = GetAllPathsFromPath(GetWin32LongPath(path));
            foreach (var item in paths)
            {
                if (!LongExists(item))
                {
                    var ok = NativeMethods.CreateDirectory(item, IntPtr.Zero);
                    if (!ok)
                    {
                        ThrowWin32Exception();
                    }
                }
            }
        }
    }

    public static void Delete(string path)
    {
        Delete(path, false);
    }

    public static void Delete(string path, bool recursive)
    {
        if (path.Length < MAX_PATH && !recursive)
        {
            System.IO.Directory.Delete(path, false);
        }
        else
        {
            if (!recursive)
            {
                bool ok = NativeMethods.RemoveDirectory(GetWin32LongPath(path));
                if (!ok) ThrowWin32Exception();
            }
            else
            {
                DeleteDirectories(new string[] { GetWin32LongPath(path) });
            }
        }
    }


    private static void DeleteDirectories(string[] directories)
    {
        foreach (string directory in directories)
        {
            string[] files = LongDirectory.GetFiles(directory, null, System.IO.SearchOption.TopDirectoryOnly);
            foreach (string file in files)
            {
                LongFile.Delete(file);
            }
            directories = LongDirectory.GetDirectories(directory, null, System.IO.SearchOption.TopDirectoryOnly);
            DeleteDirectories(directories);
            bool ok = NativeMethods.RemoveDirectory(GetWin32LongPath(directory));
            if (!ok) ThrowWin32Exception();
        }
    }

    public static bool Exists(string path)
    {
        if (path.Length < MAX_PATH) return System.IO.Directory.Exists(path);
        return LongExists(GetWin32LongPath(path));
    }

    private static bool LongExists(string path)
    {
        var attr = NativeMethods.GetFileAttributesW(path);
        return (attr != NativeMethods.INVALID_FILE_ATTRIBUTES && ((attr & NativeMethods.FILE_ATTRIBUTE_DIRECTORY) == NativeMethods.FILE_ATTRIBUTE_DIRECTORY));
    }


    public static string[] GetDirectories(string path)
    {
        return GetDirectories(path, null, SearchOption.TopDirectoryOnly);
    }

    public static string[] GetDirectories(string path, string searchPattern)
    {
        return GetDirectories(path, searchPattern, SearchOption.TopDirectoryOnly);
    }

    public static string[] GetDirectories(string path, string searchPattern, System.IO.SearchOption searchOption)
    {
        searchPattern = searchPattern ?? "*";
        var dirs = new List<string>();
        InternalGetDirectories(path, searchPattern, searchOption, ref dirs);
        return dirs.ToArray();
    }

    private static void InternalGetDirectories(string path, string searchPattern, System.IO.SearchOption searchOption, ref List<string> dirs)
    {
        NativeMethods.WIN32_FIND_DATA findData;
        IntPtr findHandle = NativeMethods.FindFirstFile(System.IO.Path.Combine(GetWin32LongPath(path), searchPattern), out findData);

        try
        {
            if (findHandle != new IntPtr(-1))
            {

                do
                {
                    if ((findData.dwFileAttributes & System.IO.FileAttributes.Directory) != 0)
                    {
                        if (findData.cFileName != "." && findData.cFileName != "..")
                        {
                            string subdirectory = System.IO.Path.Combine(path, findData.cFileName);
                            dirs.Add(GetCleanPath(subdirectory));
                            if (searchOption == SearchOption.AllDirectories)
                            {
                                InternalGetDirectories(subdirectory, searchPattern, searchOption, ref dirs);
                            }
                        }
                    }
                } while (NativeMethods.FindNextFile(findHandle, out findData));
                NativeMethods.FindClose(findHandle);
            }
            else
            {
                ThrowWin32Exception();
            }
        }
        catch (Exception)
        {
            NativeMethods.FindClose(findHandle);
            throw;
        }
    }

    public static string[] GetFiles(string path)
    {
        return GetFiles(path, null, SearchOption.TopDirectoryOnly);
    }

    public static string[] GetFiles(string path, string searchPattern)
    {
        return GetFiles(path, searchPattern, SearchOption.TopDirectoryOnly);
    }


    public static string[] GetFiles(string path, string searchPattern, System.IO.SearchOption searchOption)
    {
        searchPattern = searchPattern ?? "*";

        var files = new List<string>();
        var dirs = new List<string> { path };

        if (searchOption == SearchOption.AllDirectories)
        {
            //Add all the subpaths
            dirs.AddRange(LongDirectory.GetDirectories(path, null, SearchOption.AllDirectories));
        }

        foreach (var dir in dirs)
        {
            NativeMethods.WIN32_FIND_DATA findData;
            IntPtr findHandle = NativeMethods.FindFirstFile(System.IO.Path.Combine(GetWin32LongPath(dir), searchPattern), out findData);

            try
            {
                if (findHandle != new IntPtr(-1))
                {

                    do
                    {
                        if ((findData.dwFileAttributes & System.IO.FileAttributes.Directory) == 0)
                        {
                            string filename = System.IO.Path.Combine(dir, findData.cFileName);
                            files.Add(GetCleanPath(filename));
                        }
                    } while (NativeMethods.FindNextFile(findHandle, out findData));
                    NativeMethods.FindClose(findHandle);
                }
            }
            catch (Exception)
            {
                NativeMethods.FindClose(findHandle);
                throw;
            }
        }

        return files.ToArray();
    }



    public static void Move(string sourceDirName, string destDirName)
    {
        if (sourceDirName.Length < MAX_PATH || destDirName.Length < MAX_PATH)
        {
            System.IO.Directory.Move(sourceDirName, destDirName);
        }
        else
        {
            var ok = NativeMethods.MoveFileW(GetWin32LongPath(sourceDirName), GetWin32LongPath(destDirName));
            if (!ok) ThrowWin32Exception();
        }
    }

    #region Helper methods



    [DebuggerStepThrough]
    public static void ThrowWin32Exception()
    {
        int code = Marshal.GetLastWin32Error();
        if (code != 0)
        {
            throw new System.ComponentModel.Win32Exception(code);
        }
    }

    public static string GetWin32LongPath(string path)
    {

        if (path.StartsWith(@"\\?\")) return path;

        var newpath = path;
        if (newpath.StartsWith("\\"))
        {
            newpath = @"\\?\UNC\" + newpath.Substring(2);
        }
        else if (newpath.Contains(":"))
        {
            newpath = @"\\?\" + newpath;
        }
        else
        {
            var currdir = Environment.CurrentDirectory;
            newpath = Combine(currdir, newpath);
            while (newpath.Contains("\\.\\")) newpath = newpath.Replace("\\.\\", "\\");
            newpath = @"\\?\" + newpath;
        }
        return newpath.TrimEnd('.');
    }

    private static string GetCleanPath(string path)
    {
        if (path.StartsWith(@"\\?\UNC\")) return @"\\" + path.Substring(8);
        if (path.StartsWith(@"\\?\")) return path.Substring(4);
        return path;
    }

    private static List<string> GetAllPathsFromPath(string path)
    {
        bool unc = false;
        var prefix = @"\\?\";
        if (path.StartsWith(prefix + @"UNC\"))
        {
            prefix += @"UNC\";
            unc = true;
        }
        var split = path.Split('\\');
        int i = unc ? 6 : 4;
        var list = new List<string>();
        var txt = "";

        for (int a = 0; a < i; a++)
        {
            if (a > 0) txt += "\\";
            txt += split[a];
        }
        for (; i < split.Length; i++)
        {
            txt = Combine(txt, split[i]);
            list.Add(txt);
        }

        return list;
    }

    private static string Combine(string path1, string path2)
    {
        return path1.TrimEnd('\\') + "\\" + path2.TrimStart('\\').TrimEnd('.');
    }


    #endregion
}

NativeMethods::

internal static class NativeMethods
{
    internal const int FILE_ATTRIBUTE_ARCHIVE = 0x20;
    internal const int INVALID_FILE_ATTRIBUTES = -1;

    internal const int FILE_READ_DATA = 0x0001;
    internal const int FILE_WRITE_DATA = 0x0002;
    internal const int FILE_APPEND_DATA = 0x0004;
    internal const int FILE_READ_EA = 0x0008;
    internal const int FILE_WRITE_EA = 0x0010;

    internal const int FILE_READ_ATTRIBUTES = 0x0080;
    internal const int FILE_WRITE_ATTRIBUTES = 0x0100;

    internal const int FILE_SHARE_NONE = 0x00000000;
    internal const int FILE_SHARE_READ = 0x00000001;

    internal const int FILE_ATTRIBUTE_DIRECTORY = 0x10;

    internal const long FILE_GENERIC_WRITE = STANDARD_RIGHTS_WRITE |
                                                FILE_WRITE_DATA |
                                                FILE_WRITE_ATTRIBUTES |
                                                FILE_WRITE_EA |
                                                FILE_APPEND_DATA |
                                                SYNCHRONIZE;

    internal const long FILE_GENERIC_READ = STANDARD_RIGHTS_READ |
                                            FILE_READ_DATA |
                                            FILE_READ_ATTRIBUTES |
                                            FILE_READ_EA |
                                            SYNCHRONIZE;



    internal const long READ_CONTROL = 0x00020000L;
    internal const long STANDARD_RIGHTS_READ = READ_CONTROL;
    internal const long STANDARD_RIGHTS_WRITE = READ_CONTROL;

    internal const long SYNCHRONIZE = 0x00100000L;

    internal const int CREATE_NEW = 1;
    internal const int CREATE_ALWAYS = 2;
    internal const int OPEN_EXISTING = 3;

    internal const int MAX_PATH = 260;
    internal const int MAX_ALTERNATE = 14;

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    internal struct WIN32_FIND_DATA
    {
        public System.IO.FileAttributes dwFileAttributes;
        public FILETIME ftCreationTime;
        public FILETIME ftLastAccessTime;
        public FILETIME ftLastWriteTime;
        public uint nFileSizeHigh; //changed all to uint, otherwise you run into unexpected overflow
        public uint nFileSizeLow;  //|
        public uint dwReserved0;   //|
        public uint dwReserved1;   //v
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)]
        public string cFileName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_ALTERNATE)]
        public string cAlternate;
    }


    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode, IntPtr lpSecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile);


    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern bool CopyFileW(string lpExistingFileName, string lpNewFileName, bool bFailIfExists);


    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern int GetFileAttributesW(string lpFileName);


    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern bool DeleteFileW(string lpFileName);


    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern bool MoveFileW(string lpExistingFileName, string lpNewFileName);


    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern bool SetFileTime(SafeFileHandle hFile, ref long lpCreationTime, ref long lpLastAccessTime, ref long lpLastWriteTime);


    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern bool GetFileTime(SafeFileHandle hFile, ref long lpCreationTime, ref long lpLastAccessTime, ref long lpLastWriteTime);


    [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern IntPtr FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData);


    [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern bool FindNextFile(IntPtr hFindFile, out WIN32_FIND_DATA lpFindFileData);


    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern bool FindClose(IntPtr hFindFile);


    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern bool RemoveDirectory(string path);


    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern bool CreateDirectory(string lpPathName, IntPtr lpSecurityAttributes);


    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern int SetFileAttributesW(string lpFileName, int fileAttributes);
}

StackOverflow ist kein guter Ort, um den Quellcode von Bibliotheken gemeinsam zu nutzen. Wenn Sie möchten, dass es tatsächlich von anderen Entwicklern verwendet wird, sollten Sie (1) es auf GitHub oder einem ähnlichen Dienst veröffentlichen, (2) Komponententests einschließen und (3) es als NuGet-Paket veröffentlichen. Optional sollten Sie erwägen, Dokumentation hinzuzufügen, wenn Sie andere Personen dazu ermutigen möchten, einen Beitrag zu Ihrer Bibliothek zu leisten. Sie können diese Antwort dann bearbeiten, um zu erklären, was Sie getan haben und wie diese Bibliothek die ursprüngliche Frage beantwortet (weil dies der Fall ist!), Und um die entsprechenden Links zu GitHub und NuGet aufzunehmen.
Arseni Mourzenko

13
Dies sind nur einige Klassen aus einem größeren Projekt, das ich in meinem persönlichen TFS-Repository (visualstudio.com) habe. Ich dachte, ich teile es, da ich alle häufig Softwareprobleme mit fehlender Unterstützung für lange Pfade sehe (sogar TFS 2013 schlägt fehl, wenn Sie länger als 259 sind ...). Aber ja. Könnte in Zukunft so etwas tun (zum Beispiel, wenn dieser Beitrag viele Stimmen bekommt :)).
Wolf5

Diese Lösung funktioniert für mich, aber in der Funktion InternalGetDirectories funktioniert die Rekursion nicht, wenn die Option AllDirectories ausgewählt ist und das Suchmuster nicht in der Liste der Unterverzeichnisse gefunden wird. Ich musste die Zeile ThrowWin32Exception () ersetzen ; durch eine Art Vorsuche mit "*" als Muster (Code zu lang, um hier aufgenommen zu werden, aber sehr ähnlich, um Code in der Funktion zu tun ).
Alex

Beim Versuch, ZetaLongPaths Nuget / Github-Projekt durchzuführen, wurden beim Kopieren von Dateien aus irgendeinem Grund beschädigte Dateien erstellt. Versuchen Sie dies jetzt. Ich denke, es werden auch diese Pinvoke-Muster verwendet, aber man weiß es nie!
Furz

Vielen Dank für diesen Sir, aber nur zu Ihrer Information, wenn der Zielpfad relativ ist (".. \ Fu \ Bar.txt"), funktioniert er nicht. Ich habe es auf meiner Seite repariert, indem ich es als absoluten Pfad erzwungen habe.
Tipx

22

Sie können die Delimon-Bibliothek ausprobieren, eine auf .NET Framework 4 basierende Bibliothek auf Microsoft TechNet, um das Problem mit langen Dateinamen zu überwinden:

Delimon.Win32.I O Library (V4.0).

Es verfügt über eigene Versionen der wichtigsten Methoden von System.IO. Zum Beispiel würden Sie ersetzen:

System.IO.Directory.GetFiles

mit

Delimon.Win32.IO.Directory.GetFiles

Damit können Sie lange Dateien und Ordner verarbeiten.

Von der Website:

Delimon.Win32.IO ersetzt grundlegende Dateifunktionen von System.IO und unterstützt Datei- und Ordnernamen mit bis zu 32.767 Zeichen.

Diese Bibliothek ist in .NET Framework 4.0 geschrieben und kann entweder auf x86- und x64-Systemen verwendet werden. Die Datei- und Ordnerbeschränkungen des Standard-System.IO-Namespace können mit Dateien arbeiten, die 260 Zeichen in einem Dateinamen und 240 Zeichen in einem Ordnernamen enthalten (MAX_PATH ist normalerweise als 260 Zeichen konfiguriert). In der Regel tritt der System.IO.PathTooLongException- Fehler mit der Standard .NET-Bibliothek auf.


4
Es gibt auch eine AlphaFS- Bibliothek für Pfade mit mehr als 260 Zeichen.
Mark G


5

Ich bin einmal auf dieses Problem mit einer Anwendung gestoßen, die ich geschrieben habe. Als ich mich der Beschränkung auf 260 Zeichen näherte, ordnete ich ein Netzwerklaufwerk im laufenden Betrieb einem bestimmten Segment des vollständigen Pfads zu, wodurch die Länge des vollständigen Pfads und des Dateinamens erheblich verkürzt wurde. Es ist nicht wirklich eine elegante Lösung, aber es hat die Arbeit erledigt.



2

Die MSDN-Referenz für GetFileAttributesEx lautet:

In der ANSI-Version dieser Funktion ist der Name auf MAX_PATH-Zeichen beschränkt. Um diese Begrenzung auf 32.767 Zeichen zu erweitern, rufen Sie die Unicode-Version der Funktion auf und stellen Sie dem Pfad "\\? \" Vor. Weitere Informationen finden Sie unter Benennen einer Datei .

Sie möchten also GetFileAttributesExW verwenden und Ihrem Pfad "\\? \" Voranstellen.


Ihr Zitat ist korrekt, aber etwas irreführend: Diese Einschränkung hat nichts mit der ANSI-Version zu tun (sie ist auch in der Unicode-Version eingeschränkt).
user541686

1
Es wird sehr deutlich angegeben, dass Sie sowohl die Unicode-Version als auch das Präfix verwenden müssen, um das Limit zu erweitern.
Lunixbochs

1

Bitte aktualisieren Sie Ihre Konfigurationsdatei wie folgt:

<configuration>
  <runtime>
    <AppContextSwitchOverrides value="Switch.System.IO.UseLegacyPathHandling=false;Switch.System.IO.BlockLongPaths=false" />
  </runtime>
</configuration>

0

Das Erstellen eines separaten Prozesses mit Robocopy ist ebenfalls eine Lösung, wie hier beschrieben: Wie verschiebe ich Ordner / Dateien mit Pfadnamen> 255 Zeichen in Windows 8.1?

  public static void RoboCopy(string src, string dst)
        {
            Process p = new Process();
            p.StartInfo.Arguments = string.Format("/C Robocopy {0} {1}", src, dst);
            p.StartInfo.FileName = "CMD.EXE";
            p.StartInfo.CreateNoWindow = true;
            p.StartInfo.UseShellExecute = false;
            p.Start();
            p.WaitForExit();
        }

Wie in: Dateikopie mit Robo-Kopie und -Prozess zu sehen

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.