65.9K
CodeProject is changing. Read more.
Home

Vista Directory Links in .NET

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.69/5 (9 votes)

Aug 17, 2007

1 min read

viewsIcon

32361

How to retrieve the target directory from a directory link in Windows Vista

Introduction

This article is based on the article Manipulating NTFS Junction Points in .NET by Jeff Brown.

In Windows Vista, there are a lot of directory links.
For example: The link C:\Documents and Settings opens the target directory C:\Users.
When I was writing my own directory browser for Windows Vista, I stumbled across these directory links.
How should I retrieve the target directory from such a directory link?
This article describes how.

Background

The NTFS file system saves the link information in a so called "reparse point",
using the REPARSE_GUID_DATA_BUFFER structure (See Platform SDK for details).
To read this "reparse point" out of a directory link, you have to open it as a file,
using the CreateFile function without any special access permissions.
If you have successfully opened the file,
you can read the REPARSE_GUID_DATA_BUFFER structure by calling DeviceIoControl,
and then get the target directory out of it.

Using the Code

Use the ReparsePoint.GetTargetDir method like this:

using System.IO;

String linkDir = "C:\\Documents and Settings";
DirectoryInfo directoryInfo = new DirectoryInfo(linkDir);
// targetDir should be "C:\Users"

String targetDir = ReparsePoint.GetTargetDir(directoryInfo); 
if (targetDir == "")
{
    // The directory is not a link.

}
else
{
    // The directory is a link.

}

Source Code

using System;
using System.Text;
using System.IO;
using System.Runtime.InteropServices; // DllImport

 

namespace MyNameSpace
{
    class ReparsePoint
    {
#region "DllImports, Constants & Structs"
        private const Int32 INVALID_HANDLE_VALUE = -1;
        private const Int32 OPEN_EXISTING = 3;
        private const Int32 FILE_FLAG_OPEN_REPARSE_POINT = 0x200000;
        private const Int32 FILE_FLAG_BACKUP_SEMANTICS = 0x2000000;
        private const Int32 FSCTL_GET_REPARSE_POINT = 0x900A8;
 
        /// <summary>

        /// If the path "REPARSE_GUID_DATA_BUFFER.SubstituteName" 

        /// begins with this prefix,

        /// it is not interpreted by the virtual file system.

        /// </summary>

        private const String NonInterpretedPathPrefix = "\\??\\"; 
 
        [StructLayout(LayoutKind.Sequential)]
        private struct REPARSE_GUID_DATA_BUFFER
        {
            public UInt32 ReparseTag;
            public UInt16 ReparseDataLength;
            public UInt16 Reserved;
            public UInt16 SubstituteNameOffset;
            public UInt16 SubstituteNameLength;
            public UInt16 PrintNameOffset;
            public UInt16 PrintNameLength;
 
            /// <summary>
            /// Contains the SubstituteName and the PrintName.
            /// The SubstituteName is the path of the target directory.
            /// </summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3FF0)]
            public byte[] PathBuffer;
        }
 
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern IntPtr CreateFile(String lpFileName,
                                                Int32 dwDesiredAccess,
                                                Int32 dwShareMode,
                                                IntPtr lpSecurityAttributes,
                                                Int32 dwCreationDisposition,
                                                Int32 dwFlagsAndAttributes,
                                                IntPtr hTemplateFile);
 
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern Int32 CloseHandle(IntPtr hObject);
 
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern Int32 DeviceIoControl( IntPtr hDevice,
                                                     Int32 dwIoControlCode,
                                                     IntPtr lpInBuffer,
                                                     Int32 nInBufferSize,
                                                     IntPtr lpOutBuffer,
                                                     Int32 nOutBufferSize,
                                                     out Int32 lpBytesReturned,
                                                     IntPtr lpOverlapped);
#endregion
 
        /// <summary>
        /// Gets the target directory from a directory link in Windows Vista.
        /// </summary>
        /// <param name="directoryInfo">The directory info of this directory 
        /// link</param>
        /// <returns>the target directory, if it was read, 
        /// otherwise an empty string.</returns>
        public static String GetTargetDir(DirectoryInfo directoryInfo)
        {
            String targetDir = "";
 
            try
            {
                // Is it a directory link?
                if ((directoryInfo.Attributes 
                    & FileAttributes.ReparsePoint) != 0)
                {
                    // Open the directory link:
                    IntPtr hFile = CreateFile(  directoryInfo.FullName,
                                                0,
                                                0,
                                                IntPtr.Zero,
                                                OPEN_EXISTING,
                                                FILE_FLAG_BACKUP_SEMANTICS | 
                                                FILE_FLAG_OPEN_REPARSE_POINT,
                                                IntPtr.Zero);
                    if (hFile.ToInt32() != INVALID_HANDLE_VALUE)
                    {
                        // Allocate a buffer for the reparse point data:
                        Int32 outBufferSize = Marshal.SizeOf
                            (typeof(REPARSE_GUID_DATA_BUFFER));
                        IntPtr outBuffer = Marshal.AllocHGlobal(outBufferSize);
 
                        try
                        {
                            // Read the reparse point data:
                            Int32 bytesReturned;
                            Int32 readOK = DeviceIoControl( hFile,
                                                       FSCTL_GET_REPARSE_POINT,
                                                            IntPtr.Zero,
                                                            0,
                                                            outBuffer,
                                                            outBufferSize,
                                                            out bytesReturned,
                                                            IntPtr.Zero);
                            if (readOK != 0)
                            {
                                // Get the target directory from the reparse 
                                // point data:
                                REPARSE_GUID_DATA_BUFFER rgdBuffer = 
                                    (REPARSE_GUID_DATA_BUFFER)
                                    Marshal.PtrToStructure
                                 (outBuffer, typeof(REPARSE_GUID_DATA_BUFFER));
                                targetDir = Encoding.Unicode.GetString
                                          ( rgdBuffer.PathBuffer,
                                          rgdBuffer.SubstituteNameOffset,
                                          rgdBuffer.SubstituteNameLength);
                                if (targetDir.StartsWith
                                    (NonInterpretedPathPrefix))
                                {
                                    targetDir = targetDir.Substring
                        (NonInterpretedPathPrefix.Length);
                                }
                            }
                        }
                        catch (Exception)
                        {
                        }
 
                        // Free the buffer for the reparse point data:
                        Marshal.FreeHGlobal(outBuffer);
 
                        // Close the directory link:
                        CloseHandle(hFile);
                    }
                }
            }
            catch (Exception)
            {
            }
 
            return targetDir;
        }
    }
}

Points of Interest

It took me a lot of time to find out why Windows Vista always denied me access
to the reparse points of all my directory links.
The reason was that I used the GENERIC_READ constant as dwDesiredAccess
in the CreateFile function, and that would require a higher access permission.
But with a value of 0 (no special access permissions) it works!

References

This project would not have been possible without the assistance of others in the community.
I consulted the following references while implementing this functionality in .NET:

History

  • 8/17/2007: Initial post to CodeProject