How to avoid System.IO.PathTooLongException?

Viewed 64144

We constantly run into this problem...

Example:

if I have a file that I want to copy it into an another directory or UNC share and if the length of the path exceeds 248 (if I am not mistaken), then it throws PathTooLongException. Is there any workaround to this problem?

PS: Is there any registry setting to set this path to a longer char set?

10 Answers

This has been discussed in depth by the BCL team, see the blog entries

In essence there is no way to do this within .Net code and stick to the BCL. Too many functions rely on being able to canonicalize the path name (which immediately triggers the use of functions expecting MAX_PATH to be obeyed).

You could wrap all the win32 functions that support the "\\?\" syntax, with these you would be able to implement a suite of long path aware functionality but this would be cumbersome.

Since a vast number of tools (including explorer[1]) cannot handle long path names it is inadvisable to go down this route unless you are happy that all interaction with the resulting file system goes through your library (or the limited number of tools that are built to handle it like robocopy)

In answer to your specific need I would investigate whether the use of robocopy directly would be sufficient to perform this task.

[1] Vista has ways to mitigate the issue with some fancy renaming under the hood but this is fragile at best)

My Drive-Mapping solution works fine and stable using „NetWorkDrive.cs“ and „NetWorkUNCPath.cs“ which are listed below.

Test example:

if (srcFileName.Length > 260)
{
   string directoryName = srcFileName.Substring(0, srcFileName.LastIndexOf('\\'));

   var uncName = GetUNCPath(srcFileName.Substring(0, 2)) + directoryName.Substring(2);

   using (NetWorkDrive nDrive = new NetWorkDrive(uncName))
   {
     drvFileName = nDrive.FullDriveLetter + Path.GetFileName(sourceFileName)
     File.Copy(drvFileName, destinationFileName, true);
   }
}
else
{
   File.Copy(srcFileName, destinationFileName, true);
}

NetWorkDrive.cs souce code:

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.ComponentModel;
namespace SeekCopySupportTool.Business
{

public class NetWorkDrive : IDisposable
{
    #region private fields
    private string m_DriveLetter = string.Empty;
    private string m_FullDriveLetter = string.Empty;
    private bool m_Disposed = false;
    //this list specifies the drive-letters, whitch will be used to map networkfolders
    private string[] possibleDriveLetters = new string[] 
    { 
        "G:\\", 
        "H:\\", 
        "I:\\", 
        "J:\\", 
        "K:\\", 
        "L:\\", 
        "M:\\", 
        "N:\\", 
        "O:\\", 
        "P:\\", 
        "Q:\\", 
        "R:\\", 
        "S:\\", 
        "T:\\", 
        "U:\\", 
        "V:\\", 
        "W:\\", 
        "X:\\", 
        "Y:\\", 
        "Z:\\" 
    };
    #endregion

    #region public properties
    public string DriveLetter
    {
        get { return m_DriveLetter; }
        set { m_DriveLetter = value; }
    }
    public string FullDriveLetter
    {
        get { return m_FullDriveLetter; }
        set { m_FullDriveLetter = value; }
    }
    #endregion

    #region .ctor
    public NetWorkDrive(string folderPath)
    {
        m_FullDriveLetter = MapFolderAsNetworkDrive(folderPath);
        if (string.IsNullOrEmpty(m_FullDriveLetter))
        {
            throw new Exception("no free valid drive-letter found");
        }
        m_DriveLetter = m_FullDriveLetter.Substring(0,2);
    }
    #endregion

    #region private methods
    /// maps a given folder to a free drive-letter (f:\)
    /// <param name="folderPath">the folder to map</param>
    /// <returns>the drive letter in this format: "(letter):\" -> "f:\"</returns>
    /// <exception cref="Win32Exception">if the connect returns an error</exception>
    private string MapFolderAsNetworkDrive(string folderPath)
    {
        string result = GetFreeDriveLetter();
        NETRESOURCE myNetResource = new NETRESOURCE();
        myNetResource.dwScope = ResourceScope.RESOURCE_GLOBALNET;
        myNetResource.dwType = ResourceType.RESOURCETYPE_ANY;
        myNetResource.dwDisplayType = ResourceDisplayType.RESOURCEDISPLAYTYPE_SERVER;
        myNetResource.dwUsage = ResourceUsage.RESOURCEUSAGE_CONNECTABLE;
        myNetResource.lpLocalName = result.Substring(0,2);
        myNetResource.lpRemoteName = folderPath;
        myNetResource.lpProvider = null;
        int errorcode = WNetAddConnection2(myNetResource, null, null, 0);
        if(errorcode != 0)
        {
            throw new Win32Exception(errorcode);
        }
        return result;
    }
    private void DisconnectNetworkDrive()
    {
        int CONNECT_UPDATE_PROFILE = 0x1;
        int errorcode = WNetCancelConnection2(m_DriveLetter, CONNECT_UPDATE_PROFILE, true);
        if (errorcode != 0)
        {
            throw new Win32Exception(errorcode);
        }
    }

    private string GetFreeDriveLetter()
    {
        //first get the existing driveletters
        const int size = 512;
        char[] buffer = new char[size];
        uint code = GetLogicalDriveStrings(size, buffer);
        if (code == 0)
        {                
            return "";
        }
        List<string> list = new List<string>();
        int start = 0;
        for (int i = 0; i < code; ++i)
        {
            if (buffer[i] == 0)
            {
                string s = new string(buffer, start, i - start);
                list.Add(s);
                start = i + 1;
            }
        }            
        foreach (string s in possibleDriveLetters)
        {                
            if (!list.Contains(s))
            {
                return s;
            }
        }
        return null;
    }        
    #endregion

    #region dll imports
    /// <summary>
    /// to connect to a networksource
    /// </summary>
    /// <param name="netResource"></param>
    /// <param name="password">null the function uses the current default password associated with the user specified by the username parameter ("" the function does not use a password)</param>
    /// <param name="username">null the function uses the default user name (The user context for the process provides the default user name)</param>
    /// <param name="flags"></param>
    /// <returns></returns>
    [DllImport("mpr.dll")]
    //public static extern int WNetAddConnection2(ref NETRESOURCE netResource, string password, string username, int flags);
    private static extern int WNetAddConnection2([In] NETRESOURCE netResource, string password, string username, int flags);
    /// <summary>
    /// to disconnect the networksource
    /// </summary>
    /// <param name="lpName"></param>
    /// <param name="dwFlags"></param>
    /// <param name="bForce"></param>
    /// <returns></returns>
    [DllImport("mpr.dll")]
    private static extern int WNetCancelConnection2(string lpName, Int32 dwFlags, bool bForce);
    /// <param name="nBufferLength"></param>
    /// <param name="lpBuffer"></param>
    /// <returns></returns>
    [DllImport("kernel32.dll")]
    private static extern uint GetLogicalDriveStrings(uint nBufferLength, [Out] char[] lpBuffer);
    #endregion

    #region enums/structs
    /// <example>
    /// NETRESOURCE myNetResource = new NETRESOURCE();
    /// myNetResource.dwScope = 2;
    /// myNetResource.dwType = 1;
    /// myNetResource.dwDisplayType = 3;
    /// myNetResource.dwUsage = 1;
    /// myNetResource.LocalName = "z:";
    /// myNetResource.RemoteName = @"\servername\sharename";
    /// myNetResource.Provider = null;
    /// </example>
    [StructLayout(LayoutKind.Sequential)]
    public class NETRESOURCE
    {
        public ResourceScope dwScope = 0;
        public ResourceType dwType = 0;
        public ResourceDisplayType dwDisplayType = 0;
        public ResourceUsage dwUsage = 0;
        public string lpLocalName = null;
        public string lpRemoteName = null;
        public string lpComment = null;
        public string lpProvider = null;
    };
    public enum ResourceScope : int
    {
        RESOURCE_CONNECTED = 1,
        RESOURCE_GLOBALNET,
        RESOURCE_REMEMBERED,
        RESOURCE_RECENT,
        RESOURCE_CONTEXT
    };
    public enum ResourceType : int
    {
        RESOURCETYPE_ANY,
        RESOURCETYPE_DISK,
        RESOURCETYPE_PRINT,
        RESOURCETYPE_RESERVED
    };
    public enum ResourceUsage
    {
        RESOURCEUSAGE_CONNECTABLE = 0x00000001,
        RESOURCEUSAGE_CONTAINER = 0x00000002,
        RESOURCEUSAGE_NOLOCALDEVICE = 0x00000004,
        RESOURCEUSAGE_SIBLING = 0x00000008,
        RESOURCEUSAGE_ATTACHED = 0x00000010,
        RESOURCEUSAGE_ALL = (RESOURCEUSAGE_CONNECTABLE | RESOURCEUSAGE_CONTAINER | RESOURCEUSAGE_ATTACHED),
    };
    public enum ResourceDisplayType
    {
        RESOURCEDISPLAYTYPE_GENERIC,
        RESOURCEDISPLAYTYPE_DOMAIN,
        RESOURCEDISPLAYTYPE_SERVER,
        RESOURCEDISPLAYTYPE_SHARE,
        RESOURCEDISPLAYTYPE_FILE,
        RESOURCEDISPLAYTYPE_GROUP,
        RESOURCEDISPLAYTYPE_NETWORK,
        RESOURCEDISPLAYTYPE_ROOT,
        RESOURCEDISPLAYTYPE_SHAREADMIN,
        RESOURCEDISPLAYTYPE_DIRECTORY,
        RESOURCEDISPLAYTYPE_TREE,
        RESOURCEDISPLAYTYPE_NDSCONTAINER
    };
    #endregion

    #region IDisposable Members
    public void Dispose()
    {
        Dispose(true);
    }
    #endregion

    #region overrides/virtuals
    public override string ToString()
    {
        return m_FullDriveLetter;
    }
    /// <param name="disposing"></param>
    protected virtual void Dispose(bool disposing)
    {
        if (!m_Disposed)
        {
            if (disposing)
            {
                DisconnectNetworkDrive();
            }
            m_Disposed = true;
        }
    }
    #endregion
}

}

NetWorkUNCPath.cs source code:

using System;
using System.Management;
namespace SeekCopySupportTool.Business
{

public class NetWorkUNCPath
{
    // get UNC path
    public static string GetUNCPath(string path)
    {
        if (path.StartsWith(@"\\"))
        {
            return path;
        }

        ManagementObject mo = new ManagementObject();
        mo.Path = new ManagementPath(String.Format("Win32_LogicalDisk='{0}'", path));

        // DriveType 4 = Network Drive
        if (Convert.ToUInt32(mo["DriveType"]) == 4)
        {
            return Convert.ToString(mo["ProviderName"]);
        }
        // DriveType 3 = Local Drive
        else if (Convert.ToUInt32(mo["DriveType"]) == 3)
        {
            return "\\\\" + Environment.MachineName + "\\" + path.Substring(0,1) + "$";
        }
        else
        {
            return path;
        }
    }
}

}

I have been surprise to discover that solution of Jason work perfectly well on my PC using Visual Studio 2019 and .Net Framework 4.7.2

Initially, I have written something similar to this code

Dim sBaseDir = "D:\Documents\+Informatique\Application\@Visual Basic.NET\GetTestAchatsPosts\Site\communication-multimedia\ordinateurs"
Dim sLastDir = "2638.pc-acer-ne-charge-plus-malgre-un-retour-en-garantie-reviens-charge-mais-ne-charge-toujours-pas-garantie-passee-plus-dintervention-gra"

System.IO.Directory.CreateDirectory(sBaseDir & "\" & sLastDir)

This code generates an error 429-PathTooLongException


I have then tested is creating only last directory

System.IO.Directory.SetCurrentDirectory(sBaseDir)
System.IO.Directory.CreateDirectory(sLastDir)

This code return same error 429-PathTooLongException


I have then tested using a short base directory to see if problem is linked to length of last directory or full directory

System.IO.Directory.SetCurrentDirectory("D:\Documents")
System.IO.Directory.CreateDirectory(sLastDir)

This code work. But directory is created in bad location.


I have then tested is reducing name's length of last directory.

System.IO.Directory.SetCurrentDirectory(sBaseDir)
System.IO.Directory.CreateDirectory(sLastDir.Substring(0, 100))

This code works, but last directory name is reducted !


I have then tested using proposal of Jason in 2 steps

System.IO.Directory.SetCurrentDirectory(sBaseDir)
System.IO.Directory.CreateDirectory("\\?\" & sLastDir)

This code crashes indicating that directory syntax is not correct !


I have then also tested same syntax in adding "." string

System.IO.Directory.SetCurrentDirectory(sBaseDir)
System.IO.Directory.CreateDirectory("\\?\.\" & sLastDir)

This code crashes indicating that directory syntax is not correct !


I have then tested using a simple command prefixing full directory with "\\?"

System.IO.Directory.CreateDirectory("\\?\" & sBaseDir & "\" & sLastDir)

This code work perfectly and it is the best solution for me.

The only inconvenience is that directory must be a full directory.

Warning ! You cannot not mix "\" and "/" characters !

I have written following code

sQuestionDir = "\\?\" & sArticleDir & "/" & sQuestionDir
If Not System.IO.Directory.Exists(sQuestionDir) Then
    System.IO.Directory.CreateDirectory(sQuestionDir)
End If

and program crashes when executing CreateDirectory() function !

The correct code that is working is

sQuestionDir = "\\?\" & sArticleDir & "/" & sQuestionDir
If Not System.IO.Directory.Exists(sQuestionDir) Then
    System.IO.Directory.CreateDirectory(sQuestionDir)
End If

I hope that all these tests can help others to solve their problem.

Related