Friday, 1 July 2011

Handling Longpath problem in Windows Application .Net C#

The Windows OS does not allow a path to be more than 248 characters long. So what if your directories and file names are too long and they are nested. Then eventually you'll not be able to access/create the file /directory in it. How can they be created and how they can be accessed programmatically?

Recently I was facing that problem; I need to search a deep level of nested directories. I have tried System.IO.Directory.GetFiles() method and then suddenly it throws an error that "Couldn't read the part of path" or "Invalid path". Then somehow I managed to find the solution and now I am sharing it with you guys.

Windows provides the API to handle the longer files and directories paths. Using this API we can access the path longer than 248 chars. To use the path which is longer we need to add a prefix "\\?\" in the path. This informs the API that it's a long path and the Windows API will support up to 32767 bytes for long paths with this.

So you need to specify it using the CharSet.Unicode as an attribute of an internal static extern type method of System library function.

For Example:
 
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]   internal static extern IntPtr FindFirstFile(string lpFileName, out       LongPathHandler.InteropVar.WIN32_FIND_DATA lpFindFileData);
Here is the code to access the files and directories:
using System;using System.Collections.Generic;using System.IO;using System.Runtime.InteropServices;using Microsoft.Win32.SafeHandles;namespace LongPathHandler
{
    public class
CustomDir    {
        [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
        internal static extern IntPtr FindFirstFile(string lpFileName,
out             LongPathHandler.InteropVar.WIN32_FIND_DATA lpFindFileData);
        [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
        internal static extern bool FindNextFile(IntPtr hFindFile,
out             LongPathHandler.InteropVar.WIN32_FIND_DATA lpFindFileData);
        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static extern bool FindClose(IntPtr hFindFile);
        ///
<summary>        /// Return all the dirctories and files        /// Assumed dirName passed in is already prefixed with file:///?/        /// </summary>        /// <param name="dirName">Directory that need to be searched</param>        /// <returns></returns>        public static List<string> FindFilesAndDirs(string dirName)
        {
            List<string> results = new List<string>();
            LongPathHandler.InteropVar.WIN32_FIND_DATA findData;
            IntPtr findHandle = FindFirstFile(dirName + @"\*", out findData);
            if (findHandle != LongPathHandler.InteropVar.INVALID_HANDLE_VALUE)
            {
                bool found;
               
do                {
                    string currentFileName = findData.cFileName;
                   
// if this is a directory, find its contents                    if (((int)findData.dwFileAttributes & LongPathHandler.InteropVar.FILE_ATTRIBUTE_DIRECTORY) != 0)
                    {
                        if (currentFileName != "." && currentFileName != "..")
                        {
                            List<string> childResults = FindFilesAndDirs(Path.Combine(dirName, currentFileName));
                            
// add children and self to results                            results.AddRange(childResults);
                            results.Add(Path.Combine(dirName, currentFileName));                        }
                    else
// it's a file; add it to the results                    {
                        results.Add(Path.Combine(dirName, currentFileName));
                    }
                   
// find next                    found = FindNextFile(findHandle, out findData);
                }
                while (found);
            }
           
// close the find handle            FindClose(findHandle);
            return results;
         }
    }
}
Another class is that used in the above code is:
using System;using System.Collections.Generic;
using
System.Linq;using System.Text;using System.IO;using System.Runtime.InteropServices;namespace LongPathHandler
{
    class
InteropVar    {
        internal static IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
        internal static int FILE_ATTRIBUTE_DIRECTORY = 0x00000010;
        internal const int MAX_PATH = 260;
        [StructLayout(LayoutKind.Sequential)]
        internal struct
FILETIME        {
            internal uint dwLowDateTime;
            internal uint dwHighDateTime;
        };
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        internal struct
WIN32_FIND_DATA        {
            internal FileAttributes dwFileAttributes;
            internal FILETIME ftCreationTime;
            internal FILETIME ftLastAccessTime;
            internal FILETIME ftLastWriteTime;
            internal int nFileSizeHigh;
            internal int nFileSizeLow;  
            internal int dwReserved0;
            internal int dwReserved1;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)]
            internal string cFileName;
            
// not using this         
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
            internal string cAlternate;
        }
        [Flags]
        public
enum EFileAccess :
uint
       
{
            GenericRead = 0x80000000,
            GenericWrite = 0x40000000,     
            GenericExecute = 0x20000000,
            GenericAll = 0x10000000,
        }
        [Flags]
        public enum EFileShare :
uint        {
            None = 0x00000000,
            Read = 0x00000001,
            Write = 0x00000002,   
            Delete = 0x00000004,
        }
        public enum ECreationDisposition :
uint
      
 {
            New = 1,
            CreateAlways = 2,
            OpenExisting = 3,          
            OpenAlways = 4,        
            TruncateExisting = 5,
        }
        [Flags]
        public enum EFileAttributes :
uint        {
            Readonly = 0x00000001,          
            Hidden = 0x00000002,
            System = 0x00000004,
            Directory = 0x00000010,
            Archive = 0x00000020,
            Device = 0x00000040,
            Normal = 0x00000080,
            Temporary = 0x00000100,
            SparseFile = 0x00000200,
            ReparsePoint = 0x00000400,
            Compressed = 0x00000800,
            Offline = 0x00001000,
            NotContentIndexed = 0x00002000,     
            Encrypted = 0x00004000,
            Write_Through = 0x80000000,
            Overlapped = 0x40000000,
            NoBuffering = 0x20000000,
            RandomAccess = 0x10000000,
            SequentialScan = 0x08000000,
            DeleteOnClose = 0x04000000,
            BackupSemantics = 0x02000000,
            PosixSemantics = 0x01000000,
            OpenReparsePoint = 0x00200000,
            OpenNoRecall = 0x00100000,
            FirstPipeInstance = 0x00080000
        }
        [StructLayout(LayoutKind.Sequential)]
        public struct
SECURITY_ATTRIBUTES        {
            public int nLength;
            public IntPtr lpSecurityDescriptor;
            public int bInheritHandle;
        }
    }
}
Now just create these classes in your project and call the method FindFilesAndDirs() of the CustomDir class.

For Example:
 
List<String> ls = CustomDir.FindFilesAndDirs(@"\\?\" + rootPath);
Note: The result will contain this prefix "\\?\" so you need to explicitly remove it if you want to get rid of an error "Invalid symbols in path". Now search the deepest path in your Windows pc using C# a program. Microsoft has released a support library code for handling the long paths but it's for .Net Version 4.0. But this code can be used with most all versions of the Framework.

Thanks.

14 comments:

  1. great.......
    Thid is one of the best

    ReplyDelete
  2. can you send me the link to Microsoft library for use in .NET version 4.0

    ReplyDelete
  3. Re: "Microsoft has released a support library code for handling the long paths but it's for .Net Version 4.0."
    By any chance you have the link? I couldn't find it myself...:-(
    TIA

    ReplyDelete
  4. Uhm, sorry, I found it on CodePlex:
    http://bcl.codeplex.com/releases/view/42783

    ReplyDelete
  5. Sorry for late reply yes.. code plex have this open source project for .net 4.0. its good you find it yourself and thanks for sharing it here.. :)

    ReplyDelete
  6. I just want to Process.Star(@"\?\\"+), but i get an error.
    How do i do that?

    ReplyDelete
  7. What error are you getting? Give details i'll try to help

    ReplyDelete
  8. I get an Exception of FileNotFound. System could not find: "\\\\?\\S:\\longfilepath\\longfilename.txt"

    I think i'm not grasping the concept well enough. I made a console aplication that opens a file with a very long path. The path is passed as an arg.

    I'm not really sure what i have to do. File handling is a bit of an unexplored area to me...

    ReplyDelete
  9. Here's the code i came up with:
    [code]
    using System;
    using System.Diagnostics;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.IO;
    using System.ComponentModel;
    using System.Runtime.InteropServices;
    using Microsoft.Win32.SafeHandles;

    namespace ConsoleApplication1
    {
    class Program
    {
    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    static extern bool abreFicheiro(string lpFileName, bool bFailIfExists);

    static void Main(string[] args) {
    string caminho = fixPathForLong(@"S:\longfilepath\longfilename.txt");
    abreFicheiro(caminho);
    }

    public static bool abreFicheiro(string caminho) {
    Process.Start(caminho);
    if (!abreFicheiro(caminho, false))
    {
    throw new Win32Exception();
    }

    return true;
    }

    private static string fixPathForLong(String path)
    {
    if (!path.StartsWith(@"\\?\"))
    path = @"\\?\" + path;
    return path;
    }
    }
    }
    [/code]

    ReplyDelete
  10. your code looks ok.. I guess you need to make sure that path you are using to pointing that particular file is correct because the error is not for Long path its actually can not find the file specified. Make sure filename and path is correct.

    ReplyDelete
  11. You're absolutely right!
    Don't know how i missed such a trivial thing...

    Thankyou for your invaluable help!

    ReplyDelete
  12. I am a complete beginner. Can you please explain "enum EFileAttributes" and the constant values being assigned to it. ALso, what is meant by "[Flags]" notation above it. Thank you.

    ReplyDelete
  13. Hi,

    For a good solution, take a look at AlphaFS.dll
    Site: https://alphafs.codeplex.com/

    ReplyDelete