Reparse Point

C# Reparse Point Enumerator

Class for enumerating reparse points under a particular path.
Goes along with the reparse point articles under the Win32 API section.

I'll add more explanation to this soon, though I hope the code is fairly self explanatory.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.InteropServices;

/*
LICENSE

Copyright (c) 2009, Michael Lehman (http://www.machinegods.com)
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of the Michael Lehman, machinegods.com, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

namespace MachineGods.FS
{

//-----------------------------------------------------------------------
public struct ReparsePoint
{
public string Path;
public string Target;
public bool IsReparsePoint;
public Junctions.ReparseTagType TagType;
public int Err;
public string ErrMsg;
}

//-----------------------------------------------------------------------

public class Junctions
{
#region PInvoke Defines

internal const int INVALID_HANDLE_VALUE = -1;
internal const int MAX_PATH = 260;
internal const int MAX_ALTERNATE = 14;

internal const int ERROR_NO_MORE_FILES = 18;

internal const uint GENERIC_READ = 0x80000000;
internal const uint GENERIC_WRITE = 0x40000000;

internal const uint CREATE_NEW = 1;
internal const uint CREATE_ALWAYS = 2;
internal const uint OPEN_EXISTING = 3;

internal const uint FILE_SHARE_NONE = 0x00000000;
internal const uint FILE_SHARE_READ = 0x00000001;
internal const uint FILE_SHARE_WRITE = 0x00000002;
internal const uint FILE_SHARE_DELETE = 0x00000004;

internal const uint FILE_ATTRIBUTE_NORMAL = 0x80;
internal const uint FILE_ATTRIBUTE_DIRECTORY = 0x10;

internal const uint FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000;
internal const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
internal const uint FILE_FLAG_WRITE_THROUGH = 0x08000000;

internal const uint FSCTL_GET_REPARSE_POINT = 0x000900a8;

[StructLayout(LayoutKind.Sequential)]
internal struct FILETIME
{
public uint dwLowDateTime;
public uint dwHighDateTime;
};

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct WIN32_FIND_DATA
{
public FileAttributes dwFileAttributes;
public FILETIME ftCreationTime;
public FILETIME ftLastAccessTime;
public FILETIME ftLastWriteTime;
public int nFileSizeHigh;
public int nFileSizeLow;
public uint dwReserved0;
public uint dwReserved1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)]
public string cFileName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_ALTERNATE)]
public string cAlternate;
}

internal enum FINDEX_SEARCH_OPS
{
NameMatch,
LimitToDirectories,
LimitToDevices
}

internal enum FINDEX_INFO_LEVELS
{
FindExInfoStandard,
FindExInfoMaxInfoLevel
}

[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern IntPtr FindFirstFileExW(string lpFileName, FINDEX_INFO_LEVELS
fInfoLevelId, out WIN32_FIND_DATA lpFindFileData, FINDEX_SEARCH_OPS fSearchOp,
IntPtr lpSearchFilter, uint dwAdditionalFlags);

[DllImport("kernel32", CharSet = CharSet.Unicode)]
internal static extern IntPtr FindFirstFileW(string lpFileName, out WIN32_FIND_DATA lpFindFileData);

[DllImport("kernel32", CharSet = CharSet.Unicode)]
internal static extern bool FindNextFileW(IntPtr hFindFile, out WIN32_FIND_DATA lpFindFileData);

[DllImport("kernel32.dll")]
internal static extern bool FindClose(IntPtr hFindFile);

public enum ReparseTagType : uint
{
IO_REPARSE_TAG_MOUNT_POINT = (0xA0000003),
IO_REPARSE_TAG_HSM = (0xC0000004),
IO_REPARSE_TAG_SIS = (0x80000007),
IO_REPARSE_TAG_DFS = (0x8000000A),
IO_REPARSE_TAG_SYMLINK = (0xA000000C),
IO_REPARSE_TAG_DFSR = (0x80000012),
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct REPARSE_DATA_BUFFER
{
internal uint ReparseTag;
internal ushort ReparseDataLength;
ushort Reserved;
internal ushort SubstituteNameOffset;
internal ushort SubstituteNameLength;
internal ushort PrintNameOffset;
internal ushort PrintNameLength;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)]
internal string PathBuffer;
}

internal const int ReparseHeaderSize = sizeof(uint) + sizeof(ushort) + sizeof(ushort);

[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool DeviceIoControl(
IntPtr hDevice,
uint dwIoControlCode,
IntPtr lpInBuffer,
uint nInBufferSize,
IntPtr lpOutBuffer,
uint nOutBufferSize,
out uint lpBytesReturned,
IntPtr lpOverlapped
);

[DllImport("kernel32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
internal static extern IntPtr CreateFileW(
string lpFileName,
uint dwDesiredAccess,
uint dwShareMode,
IntPtr SecurityAttributes,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
IntPtr hTemplateFile
);

[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool CloseHandle(IntPtr hObject);

#endregion PInvoke Defines

//-----------------------------------------------------------------------

///
/// Returns true if the path is a reparse point of the type IO_REPARSE_TAG_MOUNT_POINT.
///
///
///
public static bool IsMountPointReparsePoint(string path)
{
bool isMountPoint = false;
path = path.Trim();

//skip the "." directories . and ..
if (path.EndsWith(".")) return false;

//make sure the path does not end in a "\" or "\*".
if(path.EndsWith(@"\*")) path = path.Substring(0, path.Length - 2);
if(path.EndsWith(@"\")) path = path.Substring(0, path.Length - 1);

//convert the path to a unicode path to ensure we don't hit length and character restrictions
//make sure the converted path ends with "\*" for using with the FindFirstFile/FindNextFile calls
//The managed classes don't support this, so we have to use Win32 API directly for all our processes
string wPath = path;
if (!wPath.StartsWith(@"\\?\")) wPath = @"\\?\" + wPath;

//the find data holds the attributes of the file/folder found, including if it's a reparse point
WIN32_FIND_DATA findData;

//In theory, the FINDEX_SEARCH_OPS.LimitToDirectories will cause FindFirstFileExW to only return
//directories... Unfortunatly, it's a great theory that doesn't work most of the time.
//So we have to test every object returned to determine if it is indeed a directory
IntPtr pathHndl = FindFirstFileExW(wPath, FINDEX_INFO_LEVELS.FindExInfoStandard, out findData,
FINDEX_SEARCH_OPS.LimitToDirectories, IntPtr.Zero, 0);

if (pathHndl.ToInt32() == INVALID_HANDLE_VALUE) return false;

if ((findData.dwFileAttributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint)
{
if ((findData.dwReserved0 & (uint)ReparseTagType.IO_REPARSE_TAG_MOUNT_POINT) == (uint)ReparseTagType.IO_REPARSE_TAG_MOUNT_POINT)
isMountPoint = true;
}

FindClose(pathHndl);

return isMountPoint;
}

//-----------------------------------------------------------------------

public static List GetReparsePoints(string path, bool recursive)
{
List lRps = new List();

path = path.Trim();

//skip the "." directories . and ..
if (path.EndsWith(".")) return lRps;

//make sure the path does not end in a "\" or "\*".
//We will make sure our converted path right below does however
if (path.EndsWith(@"\*")) path = path.Substring(0, path.Length - 2);
if (path.EndsWith(@"\")) path = path.Substring(0, path.Length - 1);

//convert the path to a unicode path to ensure we don't hit length and character restrictions
//make sure the converted path ends with "\*" for using with the FindFirstFile/FindNextFile calls
//The managed classes don't support this, so we have to use Win32 API directly for all our processes
string wPath = path;
if (!wPath.StartsWith(@"\\?\")) wPath = @"\\?\" + wPath;
wPath = wPath + @"\*";

//the find data holds the attributes of the file/folder found, including if it's a reparse point
WIN32_FIND_DATA findData;

//In theory, the FINDEX_SEARCH_OPS.LimitToDirectories will cause FindFirstFileExW to only return
//directories... Unfortunatly, it's a great theory that doesn't work most of the time.
//So we have to test every object returned to determine if it is indeed a directory
IntPtr pathHndl = FindFirstFileExW(wPath, FINDEX_INFO_LEVELS.FindExInfoStandard, out findData,
FINDEX_SEARCH_OPS.LimitToDirectories, IntPtr.Zero, 0);

if (pathHndl.ToInt32() == INVALID_HANDLE_VALUE) return lRps;

bool isDir = false;
bool isMountPoint = false;

if ((findData.dwFileAttributes & FileAttributes.Directory) == FileAttributes.Directory)
isDir = true;

if ((findData.dwFileAttributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint)
{
if ((findData.dwReserved0 & (uint)ReparseTagType.IO_REPARSE_TAG_MOUNT_POINT) == (uint)ReparseTagType.IO_REPARSE_TAG_MOUNT_POINT)
{
isMountPoint = true;

ReparsePoint rp = new ReparsePoint();
rp.Path = path + "\\" + findData.cFileName;
rp.TagType = ReparseTagType.IO_REPARSE_TAG_MOUNT_POINT;
rp.IsReparsePoint = true;
rp.Err = 0;
rp.ErrMsg = "";
GetTarget(ref rp);
lRps.Add(rp);
}
}

//Recurse
//We only want to recurse down reparse points if we're told to, to avoid looping
if ((isDir && !isMountPoint) || (isMountPoint && recursive))
{
List tlRps = GetReparsePoints(path + "\\" + findData.cFileName, recursive);
if (tlRps != null) lRps.AddRange(tlRps);
}

//Get all the rest of the files/directories
while (true)
{
if (FindNextFileW(pathHndl, out findData))
{
if (pathHndl.ToInt32() == INVALID_HANDLE_VALUE) return lRps;

isDir = false;
isMountPoint = false;

if ((findData.dwFileAttributes & FileAttributes.Directory) == FileAttributes.Directory)
isDir = true;

if ((findData.dwFileAttributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint)
{
if ((findData.dwReserved0 & (uint)ReparseTagType.IO_REPARSE_TAG_MOUNT_POINT) == (uint)ReparseTagType.IO_REPARSE_TAG_MOUNT_POINT)
{
isMountPoint = true;

ReparsePoint rp = new ReparsePoint();
rp.Path = path + "\\" + findData.cFileName;
rp.TagType = ReparseTagType.IO_REPARSE_TAG_MOUNT_POINT;
rp.IsReparsePoint = true;
rp.Err = 0;
rp.ErrMsg = "";
GetTarget(ref rp);
lRps.Add(rp);
}
}

//Recurse
//We only want to recurse down reparse points if we're told to, to avoid looping
if ((isDir && !isMountPoint) || (isMountPoint && recursive))
{
List tlRps = GetReparsePoints(path + "\\" + findData.cFileName, recursive);
if (tlRps != null) lRps.AddRange(tlRps);
}
}
else //findNextFile failed
{
break;
}

}

FindClose(pathHndl);
return lRps;
}

//-----------------------------------------------------------------------
// Gets the target path for a reparse point
private static void GetTarget(ref ReparsePoint rp)
{

string wPath = rp.Path;
if(!wPath.StartsWith(@"\\?\")) wPath = @"\\?\" + wPath;

if(((uint)rp.TagType & (uint)ReparseTagType.IO_REPARSE_TAG_MOUNT_POINT) != (uint)ReparseTagType.IO_REPARSE_TAG_MOUNT_POINT)
{
rp.Target = "";
return;
}

try
{
// We need a handle to the reparse point to pass to DeviceIocontrol down below.
// CreateFile is how we get that handle.
// We want to play nice with others, so we note that we're only reading it, and we want to
// allow others to be able to access (but not delete) it as well while we have the handle (the
// GENERIC_READ and FILE_SHARE_READ | FILE_SHARE_WRITE values)
//
// Biggest key is the flag FILE_FLAG_OPEN_REPARSE_POINT, which tells CreateFile that we want
// a handle to the reparse point itself and not to the target of the reparse point
//
// CreateFile will return INVALID_HANDLE_VALUE with a last error of 5 - Access Denied
// if the FILE_FLAG_BACKUP_SEMANTICS is not specified when opening a directory/reparse point
// It's noted in the directory section of the CreateFile MSDN page
IntPtr pathHndl = CreateFileW(wPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero,
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, IntPtr.Zero);

if (pathHndl.ToInt32() == INVALID_HANDLE_VALUE)
{
rp.Err = Marshal.GetLastWin32Error();
rp.ErrMsg = "Invalid Handle returned by CreateFile";
rp.Target = "";
return;
}

uint lenDataReturned = 0;
REPARSE_DATA_BUFFER rpDataBuf = new REPARSE_DATA_BUFFER();

//Allocate a buffer to get the "user defined data" out of the reaprse point.
//MSDN page on FSCTL_GET_REPARSE_POINT discusses size calculation
IntPtr pMem = Marshal.AllocHGlobal(Marshal.SizeOf(rpDataBuf) + ReparseHeaderSize);

//DeviceIocontrol takes a handle to a file/directory/device etc, that's obtained via CreateFile
// In our case, it's a handle to the directory that's marked a reparse point
//We pass in the FSCTL_GET_REPARSE_POINT flag to getll DeviceIoControl that we want to get data about a reparse point
//There is no In buffer. pMem is our out buffer that will hold the returned REPARSE_DATA_BUFFER
//lenDataReturned is of course how much data was copied into our buffer pMem.
//We're doing a simple call.. no asyncronous stuff going on, so Overlapped is a null pointer
if (!DeviceIoControl(pathHndl, FSCTL_GET_REPARSE_POINT, IntPtr.Zero, 0, pMem,
(uint)Marshal.SizeOf(rpDataBuf) + ReparseHeaderSize, out lenDataReturned, IntPtr.Zero))
{
rp.ErrMsg = "Call to DeviceIoControl failed";
rp.Err = Marshal.GetLastWin32Error();
rp.Target = "";
}
else
{
rpDataBuf = (REPARSE_DATA_BUFFER)Marshal.PtrToStructure(pMem, rpDataBuf.GetType());
rp.Target = rpDataBuf.PathBuffer;
}
Marshal.FreeHGlobal(pMem);
CloseHandle(pathHndl);

}
catch (Exception e)
{
rp.Err = -1;
rp.ErrMsg = e.Message;
rp.Target = "";
}

return;
}
}
}

Understanding Win32 Reparse Points

Reparse points are an often used, but seldom understood object that's available with Windows. At the time of writing, reparse points are only available with NTFS, but they could be made available with future file systems as well.

Reparse points allow us to things such as mounting a disk volume to a folder, or allowing a folder to point to some other folder (useful for managing disk space in some situations, or for creating a contiguous file tree out of non-contiguous directories).

However, using these features without understanding the subtleties of how it works can often lead to mistakes being made that can directly impact your ability maintain the files system, perform backups, manage security, etc..

In this article we'll explore basics of what a reparse point is, and then focus on understanding the reparse points used for Junctions and Volume Mount Points.

A Junction is a reparse point used to point one directory to another.

A Volume Mount Point is a reparse point used to point a directory to a disk volume, so the disk volume is mounted to that directory.

What is a Reparse Point

According to the MSDN reparse point page, a reparse point is as follows:


A file or directory can contain a reparse point, which is a collection of user-defined data. The format of this data is understood by the application which stores the data, and a file system filter, which you install to interpret the data and process the file. When an application sets a reparse point, it stores this data, plus a reparse tag, which uniquely identifies the data it is storing. When the file system opens a file with a reparse point, it attempts to find the file system filter associated with the data format identified by the reparse tag. If a file system filter is found, the filter processes the file as directed by the reparse data. If a file system filter is not found, the file open operation fails

To rephrase this, a reparse point is a special file or folder, which instead of containing the usual data contains two special bits of data:

  • A reparse point tag
  • The user defined data

This data is interpreted by a special program called a “file system filter driver”.

When the Windows OS is walking down the file system (parsing the file system) and encounters a reparse point, it uses the reparse point tag to determine which file system filter to use, and then passes the user defined data to that filter to process it. This is where the term “reparse” comes from. The OS parses the data once, and determines it's a reparse point, then the file system filter re-parses the data to get the “real” data or the desired result.

In the case of a Junction or a Volume Mount Point, the file system filter is provided by Microsoft, and is part of the Windows operating system.

Reparse Point Tags

The reparse point tag is an identifier that is registered with Microsoft that allows the OS to determine which file system filter driver to use to interpret a given reparse point.

The MSDN page for reparse point tags lays out the structure of the tag in a straightforward manner, and we will not repeat it here.

The only two fields in the tag of real interest to us that I'll point out are that the first bit indicates if the tag is an MS tag, and the last 16 bits uniquely identify the tag type.

However, for our purposes, we really only need to consider the tag as a whole. When writing a program that examines reparse points, you will typically compare the entire tag value, not individual fields in the tag.

Microsoft defines these tag types in winnt.h:

  • IO_REPARSE_TAG_DFS
  • IO_REPARSE_TAG_DFSR
  • IO_REPARSE_TAG_HSM
  • IO_REPARSE_TAG_HSM2
  • IO_REPARSE_TAG_MOUNT_POINT
  • IO_REPARSE_TAG_SIS
  • IO_REPARSE_TAG_SYMLINK

The tag IO_REPARSE_TAG_MOUNT_POINT (0xA0000003L) is the tag that identifies both Junctions and Volume Mount points, and is what we will focus on for the rest of the article.

The “User Defined Data”

The data in the reparse point is whatever the filter driver needs to be able to find the desired data.

The data is returned in one of two different structs depending on if the reparse point is a Microsoft reparse point or not (remember that the first bit in the tag will be a 1 if it is a Microsoft reparse point).

If it is a MS tag, the struct will be a REPARSE_DATA_BUFFER, otherwise it will be a REPARSE_GUID_DATA_BUFFER

In the case of IO_REPARSE_TAG_MOUNT_POINT reparse points, the PathBuffer field on the REPARSE_DATA_BUFFER struct identifies the target pointed to by the junction or volume mount point.

If it is a junction, it will have a path such as:

\??\D:\MyTargetFolder

If it is a volume mount point, it will contain the volume ID such as:

\??\Volume{e124abc3-1234-5678-0ab1-e32f863291ab3}\

One curiosity to note is the “\??\” prefix on the substitute name paths. We'll discuss this later on.

Viewing a Reparse Point

When working with a reparse point, you will obviously want to be able to examine the tag and the reparse data. However, you can't just double click the reparse point in Windows Explorer, as that will reparse it and open the target.
There are various ways to view the contents of a reparse point, but most are dependent on the type of reparse point you are looking at.

The simplest way to view the contents of any reparse point to see the type and user defined data is to use fsutil.

The exact command is:

fsutil reparsepoint query path

where path is the path of the reparse point.

For example, if I create a folder c:\testjunctions\source that points to c:\testjunctions\target, and query the reparse point with fsutil, I see the following output:


C:\testjunctions>fsutil reparsepoint query c:\testjunctions\source
Reparse Tag Value : 0xa0000003
Tag value: Microsoft
Tag value: Name Surrogate
Tag value: Mount Point
Substitue Name offset: 0
Substitue Name length: 54
Print Name offset: 56
Print Name Length: 0
Substitute Name: \??\c:\testjunctions\target

Reparse Data Length: 0x00000042
Reparse Data:
0000: 00 00 36 00 38 00 00 00 5c 00 3f 00 3f 00 5c 00 ..6.8...\.?.?.\.
0010: 63 00 3a 00 5c 00 74 00 65 00 73 00 74 00 6a 00 c.:.\.t.e.s.t.j.
0020: 75 00 6e 00 63 00 74 00 69 00 6f 00 6e 00 73 00 u.n.c.t.i.o.n.s.
0030: 5c 00 74 00 61 00 72 00 67 00 65 00 74 00 00 00 \.t.a.r.g.e.t...
0040: 63 00 c.

As you can see, fsutil nicely breaks down the tag fields, and the data in the REPARSE_DATA_BUFFER for us. It's an essential tool to use when testing any of your code for working with reparse points.

(to be continued)

Reparse Points

I have two needs for working with both junctions and volume mount points that don't have good tools out in the public domain that meet the needs.

I should qualify that everything I use needs to be easily integrated into scripts, and be able to handle the full 32,767 characters in a path with all the funky characters that NTFS can use. (Someone tell me why even windows explorer can't do this!?!??)

1. I need to be able to list all volumes on a server and paths they are mounted to
2. I need to be able to easily find every junction on a drive as quickly as possible

I have learned how to do both of these, and will be posting tools with code in the near future.

My articles for reparse points:

Links for Reparse Points

Win32 Reparse Points

One of the common challenges that we server admins face at work is, of course, disk space.
Windows and NFTS have some very powerful features built in, but I sometimes get the impression that Microsoft didn't always think how a customer of theirs would actually be able to use those features.

Win32 reparse points fall into this category. They are currently my favorite MS "feature" to rant about at work...

What is a reparse point you ask? Well, the simplest explanation is that it's a file system object that causes the OS to do a bit of extra processing when it runs across one to go somewhere else to get the desired data - insetad of just reading that object directly like a normal file/folder. It parses the file system object a second time, hence the name.

A reparse point works by sending the contents of the reparse point to a file system filter driver, that does the actual processing and spits the results back.
That file system filter can be a MS driver, or a third party driver.

The most common examples of reparse points are junctions and volume mount points.
Other common uses of reparse points are for archival of files. The archival program moves the file off to archival storage location and creates a reparse point where it was. The OS calls the archival program when it encounters the reparse point to go and fetch the actual data. The OS can do that because the archival program registers a GUID with MS for their reparse point type (tag type), and the OS knows to call their filter based on that GUID.

Fun stuff.. the above was a bit of a simplification, but it should be accurate enough.

Junctions and volume mount points concern the average server admin.
A "junction" is a not-so-technical term that means a reparse point for which the target is another folder.
A "volume mount point" means a reparse point for which the target is a volume.
Interestingly, as far as the reparse point goes, there's no difference between a junction and a volume mount point.

The frustrating thing, is that Microsoft built these powerful features into the OS and then didn't bother to include tools to adequately manage them.

This is really bad, because if you're using these features a lot to get around drive letter limitations, drive space limitations, or to make the file system more intelligently laid out, you can end up having more problems from trying to get the security right, not have loops in the backups, etc...

I should point out there are some programs out there for doing some work with reparse points, but they aren't comprehensive to say the least, and most server admins will quickly realize that they can't do things they'd expect to easily be able to do.. such as, oh.. list all the volumes on a server and where they're mounted to.

So what do we do.. well, we learn the API calls and write the tools ourselves!

Look for some entries in the Win32 and Tools section soon about this...

Win32 API

Topics regarding the Win32 API

Reparse Points

I have two needs for working with both junctions and volume mount points that don't have good tools out in the public domain that meet the needs.

I should qualify that everything I use needs to be easily integrated into scripts, and be able to handle the full 32,767 characters in a path with all the funky characters that NTFS can use. (Someone tell me why even windows explorer can't do this!?!??)

1. I need to be able to list all volumes on a server and paths they are mounted to
2. I need to be able to easily find every junction on a drive as quickly as possible

I have learned how to do both of these, and will be posting tools with code in the near future.

My articles for reparse points

External Links for Reparse Points

Syndicate content