-
-
Save LGM-AdrianHum/260bc9ab3c4cd49bc8617a2abe84ca74 to your computer and use it in GitHub Desktop.
| // File: RollThroughLibrary/CreateMaps/JunctionPoint.cs | |
| // User: Adrian Hum/ | |
| // | |
| // Created: 2017-11-19 2:46 PM | |
| // Modified: 2017-11-19 6:10 PM | |
| using System; | |
| using System.Diagnostics.CodeAnalysis; | |
| using System.IO; | |
| using System.Runtime.InteropServices; | |
| using System.Text; | |
| using Microsoft.Win32.SafeHandles; | |
| namespace CreateMaps | |
| { | |
| /// <summary> | |
| /// Provides access to NTFS junction points in .Net. | |
| /// </summary> | |
| [SuppressMessage("ReSharper", "InconsistentNaming")] | |
| [SuppressMessage("ReSharper", "UnusedMember.Local")] | |
| [SuppressMessage("ReSharper", "MemberCanBePrivate.Local")] | |
| [SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Local")] | |
| public static class JunctionPoint | |
| { | |
| /// <summary> | |
| /// The file or directory is not a reparse point. | |
| /// </summary> | |
| private const int ERROR_NOT_A_REPARSE_POINT = 4390; | |
| /// <summary> | |
| /// The reparse point attribute cannot be set because it conflicts with an existing attribute. | |
| /// </summary> | |
| private const int ERROR_REPARSE_ATTRIBUTE_CONFLICT = 4391; | |
| /// <summary> | |
| /// The data present in the reparse point buffer is invalid. | |
| /// </summary> | |
| private const int ERROR_INVALID_REPARSE_DATA = 4392; | |
| /// <summary> | |
| /// The tag present in the reparse point buffer is invalid. | |
| /// </summary> | |
| private const int ERROR_REPARSE_TAG_INVALID = 4393; | |
| /// <summary> | |
| /// There is a mismatch between the tag specified in the request and the tag present in the reparse point. | |
| /// </summary> | |
| private const int ERROR_REPARSE_TAG_MISMATCH = 4394; | |
| /// <summary> | |
| /// Command to set the reparse point data block. | |
| /// </summary> | |
| private const int FSCTL_SET_REPARSE_POINT = 0x000900A4; | |
| /// <summary> | |
| /// Command to get the reparse point data block. | |
| /// </summary> | |
| private const int FSCTL_GET_REPARSE_POINT = 0x000900A8; | |
| /// <summary> | |
| /// Command to delete the reparse point data base. | |
| /// </summary> | |
| private const int FSCTL_DELETE_REPARSE_POINT = 0x000900AC; | |
| /// <summary> | |
| /// Reparse point tag used to identify mount points and junction points. | |
| /// </summary> | |
| private const uint IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003; | |
| /// <summary> | |
| /// This prefix indicates to NTFS that the path is to be treated as a non-interpreted | |
| /// path in the virtual file system. | |
| /// </summary> | |
| private const string NonInterpretedPathPrefix = @"\??\"; | |
| [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] | |
| private static extern bool DeviceIoControl(IntPtr hDevice, uint dwIoControlCode, | |
| IntPtr InBuffer, int nInBufferSize, | |
| IntPtr OutBuffer, int nOutBufferSize, | |
| out int pBytesReturned, IntPtr lpOverlapped); | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| private static extern IntPtr CreateFile( | |
| string lpFileName, | |
| EFileAccess dwDesiredAccess, | |
| EFileShare dwShareMode, | |
| IntPtr lpSecurityAttributes, | |
| ECreationDisposition dwCreationDisposition, | |
| EFileAttributes dwFlagsAndAttributes, | |
| IntPtr hTemplateFile); | |
| /// <summary> | |
| /// Creates a junction point from the specified directory to the specified target directory. | |
| /// </summary> | |
| /// <remarks> | |
| /// Only works on NTFS. | |
| /// </remarks> | |
| /// <param name="junctionPoint">The junction point path</param> | |
| /// <param name="targetDir">The target directory</param> | |
| /// <param name="overwrite">If true overwrites an existing reparse point or empty directory</param> | |
| /// <exception cref="IOException"> | |
| /// Thrown when the junction point could not be created or when | |
| /// an existing directory was found and <paramref name="overwrite" /> if false | |
| /// </exception> | |
| public static void Create(string junctionPoint, string targetDir, bool overwrite) | |
| { | |
| targetDir = Path.GetFullPath(targetDir); | |
| if (!Directory.Exists(targetDir)) | |
| throw new IOException("Target path does not exist or is not a directory."); | |
| if (Directory.Exists(junctionPoint)) | |
| { | |
| if (!overwrite) | |
| throw new IOException("Directory already exists and overwrite parameter is false."); | |
| } | |
| else | |
| { | |
| Directory.CreateDirectory(junctionPoint); | |
| } | |
| using (var handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericWrite)) | |
| { | |
| var targetDirBytes = Encoding.Unicode.GetBytes(NonInterpretedPathPrefix + Path.GetFullPath(targetDir)); | |
| var reparseDataBuffer = | |
| new REPARSE_DATA_BUFFER | |
| { | |
| ReparseTag = IO_REPARSE_TAG_MOUNT_POINT, | |
| ReparseDataLength = (ushort) (targetDirBytes.Length + 12), | |
| SubstituteNameOffset = 0, | |
| SubstituteNameLength = (ushort) targetDirBytes.Length, | |
| PrintNameOffset = (ushort) (targetDirBytes.Length + 2), | |
| PrintNameLength = 0, | |
| PathBuffer = new byte[0x3ff0] | |
| }; | |
| Array.Copy(targetDirBytes, reparseDataBuffer.PathBuffer, targetDirBytes.Length); | |
| var inBufferSize = Marshal.SizeOf(reparseDataBuffer); | |
| var inBuffer = Marshal.AllocHGlobal(inBufferSize); | |
| try | |
| { | |
| Marshal.StructureToPtr(reparseDataBuffer, inBuffer, false); | |
| int bytesReturned; | |
| var result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_SET_REPARSE_POINT, | |
| inBuffer, targetDirBytes.Length + 20, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero); | |
| if (!result) | |
| ThrowLastWin32Error("Unable to create junction point."); | |
| } | |
| finally | |
| { | |
| Marshal.FreeHGlobal(inBuffer); | |
| } | |
| } | |
| } | |
| /// <summary> | |
| /// Deletes a junction point at the specified source directory along with the directory itself. | |
| /// Does nothing if the junction point does not exist. | |
| /// </summary> | |
| /// <remarks> | |
| /// Only works on NTFS. | |
| /// </remarks> | |
| /// <param name="junctionPoint">The junction point path</param> | |
| public static void Delete(string junctionPoint) | |
| { | |
| if (!Directory.Exists(junctionPoint)) | |
| { | |
| if (File.Exists(junctionPoint)) | |
| throw new IOException("Path is not a junction point."); | |
| return; | |
| } | |
| using (var handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericWrite)) | |
| { | |
| var reparseDataBuffer = new REPARSE_DATA_BUFFER | |
| { | |
| ReparseTag = IO_REPARSE_TAG_MOUNT_POINT, | |
| ReparseDataLength = 0, | |
| PathBuffer = new byte[0x3ff0] | |
| }; | |
| var inBufferSize = Marshal.SizeOf(reparseDataBuffer); | |
| var inBuffer = Marshal.AllocHGlobal(inBufferSize); | |
| try | |
| { | |
| Marshal.StructureToPtr(reparseDataBuffer, inBuffer, false); | |
| int bytesReturned; | |
| var result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_DELETE_REPARSE_POINT, | |
| inBuffer, 8, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero); | |
| if (!result) | |
| ThrowLastWin32Error("Unable to delete junction point."); | |
| } | |
| finally | |
| { | |
| Marshal.FreeHGlobal(inBuffer); | |
| } | |
| try | |
| { | |
| Directory.Delete(junctionPoint); | |
| } | |
| catch (IOException ex) | |
| { | |
| throw new IOException("Unable to delete junction point.", ex); | |
| } | |
| } | |
| } | |
| /// <summary> | |
| /// Determines whether the specified path exists and refers to a junction point. | |
| /// </summary> | |
| /// <param name="path">The junction point path</param> | |
| /// <returns>True if the specified path represents a junction point</returns> | |
| /// <exception cref="IOException"> | |
| /// Thrown if the specified path is invalid | |
| /// or some other error occurs | |
| /// </exception> | |
| public static bool Exists(string path) | |
| { | |
| if (!Directory.Exists(path)) | |
| return false; | |
| using (var handle = OpenReparsePoint(path, EFileAccess.GenericRead)) | |
| { | |
| var target = InternalGetTarget(handle); | |
| return target != null; | |
| } | |
| } | |
| /// <summary> | |
| /// Gets the target of the specified junction point. | |
| /// </summary> | |
| /// <remarks> | |
| /// Only works on NTFS. | |
| /// </remarks> | |
| /// <param name="junctionPoint">The junction point path</param> | |
| /// <returns>The target of the junction point</returns> | |
| /// <exception cref="IOException"> | |
| /// Thrown when the specified path does not | |
| /// exist, is invalid, is not a junction point, or some other error occurs | |
| /// </exception> | |
| public static string GetTarget(string junctionPoint) | |
| { | |
| using (var handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericRead)) | |
| { | |
| var target = InternalGetTarget(handle); | |
| if (target == null) | |
| throw new IOException("Path is not a junction point."); | |
| return target; | |
| } | |
| } | |
| private static string InternalGetTarget(SafeFileHandle handle) | |
| { | |
| var outBufferSize = Marshal.SizeOf(typeof(REPARSE_DATA_BUFFER)); | |
| var outBuffer = Marshal.AllocHGlobal(outBufferSize); | |
| try | |
| { | |
| int bytesReturned; | |
| var result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_GET_REPARSE_POINT, | |
| IntPtr.Zero, 0, outBuffer, outBufferSize, out bytesReturned, IntPtr.Zero); | |
| if (!result) | |
| { | |
| var error = Marshal.GetLastWin32Error(); | |
| if (error == ERROR_NOT_A_REPARSE_POINT) | |
| return null; | |
| ThrowLastWin32Error("Unable to get information about junction point."); | |
| } | |
| var reparseDataBuffer = (REPARSE_DATA_BUFFER) | |
| Marshal.PtrToStructure(outBuffer, typeof(REPARSE_DATA_BUFFER)); | |
| if (reparseDataBuffer.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT) | |
| return null; | |
| var targetDir = Encoding.Unicode.GetString(reparseDataBuffer.PathBuffer, | |
| reparseDataBuffer.SubstituteNameOffset, reparseDataBuffer.SubstituteNameLength); | |
| if (targetDir.StartsWith(NonInterpretedPathPrefix)) | |
| targetDir = targetDir.Substring(NonInterpretedPathPrefix.Length); | |
| return targetDir; | |
| } | |
| finally | |
| { | |
| Marshal.FreeHGlobal(outBuffer); | |
| } | |
| } | |
| private static SafeFileHandle OpenReparsePoint(string reparsePoint, EFileAccess accessMode) | |
| { | |
| var reparsePointHandle = new SafeFileHandle(CreateFile(reparsePoint, accessMode, | |
| EFileShare.Read | EFileShare.Write | EFileShare.Delete, | |
| IntPtr.Zero, ECreationDisposition.OpenExisting, | |
| EFileAttributes.BackupSemantics | EFileAttributes.OpenReparsePoint, IntPtr.Zero), true); | |
| if (Marshal.GetLastWin32Error() != 0) | |
| ThrowLastWin32Error("Unable to open reparse point."); | |
| return reparsePointHandle; | |
| } | |
| private static void ThrowLastWin32Error(string message) | |
| { | |
| throw new IOException(message, Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error())); | |
| } | |
| [Flags] | |
| private enum EFileAccess : uint | |
| { | |
| GenericRead = 0x80000000, | |
| GenericWrite = 0x40000000, | |
| GenericExecute = 0x20000000, | |
| GenericAll = 0x10000000 | |
| } | |
| [Flags] | |
| private enum EFileShare : uint | |
| { | |
| None = 0x00000000, | |
| Read = 0x00000001, | |
| Write = 0x00000002, | |
| Delete = 0x00000004 | |
| } | |
| private enum ECreationDisposition : uint | |
| { | |
| New = 1, | |
| CreateAlways = 2, | |
| OpenExisting = 3, | |
| OpenAlways = 4, | |
| TruncateExisting = 5 | |
| } | |
| [Flags] | |
| private 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)] | |
| private struct REPARSE_DATA_BUFFER | |
| { | |
| /// <summary> | |
| /// Reparse point tag. Must be a Microsoft reparse point tag. | |
| /// </summary> | |
| public uint ReparseTag; | |
| /// <summary> | |
| /// Size, in bytes, of the data after the Reserved member. This can be calculated by: | |
| /// (4 * sizeof(ushort)) + SubstituteNameLength + PrintNameLength + | |
| /// (namesAreNullTerminated ? 2 * sizeof(char) : 0); | |
| /// </summary> | |
| public ushort ReparseDataLength; | |
| /// <summary> | |
| /// Reserved; do not use. | |
| /// </summary> | |
| public ushort Reserved; | |
| /// <summary> | |
| /// Offset, in bytes, of the substitute name string in the PathBuffer array. | |
| /// </summary> | |
| public ushort SubstituteNameOffset; | |
| /// <summary> | |
| /// Length, in bytes, of the substitute name string. If this string is null-terminated, | |
| /// SubstituteNameLength does not include space for the null character. | |
| /// </summary> | |
| public ushort SubstituteNameLength; | |
| /// <summary> | |
| /// Offset, in bytes, of the print name string in the PathBuffer array. | |
| /// </summary> | |
| public ushort PrintNameOffset; | |
| /// <summary> | |
| /// Length, in bytes, of the print name string. If this string is null-terminated, | |
| /// PrintNameLength does not include space for the null character. | |
| /// </summary> | |
| public ushort PrintNameLength; | |
| /// <summary> | |
| /// A buffer containing the unicode-encoded path string. The path string contains | |
| /// the substitute name string and print name string. | |
| /// </summary> | |
| [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3FF0)] public byte[] PathBuffer; | |
| } | |
| } | |
| } |
I too would care to know.
Personally? I don't really care it's grown out of stuff from MSDN, bits on the net...
So buy me a virtual free beer one day...
Adrian
Hello, @LGM-AdrianHum . I tried to use your code to solve a problem that i got. The problem is when i try to delete or modify permission of a directory, that seems a junction point. Without using your code i got access denied error, and using your code i get the same error. the directory catch the condition "if (Marshal.GetLastWin32Error() != 0)" in OpenReparsePoint method. Can you help me with this, please?
Code looks similar, if not identical, to one from Jeff Brown's JunctionPoint.cs on Code Project: https://www.codeproject.com/script/Articles/ViewDownloads.aspx?aid=15633
Thank you. Good reference.
Hi Adrian Hum,
What is the license of this code?