MAX_PATH und die Fallstricke von Windows-Pfaden und Dateinamen ── Das 260-Zeichen-Limit, reservierte Namen, abschließende Punkte und Groß-/Kleinschreibung

· · MAX_PATH, Dateipfad, Langer Pfad, Dateiname, NTFS, Win32, C#, .NET, Windows-Entwicklung, Fehleranalyse, Technische Beratung

„Nur auf dem Rechner dieser einen Benutzerin tritt ‚Datei nicht gefunden‘ auf.“ „Das Kopieren war erfolgreich, aber diese Datei lässt sich nicht öffnen.“ Bei der Fehleranalyse einer Business-Anwendung mit Dateizugriffen stellt sich nicht selten heraus, dass die eigentliche Ursache in der Länge eines Pfads oder im Dateinamen selbst liegt. Anwender packen Projektnamen und Datumsangaben in Ordnernamen, graben tiefe Hierarchien und sprengen mühelos jede Annahme, die Sie sich zuvor gemacht haben.

Tückisch an diesem Thema ist, dass sich die Grenzwerte auf mehrere Ebenen verteilen – die Grenze der Win32-API selbst, die Grenze des Dateisystems, die Grenze der Shell (des Explorers) und die Grenze der .NET-Laufzeit –, sodass schwer zu erkennen ist, wie weit man tatsächlich gehen kann und was einem trotzdem noch zum Verhängnis wird. Dieser Artikel arbeitet auf, woraus MAX_PATH=260 tatsächlich besteht, unter welchen Bedingungen sich längere Pfade legal verarbeiten lassen, welche Fallstricke bei Dateinamen lauern – reservierte Namen wie CON und abschließende Punkte – und wie Groß-/Kleinschreibung behandelt wird, jeweils zusammen mit praktischen Gegenmaßnahmen in C#.

1. Das Wichtigste zuerst

  • MAX_PATH=260 ist eine Grenze der Win32-API, die Laufwerksbuchstabe, Doppelpunkt, Backslash, bis zu 256 Zeichen Pfadtext und das abschließende NUL umfasst. Die Dateisystemebene (NTFS und andere) kann deutlich längere Pfade verarbeiten – hängen Sie einer Unicode-API-Aufruf das Präfix \\?\ an, lassen sich insgesamt rund 32.767 Zeichen angeben.12
  • Um die 260-Zeichen-Grenze unter Windows 10 Version 1607 oder neuer aufzuheben, sind sowohl „LongPathsEnabled=1 in der Registrierung“ als auch „longPathAware im App-Manifest“ erforderlich. Wird nur eines von beiden gesetzt, bleibt es wirkungslos.3
  • Die .NET-(Core-)/.NET-5+-Laufzeit führt keine MAX_PATH-Prüfung durch und verarbeitet lange Pfade implizit. Zielt eine .NET-Framework-Anwendung auf Version 4.6.2 oder neuer, entfällt die laufzeiteigene 260-Zeichen-Prüfung.45
  • Dennoch existieren in der Praxis tatsächlich weiterhin Anwendungen ohne Unterstützung für lange Pfade. Die offizielle Dokumentation selbst hält unmissverständlich fest, dass die Shell (der Explorer) einen Pfad, den die Win32-API erzeugen konnte, unter Umständen nicht korrekt interpretiert.1
  • Dateinamen dürfen < > : " / \ | ? * sowie Steuerzeichen (0–31) nicht enthalten, und CON, PRN, AUX, NUL, COM1-9 sowie LPT1-9 gelten selbst mit angehängter Dateiendung (z. B. CON.txt) als reservierte Namen.6
  • Abschließende Leerzeichen und Punkte in einem Namen werden bei der Pfadnormalisierung stillschweigend entfernt. Das ist die Ursache für Pannen wie „report-v2.“ einzugeben und „report-v2“ zurückzubekommen, oder eine von einem anderen Betriebssystem mit abschließendem Leerzeichen erzeugte Datei von Windows aus nicht erreichen zu können.67
  • Windows-Dateinamen sind standardmäßig „case-preserving, aber case-insensitive“. NTFS unterstützt außerdem eine Unterscheidung der Groß-/Kleinschreibung pro Verzeichnis (fsutil.exe file setCaseSensitiveInfo), doch das Einschalten bringt den Nebeneffekt mit sich, dass Windows-Anwendungen damit nicht zwangsläufig zurechtkommen.89
  • Achten Sie auf der Implementierungsseite auf das Verhalten von Path.Combine, das vorangehende Argumente verwirft, sobald ein späteres Argument einen absoluten Pfad darstellt (Path.Join ist unter .NET Core eine Alternative), und bereinigen Sie von Benutzern eingegebene Dateinamen mit Path.GetInvalidFileNameChars sowie eigenen Prüfungen auf reservierte Namen und abschließende Zeichen.1011

2. Woraus MAX_PATH=260 tatsächlich besteht

Die maximale Pfadlänge in der Win32-API ist, von einigen Ausnahmen abgesehen, als MAX_PATH=260 Zeichen definiert. Diese 260 Zeichen setzen sich präzise zusammen. Ein lokaler Pfad besteht aus „Laufwerksbuchstabe, Doppelpunkt, Backslash, den durch Backslashes getrennten Namenskomponenten und dem abschließenden NUL-Zeichen“ – für ein Laufwerk D: ergibt sich beispielsweise als Maximum „D:\ + 256 Zeichen Pfadtext + das abschließende NUL“.1

Betrachtet man die 260 Zeichen also als „die für den Dateinamen verfügbare Länge“, fehlen einem, sobald man Laufwerksangabe und abschließendes NUL abzieht, tatsächlich vier Zeichen. Es gibt eine noch feinere Einschränkung: Da APIs zum Anlegen von Verzeichnissen Platz benötigen, um im Nachhinein einen Dateinamen im 8.3-Format anzuhängen, darf ein Verzeichnispfad MAX_PATH minus 12 Zeichen nicht überschreiten.1

Wichtig ist, dass dies eine Grenze auf Ebene der Win32-API ist, keine vom Dateisystem auferlegte Obergrenze. NTFS unterstützt lange Dateinamen und Pfade erweiterter Länge, und die Unicode-Versionen vieler Win32-Funktionen akzeptieren Pfade erweiterter Länge von insgesamt rund 32.767 Zeichen. Die Obergrenze für eine einzelne Komponente, aus denen sich der Pfad zusammensetzt (ein einzelner Ordner- oder Dateiname), ist der von GetVolumeInformation zurückgegebene Wert und liegt in der Regel bei 255 Zeichen.12

Genau diese Lücke – „die API sagt 260, das Dateisystem sagt rund 32.767“ – ist die Quelle realer Fehler in der Praxis. Es ist durchaus legitim, dass ein Pfad, den ein Werkzeug erzeugen konnte, in einem anderen Werkzeug (oder Ihrer eigenen Anwendung) nicht mehr geöffnet werden kann. Ein über git clone in einen Ordner mit langem Namen entpacktes tief verschachteltes Repository, dessen anschließender Build fehlschlägt, ist ein Lehrbuchbeispiel, das sogar die offizielle Dokumentation nennt.1

Noch ein Hinweis: In älteren .NET-Framework-Versionen löste ein vollständiger Pfad ab 260 Zeichen System.IO.PathTooLongException aus. Sehen Sie diese Ausnahme, sollten Sie zuerst die Pfadlänge verdächtigen.12

3. Wie Sie die 260-Zeichen-Grenze überwinden – und unter welchen Bedingungen

Es gibt grundsätzlich zwei Wege, lange Pfade zu verarbeiten: das Präfix \\?\ und die Aktivierung langer Pfade auf Betriebssystemebene.

3.1. Das Präfix \\?\

Hängen Sie einem Pfad-String \\?\ voran, hört die Win32-API auf, den String zu parsen, und reicht ihn direkt an das Dateisystem durch. Damit überwinden Sie das MAX_PATH-Limit (ein UNC-Pfad hat die Form \\?\UNC\server\share). Allerdings gibt es Bedingungen und Nebenwirkungen.16

  • Es muss sich um eine Unicode-API handeln (Funktionen mit dem Suffix „W“ oder alles, was wie bei .NET mit UTF-16 aufgerufen wird).
  • Da die Normalisierung übersprungen wird, können Sie weder / als Trennzeichen noch die relative Schreibweise mit ./.. verwenden. \\?\ lässt sich nicht an einen relativen Pfad anhängen, daher unterliegen relative Pfade stets dem Limit von MAX_PATH.1
  • Nicht jede API unterstützt es – Sie müssen die Referenzdokumentation jeder einzelnen API auf Unterstützung prüfen.6

3.2. Aktivierung langer Pfade ab Windows 10 1607 – die Bedingung lautet „beides“

Unter Windows 10 Version 1607 und neuer lässt sich das MAX_PATH-Limit für viele häufig genutzte Win32-Datei- und Verzeichnisfunktionen (CreateFileW, FindFirstFileW, GetFileAttributesW und andere) aufheben. Das ist jedoch ein Opt-in für die jeweilige Anwendung und erfordert beide der folgenden zwei Bedingungen.3

  1. Der Registrierungswert LongPathsEnabled (REG_DWORD) unter HKLM\SYSTEM\CurrentControlSet\Control\FileSystem muss auf 1 stehen. Alternativ lässt sich dies über die Gruppenrichtlinie „Computerkonfiguration > Administrative Vorlagen > System > Dateisystem > Win32-Langpfade aktivieren“ setzen.
  2. Das Anwendungsmanifest muss das Element longPathAware enthalten.
<application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
        <ws2:longPathAware>true</ws2:longPathAware>
    </windowsSettings>
</application>
# Registrierungsseite (erfordert Administratorrechte)
New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" `
-Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force

Die klassische Beschwerde „Ich habe den Registrierungswert gesetzt, trotzdem funktioniert es nicht“ liegt fast immer an einem fehlenden Manifest-Eintrag. Die offizielle Dokumentation betont diesen Punkt selbst nachdrücklich: „Diese Registrierungseinstellung wirkt sich nur auf Anwendungen aus, die angepasst wurden, um die neue Funktionalität zu nutzen.“ Zudem wird der Registrierungswert beim ersten Aufruf einer Dateifunktion pro Prozess zwischengespeichert und während der Lebensdauer des Prozesses nicht neu geladen, sodass unter Umständen ein Neustart nötig ist, damit die geänderte Einstellung garantiert in jeder Anwendung greift.3

3.3. Wie sich .NET dazu verhält

  • .NET (Core) / .NET 5 und neuer: Die Laufzeit führt keine MAX_PATH-Prüfung durch und verarbeitet lange Pfade implizit. Auf der App-Seite ist kein besonderer Code nötig.4
  • .NET Framework: Zielt die Anwendung auf 4.6.2 oder neuer, entfällt die laufzeiteigene 260-Zeichen-Prüfung, und PathTooLongException wird nur noch ausgelöst, wenn ein Pfad 32.767 Zeichen überschreitet oder das Betriebssystem selbst einen Fehler zurückgibt. Auch eine bestehende Anwendung, die eine frühere Version anvisiert, kann dies über die AppContext-Schalter Switch.System.IO.BlockLongPaths=false (und, um die klassische Pfadverarbeitung abzuschalten, Switch.System.IO.UseLegacyPathHandling=false) opt-in aktivieren.512
  • Damit eine .NET-Framework-Anwendung lange Pfade in der Praxis tatsächlich durchreicht, benötigen Sie sowohl die genannten Laufzeiteinstellungen als auch das Opt-in für lange Pfade auf OS-Ebene samt Manifest gemeinsam. Die Dokumentation zur Unterstützung langer Pfade von NuGet.exe legt genau diese Konfiguration als durchgerechnetes Beispiel dar (Windows 10 1607+, ein longPathAware-Manifest und das Deaktivieren von UseLegacyPathHandling).13

3.4. Die Realität: „Nicht unterstützende Anwendungen“ bleiben auch danach bestehen

Selbst nachdem all das erledigt ist, bedeutet das nicht, dass plötzlich jede Anwendung da draußen lange Pfade verarbeitet. Die offizielle Dokumentation stellt unmissverständlich fest, dass „die Shell und das Dateisystem unterschiedliche Anforderungen haben – die Shell-Oberfläche interpretiert einen über die Win32-API erzeugbaren Pfad möglicherweise nicht korrekt“,1 und in der Praxis gibt es weiterhin Werkzeuge, die keine Unterstützung für lange Pfade angeben (die NuGet-eigene Dokumentation weist beispielsweise darauf hin, dass die Wiederherstellung in Visual Studio und über msbuild -t:restore keine langen Pfade unterstützt13). Selbst wenn Ihre eigene Anwendung eine Datei unter einem langen Pfad anlegen kann, ist die Frage, ob der Benutzer sie anschließend im Explorer oder in einem anderen Werkzeug öffnen kann, eine völlig eigenständige Frage. Die Entscheidungstabelle in Kapitel 6 stellt Designentscheidungen dar, die dieser Asymmetrie Rechnung tragen.

4. Unzulässige Zeichen, reservierte Gerätenamen und abschließende Punkte/Leerzeichen

Neben der Pfadlänge ist das andere Minenfeld der Regelsatz für den Dateinamen selbst. Hier ein Überblick über die Teile der offiziellen Benennungsregeln, über die Business-Anwendungen am häufigsten stolpern.6

Kategorie Inhalt Anmerkungen
Reservierte Zeichen < > : " / \ \| ? * Einschließlich Pfadtrenner \ (und /) sowie des Laufwerks-Doppelpunkts :
Steuerzeichen Ganzzahlwert 0 (NUL) und 1–31 Nicht zulässig, außer innerhalb alternativer Datenströme
Reservierte Gerätenamen CON, PRN, AUX, NUL, COM1-COM9, LPT1-LPT9 (sowie die Formen mit hochgestellten Ziffern COM¹-³, LPT¹-³) Auch mit angehängter Dateiendung unzulässig (NUL.txt und NUL.tar.gz sind gleichbedeutend mit NUL)
Abschließende Zeichen Ein Name, der mit einem Leerzeichen oder einem Punkt endet Das Dateisystem lässt dies unter Umständen zu, Shell und UI jedoch nicht

4.1. Reservierte Gerätenamen – selbst CON.txt ist tabu

CON und NUL sind Gerätenamen aus der MS-DOS-Ära und bleiben bis heute im NT-Namensraum reserviert. Deshalb lässt sich eine Datei namens „CON“ auf herkömmlichem Weg nicht anlegen, und selbst das Anhängen einer Dateiendung wie bei CON.txt wird weiterhin als reservierter Name interpretiert.6 In der Praxis zeigt sich das etwa als Panne beim Versuch, ein Protokoll eines seriellen Anschlusses unter einem Namen wie „COM1.log“ zu speichern, oder als unter Linux angelegter Ordner namens „aux“, der sich unter Windows nicht entpacken lässt.

Am Rande: Nach den Regeln der Pfadnormalisierung wurde ein mit einem reservierten Namen beginnender Pfad – etwa „CON“ oder „COM1.TXT“ – früher umgewandelt und als Gerätepfad interpretiert (\\.\CON). Windows 11 hat diese Interpretation geändert, sodass inzwischen eine vollqualifizierte Form wie \\.\CON erforderlich ist, um das klassische Gerät tatsächlich anzusprechen.7 Dennoch gilt, da sowohl ältere Betriebssystemversionen als auch bestehende Anwendungen weiterhin die traditionelle Interpretation beibehalten: Die Schlussfolgerung, reservierte Namen für geschäftliche Daten zu meiden, ändert sich dadurch nicht.

4.2. Abschließende Leerzeichen und Punkte verschwinden „lautlos“

Die offiziellen Benennungsregeln legen fest, dass ein Datei- oder Verzeichnisname nicht mit einem Leerzeichen oder einem Punkt enden sollte.6 Noch konkreter formuliert es die Windows-Pfadnormalisierung: „Endet der Pfad nicht mit einem Trennzeichen, werden alle abschließenden Punkte und Leerzeichen (U+0020) entfernt.“7

Was das in der Praxis zu einem echten Problem macht, ist, dass sich der Name lautlos und ohne jede Fehlermeldung in einen anderen verwandelt. Gibt eine Benutzerin „report-v2.“ als Namen ein, wird tatsächlich eine Datei namens „report-v2“ angelegt – der abschließende Punkt ist verschwunden. Umgekehrt lässt sich eine von der Linux-Seite über SMB angelegte Datei wie „report “ (mit abschließendem Leerzeichen) über die normale Pfadschreibweise von Windows aus nicht erreichen, weil die Normalisierung den Namen unterwegs verändert. Um an einen solchen „legalen, aber durch die Normalisierung unerreichbaren“ Namen heranzukommen, dient das Präfix \\?\, das die Normalisierung überspringt. Die offizielle Dokumentation nennt diesen Anwendungsfall ausdrücklich und weist darauf hin, dass eine Datei wie hidden. auf keinem anderen Weg zugänglich ist.7

Ein führender Punkt ist übrigens völlig zulässig – ein Name wie .gitignore lässt sich problemlos anlegen.6

5. Groß-/Kleinschreibung wird „beibehalten, aber nicht unterschieden“

Das Standardverhalten des Windows-Dateisystems ist case-preserving, case-insensitive. Legen Sie eine Datei namens Readme.txt an, bleibt deren Schreibweise bei der Anzeige erhalten, doch Suche und Vergleich ignorieren die Groß-/Kleinschreibung, sodass README.TXT dieselbe Datei erreicht. Auch Laufwerksbuchstaben werden unabhängig von der Groß-/Kleinschreibung behandelt.86

Die offiziellen Benennungsregeln raten App-Entwicklern, keine Unterscheidung der Groß-/Kleinschreibung anzunehmen („OSCAR, Oscar und oscar sollten als derselbe Name behandelt werden“), und weisen zugleich darauf hin, dass NTFS selbst eine POSIX-artige Unterscheidung der Groß-/Kleinschreibung unterstützt (die standardmäßig jedoch deaktiviert ist).6

In der Praxis zeigt sich das vor allem im Zusammenhang mit der Linux-Interoperabilität. Ab Windows 10 Build 17107 lässt sich die Unterscheidung der Groß-/Kleinschreibung pro Verzeichnis aktivieren.9

# In einer als Administrator geöffneten PowerShell-Eingabeaufforderung
fsutil.exe file setCaseSensitiveInfo C:\work\linux-src enable
fsutil.exe file queryCaseSensitiveInfo C:\work\linux-src

Das ist ein sinnvolles Werkzeug bei der Arbeit mit einem Quellbaum, der ursprünglich von Linux stammt (wo etwa Makefile und makefile nebeneinander existieren), doch die offizielle Dokumentation warnt selbst vor einem Nebeneffekt: Eine Windows-Anwendung, die von einem case-insensitiven Dateisystem ausgeht, kann beim Zugriff auf Dateien in einem Verzeichnis, in dem die Unterscheidung aktiviert wurde, versagen. Zudem lässt sich das Flag nur umschalten, wenn das Zielverzeichnis leer ist, und ein neu angelegtes Unterverzeichnis übernimmt die Einstellung seines übergeordneten Verzeichnisses.9 Historisch ist zudem ein offiziell dokumentiertes Phänomen bekannt, bei dem der Explorer bei zwei Dateien, deren Namen sich nur in der Groß-/Kleinschreibung unterscheiden, beide anzeigte, die Auswahl jedoch stets dieselbe einzelne Datei öffnete.9

Für das Design von Business-Anwendungen lautet die praktische Faustregel: Behandeln Sie „nur durch Groß-/Kleinschreibung unterschiedlich“ unter Windows standardmäßig als denselben Namen, prüfen Sie aber auf Kollisionen durch Groß-/Kleinschreibung, sobald ein Dateiname zu Linux wechselt. Die Linux-Interoperabilität birgt schon vor der Dateinamensebene Fallstricke bei der Zeichenkodierung, weshalb sich auch ein Blick auf „Eine Einführung in die Windows-Zeichenkodierung — Mojibake bei der Interoperabilität mit Linux“ lohnt.

6. Praktische Hinweise für Business-Anwendungen — Pfadverknüpfung, Sanitisierung und eine Entscheidungstabelle

6.1. Kennen Sie das Verhalten von Path.Combine, bevor Sie es einsetzen

Pfade mit + zu verketten, kommt ohnehin nicht infrage, doch auch Path.Combine hat ein Verhalten, das Sie kennen müssen. Ist ein Argument ab dem zweiten ein absoluter Pfad, werden alle davor liegenden Argumente vollständig verworfen.10

var baseDir = @"C:\App\Data";

// Ist die Benutzereingabe zufällig ein absoluter Pfad, wird baseDir stillschweigend verworfen
Path.Combine(baseDir, @"C:\Windows\secret.txt"); // -> "C:\Windows\secret.txt"
Path.Combine(baseDir, @"\evil.txt");             // -> "\evil.txt" (Wurzel des aktuellen Laufwerks)

Übergeben Sie einen aus einer Benutzereingabe oder Konfigurationsdatei stammenden String direkt als zweites Argument, entsteht eine Schwachstelle, die außerhalb des vorgesehenen Speicherordners schreibt. Die offizielle Dokumentation warnt selbst davor, dass dieses Verhalten zu unbeabsichtigtem Zugriff auf sensible Dateien führen kann, und bietet Path.Join / Path.TryJoin (unter .NET Framework nicht verfügbar) als Alternativen an.1014 Unabhängig davon, welche Variante Sie verwenden, besteht die gängige Praxis letztlich darin, zu prüfen, ob das Ergebnis nach der Normalisierung mit Path.GetFullPath weiterhin unterhalb des Basisverzeichnisses liegt.

// Auch die Basisseite normalisieren, dann in einen relativen Pfad umwandeln und prüfen.
// Robuster als ein reiner String-Präfixvergleich gegenüber Eigenheiten wie
// abschließenden Trennzeichen oder einer Basis, die eine Laufwerkswurzel ist
var baseFull = Path.GetFullPath(baseDir);
var fullPath = Path.GetFullPath(Path.Combine(baseFull, userInput));
var relative = Path.GetRelativePath(baseFull, fullPath);
if (relative == ".." ||
    relative.StartsWith(".." + Path.DirectorySeparatorChar) ||
    Path.IsPathRooted(relative)) // Bei einem Wechsel auf ein anderes Laufwerk oder einen UNC-Pfad wird ein absoluter Pfad zurückgegeben
{
    throw new InvalidOperationException("Der Speicherort liegt außerhalb des erwarteten Ordners.");
}

Path.GetRelativePath vergleicht Pfade nach der Standardkonvention des Betriebssystems – unter Windows also case-insensitiv, was zum im vorigen Kapitel beschriebenen Standardverhalten „Windows ist standardmäßig case-insensitiv“ passt. Die Kehrseite: An einem Ort, an dem die Unterscheidung der Groß-/Kleinschreibung pro Verzeichnis aktiviert wurde (siehe voriges Kapitel), können Data und data unterschiedliche Verzeichnisse sein, sodass eine case-insensitive Prüfung Raum dafür lässt, „einen sich nur in der Groß-/Kleinschreibung unterscheidenden anderen Ordner“ fälschlich als innerhalb des Basisverzeichnisses liegend zu behandeln. Besteht die Möglichkeit, mit einer solchen Konfiguration konfrontiert zu sein, ist es die sicherere Vorgabe, einen Ort mit aktivierter Unterscheidung der Groß-/Kleinschreibung von vornherein nicht als Basisverzeichnis zu akzeptieren.

Noch etwas, das Sie bedenken sollten: Diese Prüfung operiert stets nur auf einem als Pfad normalisierten String. Existiert unterhalb des Basisverzeichnisses ein Junction Point oder ein symbolischer Link, kann der String zwar innerhalb der Basis zu liegen scheinen, während das tatsächliche Ziel außerhalb davon liegt. Und da ein Link nicht nur bei der finalen Datei, sondern auch bei einem zwischenliegenden Ordner platziert werden kann (in der Form basis\link\datei.txt), erfasst eine Prüfung, die nur den Endpunkt mit File.ResolveLinkTarget betrachtet, das nicht. Die erste Verteidigungslinie besteht schlicht darin, Konfigurationen zu vermeiden, in denen ein nicht vertrauenswürdiger Benutzer überhaupt erst Links oder Junctions unterhalb des Basisverzeichnisses anlegen kann. Ist darüber hinaus wirklich strikte Durchsetzung nötig, öffnen Sie entweder die Datei, ermitteln den aufgelösten Pfad aus dem Handle (das Win32-GetFinalPathNameByHandle) und prüfen, ob dieser innerhalb des Basisverzeichnisses liegt, oder Sie durchlaufen jede Ordnerkomponente des Pfads einzeln und prüfen jeweils, ob es sich um einen Link handelt.

6.2. Von Benutzern eingegebene Dateinamen bereinigen

Bei einer Funktion, die einen Dateinamen aus einer Benutzereingabe zusammensetzt – etwa nach dem Muster „Kundenname + Datum.csv“ –, sollten Sie die Bereinigung an einer zentralen Stelle bündeln. Path.GetInvalidFileNameChars ist der Ausgangspunkt, doch die offizielle Dokumentation stellt ausdrücklich fest, dass dieses Array keine Garantie für die vollständige Menge unzulässiger Zeichen darstellt.11 Reservierte Gerätenamen sowie abschließende Punkte/Leerzeichen werden von dieser API nicht erfasst, ergänzen Sie also eigene Prüfungen.

private static readonly HashSet<string> ReservedNames =
    new(StringComparer.OrdinalIgnoreCase)
    {
        "CON", "PRN", "AUX", "NUL",
        "COM1","COM2","COM3","COM4","COM5","COM6","COM7","COM8","COM9",
        "LPT1","LPT2","LPT3","LPT4","LPT5","LPT6","LPT7","LPT8","LPT9",
        "COM¹","COM²","COM³",  // Die hochgestellten Ziffern COM¹-COM³ sind ebenfalls reservierte Namen
        "LPT¹","LPT²","LPT³",  // Ebenso LPT¹-LPT³
    };

public static string SanitizeFileName(string input)
{
    var invalid = Path.GetInvalidFileNameChars();
    var name = new string(input.Select(c => invalid.Contains(c) ? '_' : c).ToArray());

    name = name.TrimEnd(' ', '.');            // Abschließende Leerzeichen/Punkte entfernen, da sie ohnehin stillschweigend wegfallen würden

    // Innerhalb der Längenbegrenzung für eine einzelne Dateinamenskomponente bleiben (meist 255 Zeichen).
    // Konservativ kürzen und Platz für die Ordnerhierarchie sowie ein später von der App angehängtes Suffix lassen
    const int MaxNameLength = 120;
    if (name.Length > MaxNameLength)
    {
        var ext = Path.GetExtension(name);
        if (ext.Length > 20)
        {
            ext = ""; // Eine absurd lange „Dateiendung“ nicht als solche beibehalten (vermeidet eine Ausnahme durch einen negativen Bereich)
        }
        name = name[..(MaxNameLength - ext.Length)].TrimEnd(' ', '.') + ext;
    }

    // Stets gegen das „endgültige“ Ergebnis auf leere/reservierte Namen prüfen,
    // um Fälle abzufangen, in denen Kürzen oder TrimEnd den Namen zu einem leeren String
    // oder einem reservierten Namen (wie NUL) werden lässt
    var stem = name.Split('.')[0];            // Schutz gegen NUL.txt: die Liste reservierter Namen gegen den Teil vor der Dateiendung prüfen
    if (name.Length == 0 || ReservedNames.Contains(stem))
    {
        name = "_" + name;                    // Das Hinzufügen eines Zeichens passt weiterhin komfortabel unter das 255-Zeichen-Limit
    }
    return name;
}

Dieses Problem tritt häufig im Zusammenhang mit Dateinamen für CSV-Ausgaben auf; zur Praxis von CSV selbst siehe auch „CSV ist nicht einfach ‚reiner Text‘ — praktischer Umgang mit CSV in C#-Business-Anwendungen“.

6.3. Die Fallstricke relativer Pfade und des aktuellen Verzeichnisses

Relative Pfade bergen zwei Fallstricke. Erstens ist das aktuelle Verzeichnis eine prozessweite Einstellung, die jederzeit von jedem Thread aus geändert werden kann. Die offizielle Dokumentation formuliert es sogar so: „Relative Pfade sind in Multithread-Anwendungen gefährlich“, und ab .NET Core 2.1 können Sie Path.GetFullPath(string, string) verwenden, womit sich der Basispfad explizit angeben lässt.7 Zweitens ist eine Form wie C:tmp.txtohne Backslash direkt nach dem Laufwerksbuchstaben – „ein relativer Pfad zum aktuellen Verzeichnis auf Laufwerk C:“, kein absoluter Pfad. Dieser „laufwerksrelative Pfad“ wird von der offiziellen Dokumentation ausdrücklich als klassische Fehlerquelle in Programmen und Skripten genannt.7

Machen Sie es sich zur Gewohnheit, jeden aus einer Konfigurationsdatei oder Benutzereingabe empfangenen Pfad sofort bei Erhalt über Path.GetFullPath in einen absoluten Pfad umzuwandeln, bevor Sie ihn protokollieren oder validieren.

6.4. Entscheidungstabelle — lange Pfade unterstützen oder von vornherein zurückweisen?

Situation Empfehlung Begründung
Eine allgemeine Business-Anwendung, bei der Benutzer den Speicherort frei wählen An der Tür validieren und zurückweisen (vor dem Speichern die Länge des vollständigen Pfads und den Dateinamen prüfen und einen klaren Fehler anzeigen) Selbst wenn die eigene Anwendung es unterstützt, besteht weiterhin das Risiko, dass Explorer oder ein nachgelagertes Werkzeug die Datei nicht öffnen kann1
Sicherung, Synchronisierung oder das Entpacken von Archiven – also das „Lesen“ einer von jemand anderem angelegten tiefen Hierarchie Lange Pfade unterstützen (.NET-Core-Familie + bei Bedarf Manifest; unter Framework eine Konfiguration ab 4.6.2) Auf die Eingabe haben Sie keinen Einfluss, und der Geschäftsbetrieb kommt zum Stillstand, wenn sich die Daten nicht lesen lassen54
Die eigene Anwendung ist es selbst, die eine tiefe Hierarchie „anlegt“ Im Regelfall so umgestalten, dass dies unterbleibt (Hierarchie abflachen, gehashte Namen einführen und Ähnliches) Es besteht eine gute Chance, dass diejenigen, die den erzeugten Pfad am Ende verwenden – Menschen oder eine andere Anwendung – ihn nicht unterstützen1
Dateiaustausch mit Linux/WSL Vor der Übertragung reservierte Namen, Kollisionen durch Groß-/Kleinschreibung und abschließende Zeichen prüfen Andernfalls entstehen Dateien, die von der Windows-Seite aus nicht erreichbar sind69
Erzeugen eines Dateinamens aus einer Benutzereingabe Die Bereinigung in einer gemeinsamen Funktion bündeln, die GetInvalidFileNameChars mit Prüfungen auf reservierte Namen und abschließende Zeichen kombiniert Das eigene Array der API allein ist unvollständig11

7. Fehlersuche — „Im Explorer sichtbar, lässt sich aber nicht öffnen“

Hier eine schrittweise Vorgehensweise für die klassische Beschwerde: „Die Datei wird im Explorer einwandfrei angezeigt, aber das Öffnen in der Anwendung liefert ‚Datei nicht gefunden’.“

Prüfung Methode Bei Zutreffen
Liegt der vollständige Pfad nahe an 260 Zeichen? In PowerShell: (Get-ChildItem -Recurse).FullName \| Where-Object { $_.Length -ge 250 } Einen übergeordneten Ordnernamen kürzen oder Unterstützung für lange Pfade in Betracht ziehen (Kapitel 3)
Ist der Dateiname ein reservierter Name (aux, con, com1 usw.)? Den Namen visuell prüfen; dies gilt auch für Namen mit angehängter Dateiendung6 Umbenennen (stammt die Quelle etwa von Linux, bei der Übertragung umwandeln)
Endet er mit einem Leerzeichen oder Punkt? Mit cmd /c dir /x oder einer in Anführungszeichen gesetzten Anzeige prüfen Über einen Pfad mit dem Präfix \\?\ entfernen oder umbenennen7
Gibt es zwei Dateien mit demselben Namen, die sich nur in der Groß-/Kleinschreibung unterscheiden? Häufig in Ordnern, die aus WSL/Git stammen9 Eine davon umbenennen oder überdenken, wofür dieses Verzeichnis genutzt wird
Verwenden Sie einen relativen oder laufwerksrelativen Pfad? Den tatsächlich versuchten absoluten Pfad protokollieren Vor der Verwendung mit Path.GetFullPath in einen absoluten Pfad umwandeln7

Für eine Fehleranalyse empfiehlt sich vor allem, den exakten Pfad, den die Anwendung zu öffnen versuchte, als absoluten Pfad und in Anführungszeichen eingeschlossen im Fehlerprotokoll der Anwendung zu protokollieren. Eine Ausnahmemeldung, die lediglich „Datei nicht gefunden“ besagt, lässt Sie im Nachhinein nicht mehr unterscheiden, ob der Pfad abgeschnitten wurde, bei der Normalisierung umbenannt wurde oder von Anfang an schlicht auf ein anderes Verzeichnis zeigte. In Anführungszeichen protokolliert, wird ein schwer zu erkennendes Problem wie ein abschließendes Leerzeichen auf einen Blick sichtbar.

Noch ein Hinweis: Auch ein fehlgeschlagenes Laden einer DLL gehört regelmäßig zur Familie der „Datei nicht gefunden“-artigen Fehler, ist jedoch häufiger ein Problem der Suchreihenfolge als der Pfadlänge – dazu mehr in „Wie die Namensauflösung von Windows-DLLs funktioniert — Suchreihenfolge und SxS“.

8. Zusammenfassung

  • MAX_PATH=260 ist eine Grenze der Win32-API, die „D:\ + bis zu 256 Zeichen + das abschließende NUL“ umfasst, während NTFS selbst Pfade erweiterter Länge von rund 32.767 Zeichen verarbeiten kann. Für Verzeichnisse gilt eine weitere Einschränkung von MAX_PATH minus 12.
  • Um über 260 Zeichen hinauszukommen, benötigen Sie entweder das Präfix \\?\ (nur Unicode-API, keine relativen Pfade) oder die Aktivierung langer Pfade ab Windows 10 1607 (sowohl LongPathsEnabled in der Registrierung als auch longPathAware im Manifest).
  • .NET (Core)/5+ verarbeitet lange Pfade implizit, und ab .NET Framework 4.6.2 als Ziel entfällt die laufzeiteigene Prüfung. Anwendungen ohne Unterstützung, darunter der Explorer, existieren jedoch weiterhin, behandeln Sie „lässt sich anlegen“ und „lässt sich vom Benutzer verarbeiten“ also als getrennte Fragen.
  • Dateinamen dürfen die reservierten Zeichen (< > : " / \ | ? *) oder Steuerzeichen nicht enthalten; reservierte Gerätenamen wie CON, NUL und COM1 sind selbst mit angehängter Dateiendung tabu, und abschließende Leerzeichen/Punkte werden bei der Normalisierung stillschweigend entfernt.
  • Die Groß-/Kleinschreibung wird standardmäßig „beibehalten, aber nicht unterschieden“. Die Unterscheidung der Groß-/Kleinschreibung pro Verzeichnis über fsutil file setCaseSensitiveInfo ist für die WSL-Interoperabilität nützlich, geht aber mit dem Risiko einher, dass Windows-Anwendungen sich fehlerhaft verhalten.
  • Auf der Implementierungsseite sind bewährte Praktiken: das Basisverzeichnis unter Berücksichtigung des Verhaltens von Path.Combine bei absoluten Argumenten validieren, die Bereinigung um GetInvalidFileNameChars sowie Prüfungen auf reservierte Namen/abschließende Zeichen bündeln und relative Pfade durch Umwandlung in absolute Pfade mit Path.GetFullPath eliminieren.

Verwandte Artikel

Verwandte Beratungsleistungen

Komura Software LLC übernimmt die Ursachenanalyse von Fehlern bei Dateizugriffen wie „nur in dieser bestimmten Umgebung oder bei dieser Datei lässt sich nichts öffnen“, die Überprüfung der Unterstützung langer Pfade und des Designs der Dateinamensvalidierung in bestehenden Business-Anwendungen sowie Design-Beratung zur Dateiinteroperabilität in gemischten Windows-/Linux-Umgebungen.

  1. Microsoft Learn, Maximum Path Length Limitation. Zur Definition von MAX_PATH=260 und ihrer Zusammensetzung aus „Laufwerksbuchstabe + Doppelpunkt + Backslash + 256 Zeichen + abschließendes NUL“, zum über Unicode-APIs und das Präfix \\?\ verfügbaren Pfad erweiterter Länge von rund 32.767 Zeichen, zur Komponentenlängenbegrenzung (in der Regel 255 Zeichen), dazu, dass relative Pfade stets auf MAX_PATH begrenzt sind, dazu, dass das Anlegen von Verzeichnissen auf MAX_PATH minus 12 begrenzt ist, sowie dazu, dass Shell und Dateisystem unterschiedliche Anforderungen haben, sodass die Shell-Oberfläche einen über Win32 erzeugbaren Pfad möglicherweise nicht interpretiert.  2 3 4 5 6 7 8 9 10 11

  2. Microsoft Learn, NTFS overview. Dazu, dass NTFS lange Dateinamen und Pfade erweiterter Länge von rund 32.767 Zeichen unterstützt, sowie zur Abwärtskompatibilität über 8.3-Aliase.  2

  3. Microsoft Learn, Maximum Path Length Limitation — Enable long paths in Windows 10, version 1607, and later. Dazu, dass unter Windows 10 1607 und neuer sowohl der Registrierungswert LongPathsEnabled=1 als auch das Element longPathAware im App-Manifest nötig sind, zur Gruppenrichtlinieneinstellung, dazu, dass der Registrierungswert pro Prozess zwischengespeichert wird, sowie zur Liste der Win32-Funktionen, für die das Limit aufgehoben wird.  2 3

  4. Microsoft Learn, File path formats on Windows systems — Skip normalization. Dazu, dass .NET Core und .NET 5+ lange Pfade implizit ohne MAX_PATH-Prüfung verarbeiten (die MAX_PATH-Prüfung gilt nur für .NET Framework), sowie dazu, dass \\?\ der Mechanismus ist, der die Normalisierung überspringt.  2 3

  5. Microsoft Learn, Retargeting changes for migration to .NET Framework 4.6.x. Dazu, dass die Ausrichtung auf .NET Framework 4.6.2 lange Pfade (bis 32K Zeichen) unterstützt und das 260-Zeichen-Limit entfernt, sowie dazu, dass bestehende Anwendungen mit einer früheren Zielversion dies über Switch.System.IO.BlockLongPaths=false opt-in aktivieren können.  2 3

  6. Microsoft Learn, Naming Files, Paths, and Namespaces. Zu reservierten Zeichen (< > : " / \ | ? *) und Steuerzeichen (0–31), reservierten Gerätenamen (CON/PRN/AUX/NUL/COM1-9/LPT1-9 und ihren Formen mit hochgestellten Ziffern), dazu, dass Namen wie NUL.txt mit Dateiendung dem reservierten Namen gleichgestellt sind, dazu, dass Namen nicht mit einem abschließenden Leerzeichen oder Punkt enden sollten, dazu, dass ein führender Punkt zulässig ist, dazu, keine Unterscheidung der Groß-/Kleinschreibung anzunehmen, sowie zur POSIX-Semantik von NTFS und zum Verhalten und zur Unicode-API-Anforderung des Präfixes \\?\ 2 3 4 5 6 7 8 9 10 11 12

  7. Microsoft Learn, File path formats on Windows systems — Path normalization. Dazu, dass die Pfadnormalisierung abschließende Punkte und Leerzeichen entfernt, dazu, dass ein Name wie hidden. nur über \\?\ zugänglich ist, zur Interpretation klassischer Gerätenamen wie CON und der Änderung in Windows 11, dazu, dass laufwerksrelative Pfade (C:tmp.txt) eine häufige Fehlerquelle in Programmen und Skripten sind, dazu, dass das aktuelle Verzeichnis prozessweit gilt und relative Pfade unter Multithreading gefährlich sind, sowie zu Path.GetFullPath(String, String).  2 3 4 5 6 7 8

  8. Microsoft Learn, File path formats on Windows systems — Case and the Windows file system. Dazu, dass Verzeichnis- und Dateinamen die bei der Erstellung verwendete Schreibweise beibehalten, während Namensvergleiche weiterhin unabhängig von der Groß-/Kleinschreibung erfolgen.  2

  9. Microsoft Learn, Adjust case sensitivity. Zur ab Windows 10 Build 17107 verfügbaren Unterscheidung der Groß-/Kleinschreibung pro Verzeichnis (fsutil.exe file setCaseSensitiveInfo), zur Voraussetzung von Administratorrechten und eines leeren Verzeichnisses für die Änderung, dazu, dass neue Unterverzeichnisse die Einstellung übernehmen, zur Warnung, dass Windows-Anwendungen, die von Case-Insensitivität ausgehen, sich fehlerhaft verhalten können, sowie zum historischen Fall, dass zwei sich nur in der Groß-/Kleinschreibung unterscheidende Dateien beide im Explorer erschienen, sich jedoch stets nur eine tatsächlich öffnen ließ.  2 3 4 5 6

  10. Microsoft Learn, Path.Combine Method. Dazu, dass ein absoluter Pfad in einem beliebigen Argument nach dem ersten dazu führt, dass alle vorangehenden Pfadelemente verworfen werden und ein beim absoluten Element beginnender String zurückgegeben wird, dass dies möglicherweise zu unbeabsichtigtem Zugriff auf sensible Dateien führt, sowie dazu, dass Join/TryJoin (unter .NET Framework nicht verfügbar) als Alternative angeboten werden.  2 3

  11. Microsoft Learn, Path.GetInvalidFileNameChars Method. Dazu, dass dies ein Array von in einem Dateinamen unzulässigen Zeichen zurückgibt, sowie dazu, dass das zurückgegebene Array keine Garantie für die vollständige Menge unzulässiger Zeichen darstellt, da diese je nach Dateisystem variieren kann.  2 3

  12. Microsoft Learn, PathTooLongException Class. Dazu, dass dies die Ausnahme ist, die bei Überschreiten der vom System definierten maximalen Pfadlänge ausgelöst wird, sowie dazu, dass .NET Framework 4.6.2 und neuer sie nur noch auslöst, wenn der Pfad 32.767 Zeichen überschreitet oder das Betriebssystem selbst einen Fehler zurückgibt.  2

  13. Microsoft Learn, Long Path Support (NuGet CLI). Zur tatsächlich erforderlichen Konfiguration, damit auf .NET Framework basierende Werkzeuge lange Pfade nutzen (Windows 10 1607+ oder 1511 + .NET Framework 4.6.2, die Win32-Langpfad-Richtlinie, das longPathAware-Manifest sowie das Deaktivieren von UseLegacyPathHandling), sowie dazu, dass die Wiederherstellung in Visual Studio und msbuild keine langen Pfade unterstützt.  2

  14. Microsoft Learn, Path.Join Method. Dazu, dass Join einen nachfolgenden absoluten Pfad verkettet, statt ihn zu verwerfen, mit durchgerechneten Beispielen des Verhaltensunterschieds zu Combine. 

Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.

Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.

Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.

Häufige Fragen

Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.

Wie viele Zeichen darf ein Windows-Pfad haben?
Standardmäßig liegt die Grenze der Win32-API bei MAX_PATH=260 Zeichen. Diese Länge umfasst Laufwerksbuchstabe, Doppelpunkt, Backslash, bis zu 256 Zeichen Pfadtext sowie das abschließende NUL-Zeichen. Das Dateisystem selbst (NTFS und andere) kann deutlich längere Pfade verarbeiten – übergeben Sie einer Unicode-API einen Pfad mit dem Präfix \\?\, lassen sich insgesamt rund 32.767 Zeichen angeben. Ein einzelner Ordner- oder Dateiname (eine Pfadkomponente) ist jedoch in der Regel auf 255 Zeichen begrenzt, und relative Pfade unterliegen stets dem Limit von MAX_PATH.
Wie hebe ich die 260-Zeichen-Grenze von MAX_PATH auf?
Unter Windows 10 Version 1607 und neuer entfällt für viele Win32-Dateifunktionen die 260-Zeichen-Grenze, wenn Sie sowohl den Registrierungswert LongPathsEnabled=1 (oder die Gruppenrichtlinie „Win32-Langpfade aktivieren“) als auch das Element longPathAware im Anwendungsmanifest setzen. Wird nur eines von beiden gesetzt, bleibt es wirkungslos. Die .NET-(Core-)/.NET-5+-Laufzeit führt gar keine MAX_PATH-Prüfung durch und verarbeitet lange Pfade implizit; bei .NET Framework entfällt die laufzeiteigene 260-Zeichen-Prüfung, sobald Sie mindestens Version 4.6.2 als Ziel wählen. Es gibt jedoch weiterhin Anwendungen ohne Unterstützung für lange Pfade – darunter der Explorer selbst –, sodass Sie mitbedenken müssen, wer den von Ihnen erzeugten langen Pfad am Ende verarbeitet.
Warum kann ich keine Datei namens CON oder NUL anlegen?
CON, PRN, AUX, NUL, COM1 bis COM9 sowie LPT1 bis LPT9 sind reservierte Gerätenamen aus der MS-DOS-Ära, und Windows interpretiert diese Namen als Geräte statt als Dateien. Auch das Anhängen einer Dateiendung wie bei NUL.txt hilft nicht – der Name wird weiterhin genauso wie NUL behandelt. Windows 11 hat einen Teil der Interpretation solcher Pfade geändert, doch da ältere Betriebssystemversionen und eine große Zahl bestehender Anwendungen weiterhin der traditionellen Interpretation folgen, bleibt es am sichersten, diese Namen für geschäftliche Dateien zu meiden.
Unterscheidet Windows bei Dateinamen zwischen Groß- und Kleinschreibung?
Windows verhält sich standardmäßig case-preserving, aber case-insensitive. Legen Sie eine Datei namens Readme.txt an, bleibt die Schreibweise bei der Anzeige erhalten, doch der Versuch, README.TXT zu öffnen, führt weiterhin zur selben Datei. NTFS unterstützt zudem eine POSIX-artige Unterscheidung von Groß- und Kleinschreibung, und ab Windows 10 Build 17107 lässt sich diese Unterscheidung pro Verzeichnis mit fsutil.exe file setCaseSensitiveInfo aktivieren – das hat jedoch den Nebeneffekt, dass Windows-Anwendungen, die von Case-Insensitivität ausgehen, nicht mehr zuverlässig funktionieren. Der Einsatz sollte daher auf tatsächlich notwendige Fälle wie die WSL-Interoperabilität beschränkt bleiben.

Autorenprofil

Profilseite des Artikelautors.

Go Komura

Geschäftsführer von KomuraSoft LLC

Spezialisiert auf Windows-Softwareentwicklung, technische Beratung und Fehleranalyse, insbesondere bei bestehenden Systemen und schwer reproduzierbaren Störungen.

Zurück zum Blog