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.