Click here to Skip to main content
15,879,474 members
Articles / Programming Languages / C#
Article

Vista Directory Links in .NET

Rate me:
Please Sign up or sign in to vote.
4.69/5 (9 votes)
17 Aug 20071 min read 32K   30   2
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:

C#
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

C#
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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer
Austria Austria
Manfred Bittersam has been developing industrial software
in C++ (MFC), C# and VB.NET for several years.
He has gone through a lot of changes, in career as well as in private life.
He says he has learned a lot from his mistakes Wink | ;)
Enjoy his free articles!

Comments and Discussions

 
GeneralException of file acces is denied when get all files from hard disk Pin
Shahaji londhe18-Mar-08 20:12
Shahaji londhe18-Mar-08 20:12 
GeneralRe: Exception of file acces is denied when get all files from hard disk Pin
Manfred Bittersam9-Jul-08 9:26
Manfred Bittersam9-Jul-08 9:26 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.