Vista Directory Links in .NET






4.69/5 (9 votes)
Aug 17, 2007
1 min read

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:
- Article and C# implementation of a Junction Point manipulation tool:
Manipulating NTFS Junction Points in .NET by Jeff Brown
History
- 8/17/2007: Initial post to CodeProject