Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DirectoryNotFoundException when using long paths in .NET 4.7

I have set Enable Win32 Long Paths in the Local Group Policy Editor to Enabled and restarted the computer.

And here's the code:

string path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
for (int i = 0; i < 10; i++)
    path += "\\" + new string('z', 200);
Directory.CreateDirectory(path);

I'm getting the error:

System.IO.DirectoryNotFoundException: 'Could not find a part of the path 'C:\Users...\Desktop\zzzzzzzzzz...

(Which is actually a strange error message.)

app.config already has:

<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7" />

More info (probably not important)

I tried adding as mentioned in this post and elsewhere (though as pointed out in the comments it's not needed when using .net 4.7) in app.config under configuration:

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

Still same error.

If I only use one zzzzzz... it creates it on the desktop with no error.

I'm using VS2017, Windows 10. I tried Winforms and WPF.

like image 895
ispiro Avatar asked Jul 03 '17 15:07

ispiro


3 Answers

The Anniversary Update (RS1) has a bug that allow long paths to work without the manifest. For any updated Windows you must add the Application Manifest File item to your project. Otherwise it will not work.

<application xmlns="urn:schemas-microsoft-com:asm.v3">
  <windowsSettings>
    <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
  </windowsSettings>
</application>
like image 109
Alexrgs Avatar answered Nov 08 '22 01:11

Alexrgs


This might not answer your question but give you a tip for a workaround. I tested your snippet with mono 4.5 under Ubuntu Linux and works like a charm, but in Windows the story might be a little bit different. Here, the one to blame seems to be the .NET Framework itself, regarding this article and this other article, does not support long paths.

Therefore, the solution as @Anastasiosyal suggest in this StackOverflow answer is to rely on the Windows Api itself. There are two ways: direct bypassing or Api call.

Directory.CreateDirectory(@"\\?\" + veryLongPath);

Api call (code is not mine, got it from @Anastasiosyal answer):

// This code snippet is provided under the Microsoft Permissive License.
using System;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

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

public static void TestCreateAndWrite(string fileName) {

    string formattedName = @"\\?\" + fileName;
    // Create a file with generic write access
    SafeFileHandle fileHandle = CreateFile(formattedName,
        EFileAccess.GenericWrite, EFileShare.None, IntPtr.Zero,
        ECreationDisposition.CreateAlways, 0, IntPtr.Zero);

    // Check for errors
    int lastWin32Error = Marshal.GetLastWin32Error();
    if (fileHandle.IsInvalid) {
        throw new System.ComponentModel.Win32Exception(lastWin32Error);
    }

    // Pass the file handle to FileStream. FileStream will close the
    // handle
    using (FileStream fs = new FileStream(fileHandle,
                                    FileAccess.Write)) {
        fs.WriteByte(80);
        fs.WriteByte(81);
        fs.WriteByte(83);
        fs.WriteByte(84);
    }
}

In addition, I advise you to use Path.Combine instead of path + "\\" + subpath.

like image 2
Kyordhel Avatar answered Nov 08 '22 00:11

Kyordhel


I have an experience:

1) in desktop application (.NET 4.7) you not need nothing more, then use path name with prefix @"\?\ (don't need manifest, set UseLegacyPathHandling in app.confing) and all works

2) in web application you have to set this:

  bool legacyPaths;
  if (AppContext.TryGetSwitch("Switch.System.IO.UseLegacyPathHandling", out legacyPaths) && legacyPaths)
  {
    var switchType = Type.GetType("System.AppContextSwitches"); 
    if (switchType != null)
    {
      AppContext.SetSwitch("Switch.System.IO.UseLegacyPathHandling", false);   
      var legacyField = switchType.GetField("_useLegacyPathHandling", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
      legacyField?.SetValue(null, (Int32)0); 
      AppContext.TryGetSwitch("Switch.System.IO.UseLegacyPathHandling", out legacyPaths);
      Assert.IsFalse(legacyPaths, "Long pathnames are not supported!");
    }
  }

I hope the help you!

like image 1
Tomáš Sýkora Avatar answered Nov 08 '22 01:11

Tomáš Sýkora