public static string GetTarget(string fileName) { SafeFileHandle handle = new SafeFileHandle(CreateFile(fileName, DESIRED_ACCESS, SHARE_MODE, IntPtr.Zero, CREATION_DISPOSITION, FLAGS_AND_ATTRIBUTES, IntPtr.Zero), true); if (Marshal.GetLastWin32Error() != 0) { string message = new Win32Exception(Marshal.GetLastWin32Error()).Message; throw new IOException(message + " (" + fileName + ")", Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error())); } int outBufferSize = Marshal.SizeOf(typeof(REPARSE_DATA_BUFFER)); IntPtr outBuffer = Marshal.AllocHGlobal(outBufferSize); try { int bytesReturned; bool result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_GET_REPARSE_POINT, IntPtr.Zero, 0, outBuffer, outBufferSize, out bytesReturned, IntPtr.Zero); if (!result) { if (Marshal.GetLastWin32Error() == ERROR_NOT_A_REPARSE_POINT) { return(null); } string message = new Win32Exception(Marshal.GetLastWin32Error()).Message; throw new IOException(message + " (" + fileName + ")", Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error())); } REPARSE_DATA_BUFFER reparseDataBuffer = (REPARSE_DATA_BUFFER) Marshal.PtrToStructure(outBuffer, typeof(REPARSE_DATA_BUFFER)); ushort offset = reparseDataBuffer.PrintNameOffset; if (reparseDataBuffer.ReparseTag == IO_REPARSE_TAG_SYMLINK) { // + sizeof(uint) for Flags // which I don't have in REPARSE_DATA_BUFFER offset += sizeof(uint); } else if (reparseDataBuffer.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT) { // throw an error? } string targetDir = Encoding.Unicode.GetString(reparseDataBuffer.PathBuffer, offset, reparseDataBuffer.PrintNameLength); return(targetDir); } finally { handle.Close(); Marshal.FreeHGlobal(outBuffer); } }
/// <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 (SafeFileHandle handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericWrite)) { byte[] targetDirBytes = Encoding.Unicode.GetBytes(NonInterpretedPathPrefix + Path.GetFullPath(targetDir)); REPARSE_DATA_BUFFER 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); int inBufferSize = Marshal.SizeOf(reparseDataBuffer); IntPtr inBuffer = Marshal.AllocHGlobal(inBufferSize); try { Marshal.StructureToPtr(reparseDataBuffer, inBuffer, false); int bytesReturned; bool 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); } } }
// https://github.com/rad1oactive/BetterExplorer/blob/master/Windows%20API%20Code%20Pack%201.1/source/WindowsAPICodePack/Shell/ReparsePoint.cs public static string ParseSymLink(string path) { using var handle = OpenFileForRead(path, false, 0x00200000); if (!handle.IsInvalid) { REPARSE_DATA_BUFFER buffer = new REPARSE_DATA_BUFFER(); if (DeviceIoControl(handle.DangerousGetHandle(), FSCTL_GET_REPARSE_POINT, IntPtr.Zero, 0, out buffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, out _, IntPtr.Zero)) { var subsString = new string(buffer.PathBuffer, ((buffer.SubsNameOffset / 2) + 2), buffer.SubsNameLength / 2); var printString = new string(buffer.PathBuffer, ((buffer.PrintNameOffset / 2) + 2), buffer.PrintNameLength / 2); var normalisedTarget = printString ?? subsString; if (string.IsNullOrEmpty(normalisedTarget)) { normalisedTarget = subsString; if (normalisedTarget.StartsWith(@"\??\", StringComparison.Ordinal)) { normalisedTarget = normalisedTarget.Substring(4); } } if (buffer.ReparseTag == IO_REPARSE_TAG_SYMLINK && (normalisedTarget.Length < 2 || normalisedTarget[1] != ':')) { // Target is relative, get the absolute path normalisedTarget = normalisedTarget.TrimStart(Path.DirectorySeparatorChar); path = path.TrimEnd(Path.DirectorySeparatorChar); normalisedTarget = Path.GetFullPath(Path.Combine(path.Substring(0, path.LastIndexOf(Path.DirectorySeparatorChar)), normalisedTarget)); } return(normalisedTarget); } } return(null); }
public static bool IsSymLink(string path) { using (SafeFileHandle output = CreateFile( path, FileAccess.FILE_READ_ATTRIBUTES, FileShare.Read, IntPtr.Zero, FileMode.Open, FileAttributes.FILE_FLAG_BACKUP_SEMANTICS | FileAttributes.FILE_FLAG_OPEN_REPARSE_POINT, IntPtr.Zero)) { if (output.IsInvalid) { ThrowLastWin32Exception($"Invalid handle for '{path}' as symlink"); } REPARSE_DATA_BUFFER reparseData = new REPARSE_DATA_BUFFER(); reparseData.ReparseDataLength = (4 * sizeof(ushort)) + ReparseDataPathBufferLength; uint bytesReturned; if (!DeviceIoControl(output, FSCTL_GET_REPARSE_POINT, IntPtr.Zero, 0, out reparseData, (uint)Marshal.SizeOf(reparseData), out bytesReturned, IntPtr.Zero)) { ThrowLastWin32Exception($"Failed to place reparse point for '{path}'"); } return(reparseData.ReparseTag == IO_REPARSE_TAG_SYMLINK || reparseData.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT); } }
/// <summary> /// Creates a junction point from the specified directory to the specified target directory. /// </summary> /// <remarks> /// Only works on NTFS. /// </remarks> /// <param name="targetDir">The target directory to create</param> /// <param name="sourceDir">The source directory to alias</param> /// <param name="overwrite">If true overwrites an existing reparse point or empty directory</param> /// <exception cref="System.IO.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 sourceDir, string targetDir, bool overwrite = false, bool makeReadOnly = false) { sourceDir = Path.GetFullPath(sourceDir); if (!Directory.Exists(sourceDir)) { throw new System.IO.IOException($"Source path does not exist or is not a directory."); } if (Directory.Exists(targetDir) && !overwrite) { throw new System.IO.IOException($"Directory '{targetDir}' already exists."); } Directory.CreateDirectory(targetDir); using (SafeFileHandle handle = OpenReparsePoint(targetDir, EFileAccess.GenericWrite)) { byte[] sourceDirBytes = Encoding.Unicode.GetBytes(NonInterpretedPathPrefix + Path.GetFullPath(sourceDir)); REPARSE_DATA_BUFFER reparseDataBuffer = new REPARSE_DATA_BUFFER(); reparseDataBuffer.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; reparseDataBuffer.ReparseDataLength = (ushort)(sourceDirBytes.Length + 12); reparseDataBuffer.SubstituteNameOffset = 0; reparseDataBuffer.SubstituteNameLength = (ushort)sourceDirBytes.Length; reparseDataBuffer.PrintNameOffset = (ushort)(sourceDirBytes.Length + 2); reparseDataBuffer.PrintNameLength = 0; reparseDataBuffer.PathBuffer = new byte[0x3ff0]; Array.Copy(sourceDirBytes, reparseDataBuffer.PathBuffer, sourceDirBytes.Length); int inBufferSize = Marshal.SizeOf(reparseDataBuffer); IntPtr inBuffer = Marshal.AllocHGlobal(inBufferSize); try { Marshal.StructureToPtr(reparseDataBuffer, inBuffer, false); int bytesReturned; bool result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_SET_REPARSE_POINT, inBuffer, sourceDirBytes.Length + 20, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero); if (!result) { ThrowLastWin32Error($"Unable to create junction point '{sourceDir}' -> '{targetDir}'."); } } finally { Marshal.FreeHGlobal(inBuffer); } } if (makeReadOnly) { File.SetAttributes(targetDir, System.IO.FileAttributes.ReadOnly); } }
/// <summary> /// 为目标目录创建一个指向目标目录的目录联接。 /// </summary> /// <remarks> /// 注意,此方法仅在 NTFS 分区上才会生效。 /// </remarks> /// <param name="junctionPoint">目录联接的路径。</param> /// <param name="targetDirectory">要联接的目标目录。</param> /// <param name="overwrite">If true overwrites an existing reparse point or empty directory</param> /// <exception cref="IOException"> /// 如果无法创建目录联接则抛出异常。或者 <paramref name="overwrite" /> 为 false 时,当前目录联接路径已经存在。 /// </exception> public static void Create(string junctionPoint, string targetDirectory, bool overwrite) { targetDirectory = Path.GetFullPath(targetDirectory); if (!Directory.Exists(targetDirectory)) { throw new IOException($"{nameof(targetDirectory)} 指定的目标目录不存在,路径是“{targetDirectory}”。"); } if (Directory.Exists(junctionPoint)) { if (!overwrite) { throw new IOException($"目录联接“{junctionPoint}”已经存在,如果需要替换,请设置 {nameof(overwrite)} 为 true。"); } } else { Directory.CreateDirectory(junctionPoint); } using (SafeFileHandle handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericWrite)) { var targetDirectoryBytes = Encoding.Unicode.GetBytes(NonInterpretedPathPrefix + Path.GetFullPath(targetDirectory)); var reparseDataBuffer = new REPARSE_DATA_BUFFER { ReparseTag = IO_REPARSE_TAG_MOUNT_POINT, ReparseDataLength = (ushort)(targetDirectoryBytes.Length + 12), SubstituteNameOffset = 0, SubstituteNameLength = (ushort)targetDirectoryBytes.Length, PrintNameOffset = (ushort)(targetDirectoryBytes.Length + 2), PrintNameLength = 0, PathBuffer = new byte[0x3ff0] }; Array.Copy(targetDirectoryBytes, reparseDataBuffer.PathBuffer, targetDirectoryBytes.Length); var inBufferSize = Marshal.SizeOf(reparseDataBuffer); var inBuffer = Marshal.AllocHGlobal(inBufferSize); try { Marshal.StructureToPtr(reparseDataBuffer, inBuffer, false); var result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_SET_REPARSE_POINT, inBuffer, targetDirectoryBytes.Length + 20, IntPtr.Zero, 0, out var bytesReturned, IntPtr.Zero); if (!result) { ThrowLastWin32Error("无法创建目录联接。"); } } 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 DeleteJunctionOrSymlink(string junctionPoint) { if (!Directory.Exists(junctionPoint)) { if (File.Exists(junctionPoint)) { throw new IOException("Path is not a junction point."); } return; } var linkManager = ReparsePointFactory.Create(); if (linkManager.GetLinkType(junctionPoint) == LinkType.Symbolic) { Directory.Delete(junctionPoint, false); return; } using (SafeFileHandle handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericWrite)) { REPARSE_DATA_BUFFER reparseDataBuffer = new REPARSE_DATA_BUFFER(); reparseDataBuffer.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; reparseDataBuffer.ReparseDataLength = 0; reparseDataBuffer.PathBuffer = new byte[0x3ff0]; int inBufferSize = Marshal.SizeOf(reparseDataBuffer); IntPtr inBuffer = Marshal.AllocHGlobal(inBufferSize); try { Marshal.StructureToPtr(reparseDataBuffer, inBuffer, false); int bytesReturned; bool 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); } } }
private static extern bool DeviceIoControl( SafeFileHandle hDevice, uint IoControlCode, [In] IntPtr InBuffer, uint nInBufferSize, [Out] out REPARSE_DATA_BUFFER OutBuffer, uint nOutBufferSize, out uint pBytesReturned, [In] IntPtr Overlapped);
/// <summary> /// Creates a junction point to the specified target directory. /// </summary> /// <remarks> /// Only works on NTFS. /// </remarks> /// <param name="junctionPoint">The directory to create</param> /// <param name="destination">The existing directory</param> /// <param name="overwrite">If true overwrites an existing reparse point or empty directory</param> /// <exception cref="System.IO.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 destination, bool overwrite) { if (destination.StartsWith(@"\\?\Volume{")) { throw new ArgumentException("Volume name is not supported!"); // TODO support "mounted folder" } junctionPoint = Path.GetFullPath(junctionPoint); destination = Path.GetFullPath(destination); if (!Directory.Exists(destination)) { throw Helper.IOException($"Destination path does not exist or is not a directory."); } if (Directory.Exists(junctionPoint) && overwrite == false) { throw Helper.IOException($"Directory '{junctionPoint}' already exists."); } // try { Directory.Delete(reparsePoint);} catch (Exception ex) {} Directory.CreateDirectory(junctionPoint); using (var handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericWrite)) { var sourceDirBytes = Encoding.Unicode.GetBytes(NonInterpretedPathPrefix + destination); var reparseDataBuffer = new REPARSE_DATA_BUFFER { ReparseTag = IO_REPARSE_TAG_MOUNT_POINT, ReparseDataLength = (ushort)(sourceDirBytes.Length + 12), SubstituteNameOffset = 0, SubstituteNameLength = (ushort)sourceDirBytes.Length, PrintNameOffset = (ushort)(sourceDirBytes.Length + 2), PrintNameLength = 0, PathBuffer = new byte[0x3ff0] }; Array.Copy(sourceDirBytes, reparseDataBuffer.PathBuffer, sourceDirBytes.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, sourceDirBytes.Length + 20, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero); if (!result) { ThrowLastWin32Error($"Unable to create junction point '{destination}' -> '{junctionPoint}'."); } } finally { Marshal.FreeHGlobal(inBuffer); } } }
private static extern bool DeviceIoControl( IntPtr hDevice, uint dwIoControlCode, IntPtr lpInBuffer, uint nInBufferSize, //IntPtr lpOutBuffer, out REPARSE_DATA_BUFFER outBuffer, uint nOutBufferSize, out uint lpBytesReturned, IntPtr lpOverlapped);
static extern bool DeviceIoControl( IntPtr hDevice, uint dwIoControlCode, IntPtr lpInBuffer, uint nInBufferSize, //IntPtr lpOutBuffer, out REPARSE_DATA_BUFFER outBuffer, uint nOutBufferSize, out uint lpBytesReturned, IntPtr lpOverlapped);
/// <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("SourceDirectory already exists and overwrite parameter is false."); } else { Directory.CreateDirectory(junctionPoint); } using (SafeFileHandle 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); } } }
public static void CreateJunction(string link, string target) { var di = new DirectoryInfo(link); if (di.Exists) { if (di.GetFiles().Length > 0 || di.GetDirectories().Length > 0) { throw new IOException("Directory already exists and is not empty"); } } else { di.Create(); } using (SafeFileHandle handle = OpenReparsePoint(link, EFileAccess.GenericWrite)) { byte[] linkbytes = Encoding.Unicode.GetBytes(VIRTUAL_NTFS_PATH_PREFIX + Path.GetFullPath(target)); REPARSE_DATA_BUFFER buffer = new REPARSE_DATA_BUFFER(); buffer.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; buffer.ReparseDataLength = (ushort)(linkbytes.Length + 12); buffer.SubstituteNameOffset = 0; buffer.SubstituteNameLength = (ushort)linkbytes.Length; buffer.PrintNameOffset = (ushort)(linkbytes.Length + 2); buffer.PrintNameLength = 0; buffer.PathBuffer = new byte[0x3ff0]; Array.Copy(linkbytes, buffer.PathBuffer, linkbytes.Length); int _size = Marshal.SizeOf(buffer); IntPtr _buffer = Marshal.AllocHGlobal(_size); try { Marshal.StructureToPtr(buffer, _buffer, false); int bytesReturned; bool result = NativeMethods.DeviceIoControl(handle.DangerousGetHandle(), FSCTL_SET_REPARSE_POINT, _buffer, linkbytes.Length + 20, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero); if (!result) { throw new IOException("Failed to create junction", Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error())); } } finally { Marshal.FreeHGlobal(_buffer); } } }
private string GetTargetPath() { IntPtr buff = IntPtr.Zero; int dataSize = 0; SafeFileHandle handle = OpenFile(); if (handle.IsInvalid) { return(null); } try { // // Try REPARSE data if (FindReparsePoint(handle, out buff, out dataSize)) { REPARSE_DATA_BUFFER reparseData = Marshal.PtrToStructure <REPARSE_DATA_BUFFER>(buff); if (reparseData.ReparseTag == ReparseTagType.IO_REPARSE_TAG_SYMLINK || reparseData.ReparseTag == ReparseTagType.IO_REPARSE_TAG_MOUNT_POINT) { // // Since the data size isn't fixed, Marshal.PtrToStructure can't return via reparseData.PathBuffer // Therefore we need to marshal it manually from the native data int offset = Marshal.SizeOf <REPARSE_DATA_BUFFER>(); byte[] data = new byte[dataSize]; Marshal.Copy(new IntPtr(buff.ToInt64() + offset), data, 0, data.Length); // // Symlink if (reparseData.ReparseTag == ReparseTagType.IO_REPARSE_TAG_SYMLINK) { return(Encoding.Unicode.GetString(data, reparseData.PrintNameOffset, reparseData.PrintNameLength)); } // // Junction return(Encoding.Unicode.GetString(data, reparseData.PrintNameOffset - Marshal.SizeOf <IntPtr>(), reparseData.PrintNameLength)); } } // // Fallback if REPARSE point can't be used return(GetFinalPathNameByHandle(handle)); } finally { handle.Dispose(); Marshal.FreeHGlobal(buff); } }
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 (SafeFileHandle handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericWrite)) { REPARSE_DATA_BUFFER reparseDataBuffer = new REPARSE_DATA_BUFFER(); reparseDataBuffer.ReparseTag = IoReparseTagMountPoint; reparseDataBuffer.ReparseDataLength = 0; reparseDataBuffer.PathBuffer = new byte[0x3ff0]; int inBufferSize = Marshal.SizeOf(reparseDataBuffer); IntPtr inBuffer = Marshal.AllocHGlobal(inBufferSize); try { Marshal.StructureToPtr(reparseDataBuffer, inBuffer, false); int bytesReturned; bool result = DeviceIoControl(handle.DangerousGetHandle(), FsctlDeleteReparsePoint, inBuffer, 8, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero); if (!result) { ThrowLastWin32Error(Strings.UnableToDeleteJunctionPoint); } } finally { Marshal.FreeHGlobal(inBuffer); } try { Directory.Delete(junctionPoint); } catch (IOException ex) { throw new IOException(Strings.UnableToDeleteJunctionPoint, ex); } } }
private static ReparsePointInfo InternalGetTarget(SafeFileHandle handle) { int outBufferSize = Marshal.SizeOf(typeof(REPARSE_DATA_BUFFER)); IntPtr outBuffer = Marshal.AllocHGlobal(outBufferSize); try { int bytesReturned; bool result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_GET_REPARSE_POINT, IntPtr.Zero, 0, outBuffer, outBufferSize, out bytesReturned, IntPtr.Zero); if (!result) { int error = Marshal.GetLastWin32Error(); if (error == ERROR_NOT_A_REPARSE_POINT) { return(null); } ThrowLastWin32Error("Unable to get information about junction point."); } REPARSE_DATA_BUFFER reparseDataBuffer = (REPARSE_DATA_BUFFER) Marshal.PtrToStructure(outBuffer, typeof(REPARSE_DATA_BUFFER)); var res = new ReparsePointInfo { ReparseTag = (ReparsePointTag)reparseDataBuffer.ReparseTag, IsJunction = reparseDataBuffer.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT, }; if (!res.IsJunction) { return(res); } res.SubstituteName = Encoding.Unicode.GetString(reparseDataBuffer.PathBuffer, reparseDataBuffer.SubstituteNameOffset, reparseDataBuffer.SubstituteNameLength); res.PrintName = Encoding.Unicode.GetString(reparseDataBuffer.PathBuffer, reparseDataBuffer.PrintNameOffset, reparseDataBuffer.PrintNameLength); return(res); } finally { Marshal.FreeHGlobal(outBuffer); } }
private static string InternalGetTarget(SafeFileHandle handle) { int outBufferSize = Marshal.SizeOf(typeof(REPARSE_DATA_BUFFER)); IntPtr outBuffer = Marshal.AllocHGlobal(outBufferSize); try { int bytesReturned; bool result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_GET_REPARSE_POINT, IntPtr.Zero, 0, outBuffer, outBufferSize, out bytesReturned, IntPtr.Zero); if (!result) { int error = Marshal.GetLastWin32Error(); if (error == ERROR_NOT_A_REPARSE_POINT) { return(null); } ThrowLastWin32Error("Unable to get information about junction point."); } REPARSE_DATA_BUFFER reparseDataBuffer = (REPARSE_DATA_BUFFER) Marshal.PtrToStructure(outBuffer, typeof(REPARSE_DATA_BUFFER)); if (reparseDataBuffer.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT) { return(null); } string 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); } }
/// <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="target">The target directory</param> /// <exception cref="IOException">Thrown when the junction point could not be created or when public void Create(string junctionPoint, string target) { target = Path.GetFullPath(target); Directory.CreateDirectory(junctionPoint); using (SafeFileHandle handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericWrite)) { byte[] targetDirBytes = Encoding.Unicode.GetBytes(NonInterpretedPathPrefix + Path.GetFullPath(target)); var reparseDataBuffer = new REPARSE_DATA_BUFFER(); reparseDataBuffer.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; reparseDataBuffer.ReparseDataLength = (ushort)(targetDirBytes.Length + 12); reparseDataBuffer.SubstituteNameOffset = 0; reparseDataBuffer.SubstituteNameLength = (ushort)targetDirBytes.Length; reparseDataBuffer.PrintNameOffset = (ushort)(targetDirBytes.Length + 2); reparseDataBuffer.PrintNameLength = 0; reparseDataBuffer.PathBuffer = new byte[0x3ff0]; Array.Copy(targetDirBytes, reparseDataBuffer.PathBuffer, targetDirBytes.Length); int inBufferSize = Marshal.SizeOf(reparseDataBuffer); IntPtr inBuffer = Marshal.AllocHGlobal(inBufferSize); try { Marshal.StructureToPtr(reparseDataBuffer, inBuffer, false); int bytesReturned; bool 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); } } }
private static string InternalGetTarget(SafeFileHandle handle) { int outBufferSize = Marshal.SizeOf(typeof(REPARSE_DATA_BUFFER)); IntPtr outBuffer = Marshal.AllocHGlobal(outBufferSize); try { int bytesReturned; bool result = DeviceIoControl(handle.DangerousGetHandle(), FsctlGetReparsePoint, IntPtr.Zero, 0, outBuffer, outBufferSize, out bytesReturned, IntPtr.Zero); if (!result) { int error = Marshal.GetLastWin32Error(); if (error == ErrorNotAReparsePoint) { return(null); } ThrowLastWin32Error(Strings.UnableToGetJunctionInformation); } REPARSE_DATA_BUFFER reparseDataBuffer = (REPARSE_DATA_BUFFER) Marshal.PtrToStructure(outBuffer, typeof(REPARSE_DATA_BUFFER)); if (reparseDataBuffer.ReparseTag != IoReparseTagMountPoint) { return(null); } string targetDir = Encoding.Unicode.GetString(reparseDataBuffer.PathBuffer, reparseDataBuffer.SubstituteNameOffset, reparseDataBuffer.SubstituteNameLength); if (targetDir.StartsWith(NonInterpretedPathPrefix, StringComparison.Ordinal)) { targetDir = targetDir.Substring(NonInterpretedPathPrefix.Length); } return(targetDir); } finally { Marshal.FreeHGlobal(outBuffer); } }
private static void CreateJuntionWindows(string sourceDir, string targetDir) { Directory.CreateDirectory(targetDir); using (SafeFileHandle handle = OpenReparsePoint(targetDir, EFileAccess.GenericWrite)) { byte[] sourceDirBytes = Encoding.Unicode.GetBytes(NonInterpretedPathPrefix + Path.GetFullPath(sourceDir)); REPARSE_DATA_BUFFER reparseDataBuffer = new REPARSE_DATA_BUFFER { ReparseTag = IO_REPARSE_TAG_MOUNT_POINT, ReparseDataLength = (ushort)(sourceDirBytes.Length + 12), SubstituteNameOffset = 0, SubstituteNameLength = (ushort)sourceDirBytes.Length, PrintNameOffset = (ushort)(sourceDirBytes.Length + 2), PrintNameLength = 0, PathBuffer = new byte[0x3ff0], }; Array.Copy(sourceDirBytes, reparseDataBuffer.PathBuffer, sourceDirBytes.Length); int inBufferSize = Marshal.SizeOf(reparseDataBuffer); IntPtr inBuffer = Marshal.AllocHGlobal(inBufferSize); try { Marshal.StructureToPtr(reparseDataBuffer, inBuffer, false); int bytesReturned; bool result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_SET_REPARSE_POINT, inBuffer: inBuffer, nInBufferSize: sourceDirBytes.Length + 20, outBuffer: IntPtr.Zero, nOutBufferSize: 0, pBytesReturned: out bytesReturned, lpOverlapped: IntPtr.Zero); if (!result) { ThrowLastWin32Error($"Unable to create junction point \'{sourceDir}\' -> \'{targetDir}\'."); } } finally { Marshal.FreeHGlobal(inBuffer); } } }
public static void Create(string junctionPoint, string substituteName, string printName) { using (SafeFileHandle handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericWrite)) { byte[] substituteNameBytes = Encoding.Unicode.GetBytes(substituteName); byte[] printNameBytes = Encoding.Unicode.GetBytes(printName); REPARSE_DATA_BUFFER reparseDataBuffer = new REPARSE_DATA_BUFFER(); reparseDataBuffer.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; reparseDataBuffer.ReparseDataLength = (ushort)(substituteNameBytes.Length + printNameBytes.Length + 12); reparseDataBuffer.SubstituteNameOffset = 0; reparseDataBuffer.SubstituteNameLength = (ushort)substituteNameBytes.Length; reparseDataBuffer.PrintNameOffset = (ushort)(substituteNameBytes.Length + 2); reparseDataBuffer.PrintNameLength = (ushort)printNameBytes.Length; reparseDataBuffer.PathBuffer = new byte[0x3ff0]; Array.Copy(substituteNameBytes, 0, reparseDataBuffer.PathBuffer, reparseDataBuffer.SubstituteNameOffset, reparseDataBuffer.SubstituteNameLength); Array.Copy(printNameBytes, 0, reparseDataBuffer.PathBuffer, reparseDataBuffer.PrintNameOffset, reparseDataBuffer.PrintNameLength); int inBufferSize = Marshal.SizeOf(reparseDataBuffer); IntPtr inBuffer = Marshal.AllocHGlobal(inBufferSize); try { Marshal.StructureToPtr(reparseDataBuffer, inBuffer, false); int bytesReturned; bool result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_SET_REPARSE_POINT, inBuffer, substituteNameBytes.Length + printNameBytes.Length + 20, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero); if (!result) { ThrowLastWin32Error("Unable to create junction point."); } } finally { Marshal.FreeHGlobal(inBuffer); } } }
public static string SymlinkTargetFromHandle(SafeFileHandle handle) { var rdb = new REPARSE_DATA_BUFFER(); var x = 10; var outSize = new IntPtr(x); if ( !DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, IntPtr.Zero, 0, out rdb, (UInt32)Marshal.SizeOf(rdb), out outSize, IntPtr.Zero)) { throw new Win32Exception(); } if (rdb.ReparseTag != IO_REPARSE_TAG_SYMLINK) { throw new ApplicationException("not a symlink"); } var arrayOffset = rdb.PrintNameOffset / 2; return(rdb.PathBuffer.Substring(arrayOffset, arrayOffset + (rdb.PrintNameLength / 2))); }
/// <summary> /// Takes a full path to a reparse point and finds the target. /// </summary> /// <param name="path">Full path of the reparse point</param> public ReparsePoint(string path) { Debug.Assert(!string.IsNullOrEmpty(path) && path.Length > 2 && path[1] == ':' && path[2] == '\\'); normalisedTarget = ""; tag = TagType.None; bool success; int lastError; // Apparently we need to have backup privileges IntPtr token; TOKEN_PRIVILEGES tokenPrivileges = new TOKEN_PRIVILEGES(); tokenPrivileges.Privileges = new LUID_AND_ATTRIBUTES[1]; success = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, out token); lastError = Marshal.GetLastWin32Error(); if (success) { success = LookupPrivilegeValue(null, SE_BACKUP_NAME, out tokenPrivileges.Privileges[0].Luid); // null for local system lastError = Marshal.GetLastWin32Error(); if (success) { tokenPrivileges.PrivilegeCount = 1; tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; success = AdjustTokenPrivileges(token, false, ref tokenPrivileges, Marshal.SizeOf(tokenPrivileges), IntPtr.Zero, IntPtr.Zero); lastError = Marshal.GetLastWin32Error(); } CloseHandle(token); } if (success) { // Open the file and get its handle IntPtr handle = CreateFile(path, FileAccess.Read, FileShare.None, 0, FileMode.Open, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero); lastError = Marshal.GetLastWin32Error(); if (handle.ToInt32() >= 0) { REPARSE_DATA_BUFFER buffer = new REPARSE_DATA_BUFFER(); // Make up the control code - see CTL_CODE on ntddk.h uint controlCode = (FILE_DEVICE_FILE_SYSTEM << 16) | (FILE_ANY_ACCESS << 14) | (FSCTL_GET_REPARSE_POINT << 2) | METHOD_BUFFERED; uint bytesReturned; success = DeviceIoControl(handle, controlCode, IntPtr.Zero, 0, out buffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, out bytesReturned, IntPtr.Zero); lastError = Marshal.GetLastWin32Error(); if (success) { string subsString = ""; string printString = ""; // Note that according to http://wesnerm.blogs.com/net_undocumented/2006/10/symbolic_links_.html // Symbolic links store relative paths, while junctions use absolute paths // however, they can in fact be either, and may or may not have a leading \. Debug.Assert(buffer.ReparseTag == IO_REPARSE_TAG_SYMLINK || buffer.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT, "Unrecognised reparse tag"); // We only recognise these two if (buffer.ReparseTag == IO_REPARSE_TAG_SYMLINK) { // for some reason symlinks seem to have an extra two characters on the front subsString = new string(buffer.ReparseTarget, (buffer.SubsNameOffset / 2 + 2), buffer.SubsNameLength / 2); printString = new string(buffer.ReparseTarget, (buffer.PrintNameOffset / 2 + 2), buffer.PrintNameLength / 2); tag = TagType.SymbolicLink; } else if (buffer.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) { // This could be a junction or a mounted drive - a mounted drive starts with "\\??\\Volume" subsString = new string(buffer.ReparseTarget, buffer.SubsNameOffset / 2, buffer.SubsNameLength / 2); printString = new string(buffer.ReparseTarget, buffer.PrintNameOffset / 2, buffer.PrintNameLength / 2); tag = subsString.StartsWith(@"\??\Volume") ? TagType.MountPoint : TagType.JunctionPoint; } Debug.Assert(!(string.IsNullOrEmpty(subsString) && string.IsNullOrEmpty(printString)), "Failed to retrieve parse point"); // the printstring should give us what we want if (!string.IsNullOrEmpty(printString)) { normalisedTarget = printString; } else { // if not we can use the substring with a bit of tweaking normalisedTarget = subsString; Debug.Assert(normalisedTarget.Length > 2, "Target string too short"); Debug.Assert( (normalisedTarget.StartsWith(@"\??\") && (normalisedTarget[5] == ':' || normalisedTarget.StartsWith(@"\??\Volume")) || (!normalisedTarget.StartsWith(@"\??\") && normalisedTarget[1] != ':')), "Malformed subsString"); // Junction points must be absolute Debug.Assert( buffer.ReparseTag == IO_REPARSE_TAG_SYMLINK || normalisedTarget.StartsWith(@"\??\Volume") || normalisedTarget[1] == ':', "Relative junction point"); if (normalisedTarget.StartsWith(@"\??\")) { normalisedTarget = normalisedTarget.Substring(4); } } actualTarget = normalisedTarget; // Symlinks can be relative. if (buffer.ReparseTag == IO_REPARSE_TAG_SYMLINK && (normalisedTarget.Length < 2 || normalisedTarget[1] != ':')) { // it's relative, we need to tack it onto the path if (normalisedTarget[0] == '\\') { normalisedTarget = normalisedTarget.Substring(1); } if (path.EndsWith(@"\")) { path = path.Substring(0, path.Length - 1); } // Need to take the symlink name off the path normalisedTarget = path.Substring(0, path.LastIndexOf('\\')) + @"\" + normalisedTarget; // Note that if the symlink target path contains any ..s these are not normalised but returned as is. } // Remove any final slash for consistency if (normalisedTarget.EndsWith("\\")) { normalisedTarget = normalisedTarget.Substring(0, normalisedTarget.Length - 1); } } CloseHandle(handle); } else { success = false; } } }
/// <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 (SafeFileHandle handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericWrite)) { REPARSE_DATA_BUFFER reparseDataBuffer = new REPARSE_DATA_BUFFER(); reparseDataBuffer.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; reparseDataBuffer.ReparseDataLength = 0; reparseDataBuffer.PathBuffer = new byte[0x3ff0]; int inBufferSize = Marshal.SizeOf(reparseDataBuffer); IntPtr inBuffer = Marshal.AllocHGlobal(inBufferSize); try { Marshal.StructureToPtr(reparseDataBuffer, inBuffer, false); int bytesReturned; bool 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); } } }
public static void CreateJunction(string name, string target) { // If the target doesn't include a full path, assume relative to the current directory. if (!Path.IsPathRooted(target)) { target = Path.Combine(Directory.GetCurrentDirectory(), target); } // likewise if the link name omits a path, make it relative to the current directory. if (!Path.IsPathRooted(name)) { name = Path.Combine(Directory.GetCurrentDirectory(), name); } // Check target exists before trying to create a link if (!Directory.Exists(target)) { throw new CreationFailedException("Target directory " + target + " does not exist"); } // Create a directory to populate with our reparse point // If it isn't empty, create file will return 'directory not empty..' Directory.CreateDirectory(name); SafeFileHandle h = CreateFile(name, GENERIC_WRITE, 0, IntPtr.Zero, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, IntPtr.Zero); try { if (h.IsInvalid) { throw new CreationFailedException(Marshal.GetLastWin32Error()); } // Transform the file into a reparse point var targetBytes = Encoding.Unicode.GetBytes(prefix + target); var reparseDataBuffer = new REPARSE_DATA_BUFFER { ReparseTag = IO_REPARSE_TAG_MOUNT_POINT, ReparseDataLength = (ushort)(targetBytes.Length + 12), SubstituteNameOffset = 0, SubstituteNameLength = (ushort)targetBytes.Length, PrintNameOffset = (ushort)(targetBytes.Length + 2), PrintNameLength = 0, PathBuffer = new byte[32768] // This should not be fixed }; Array.Copy(targetBytes, reparseDataBuffer.PathBuffer, targetBytes.Length); var inBufferSize = Marshal.SizeOf(reparseDataBuffer); var inBuffer = Marshal.AllocHGlobal(inBufferSize); Marshal.StructureToPtr(reparseDataBuffer, inBuffer, false); var r = DeviceIoControl(h.DangerousGetHandle(), FSCTL_SET_REPARSE_POINT, inBuffer, targetBytes.Length + 20, IntPtr.Zero, 0, out int bytesReturned, IntPtr.Zero); Marshal.FreeHGlobal(inBuffer); if (!r) { throw new CreationFailedException(Marshal.GetLastWin32Error()); } } finally { h.Close(); } return; }
/// <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 CreateJunctionPointTo(this PathSpec junctionPoint, PathSpec targetDir, bool overwrite) { targetDir = targetDir.ToAbsolute(); if (targetDir.GetPathType() != PathType.Folder) throw new IOException("Target path does not exist or is not a directory."); if (junctionPoint.GetPathType() == PathType.Folder) { if (!overwrite) throw new IOException("Directory already exists and overwrite parameter is false."); } else { junctionPoint.Create(PathType.Folder); } using (SafeFileHandle handle = OpenReparsePoint(junctionPoint.ToString(), EFileAccess.GenericWrite)) { byte[] targetDirBytes = Encoding.Unicode.GetBytes(NonInterpretedPathPrefix + Path.GetFullPath(targetDir.ToString())); REPARSE_DATA_BUFFER reparseDataBuffer = new REPARSE_DATA_BUFFER(); reparseDataBuffer.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; reparseDataBuffer.ReparseDataLength = (ushort)(targetDirBytes.Length + 12); reparseDataBuffer.SubstituteNameOffset = 0; reparseDataBuffer.SubstituteNameLength = (ushort)targetDirBytes.Length; reparseDataBuffer.PrintNameOffset = (ushort)(targetDirBytes.Length + 2); reparseDataBuffer.PrintNameLength = 0; reparseDataBuffer.PathBuffer = new byte[0x3ff0]; Array.Copy(targetDirBytes, reparseDataBuffer.PathBuffer, targetDirBytes.Length); int inBufferSize = Marshal.SizeOf(reparseDataBuffer); IntPtr inBuffer = Marshal.AllocHGlobal(inBufferSize); try { Marshal.StructureToPtr(reparseDataBuffer, inBuffer, false); int bytesReturned; bool 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); } } }
public static string GetSymlinkTarget(string SymlinkPath) { if (Environment.OSVersion.Version.Major <= 5) { throw new InvalidOperationException("Only supported on Vista or later"); } IntPtr hFile = CreateFileNet( SymlinkPath, EFileAccess.GenericRead, EFileShare.Read | EFileShare.Write | EFileShare.Delete, IntPtr.Zero, ECreationDisposition.OpenExisting, EFileAttributes.BackupSemantics | EFileAttributes.OpenReparsePoint, IntPtr.Zero ); try { uint bufferSize = MAXIMUM_REPARSE_DATA_BUFFER_SIZE; IntPtr symData = Marshal.AllocHGlobal((int)bufferSize); try { uint bytesReturned = 0; try { DeviceIoControlNet(hFile, FSCTL_GET_REPARSE_POINT, IntPtr.Zero, 0, symData, bufferSize, out bytesReturned, IntPtr.Zero); } catch (NotASymlinkException) { throw new NotASymlinkException(string.Format("{0} is not a symlink", SymlinkPath)); } REPARSE_DATA_BUFFER symDataHeader = (REPARSE_DATA_BUFFER)Marshal.PtrToStructure(symData, typeof(REPARSE_DATA_BUFFER)); if (symDataHeader.ReparseTag == IO_REPARSE_TAG_SYMLINK) { int subNameLength = ((int)symDataHeader.unnamedUnion.SymbolicLinkReparseBuffer.SubstituteNameLength) / ((int)Marshal.SizeOf(typeof(ushort))); uint subNameOffset = (uint)symDataHeader.unnamedUnion.SymbolicLinkReparseBuffer.SubstituteNameOffset; long pathOffset = symData.ToInt64(); pathOffset += Marshal.OffsetOf(typeof(REPARSE_DATA_BUFFER), "unnamedUnion").ToInt64(); pathOffset += Marshal.SizeOf(typeof(SymbolicLinkReparseBufferStructure)); pathOffset += subNameOffset; IntPtr pathBuffer = new IntPtr(pathOffset); string subPath = Marshal.PtrToStringUni(pathBuffer, subNameLength); if (subPath.StartsWith("\\??\\")) { subPath = subPath.Substring(4); } return(subPath); } else { throw new NotASymlinkException(string.Format("{0} is not a symlink", SymlinkPath)); } } finally { Marshal.FreeHGlobal(symData); } } finally { CloseHandleNet(hFile); } }
//----------------------------------------------------------------------- // 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; }
/// <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="target">The target directory</param> /// <exception cref="IOException">Thrown when the junction point could not be created or when public void Create(string junctionPoint, string target) { target = Path.GetFullPath(target); Directory.CreateDirectory(junctionPoint); using (SafeFileHandle handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericWrite)) { byte[] targetDirBytes = Encoding.Unicode.GetBytes(NonInterpretedPathPrefix + Path.GetFullPath(target)); var reparseDataBuffer = new REPARSE_DATA_BUFFER(); reparseDataBuffer.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; reparseDataBuffer.ReparseDataLength = (ushort) (targetDirBytes.Length + 12); reparseDataBuffer.SubstituteNameOffset = 0; reparseDataBuffer.SubstituteNameLength = (ushort) targetDirBytes.Length; reparseDataBuffer.PrintNameOffset = (ushort) (targetDirBytes.Length + 2); reparseDataBuffer.PrintNameLength = 0; reparseDataBuffer.PathBuffer = new byte[0x3ff0]; Array.Copy(targetDirBytes, reparseDataBuffer.PathBuffer, targetDirBytes.Length); int inBufferSize = Marshal.SizeOf(reparseDataBuffer); IntPtr inBuffer = Marshal.AllocHGlobal(inBufferSize); try { Marshal.StructureToPtr(reparseDataBuffer, inBuffer, false); int bytesReturned; bool 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); } } }
private static extern bool DeviceIoControl(SafeFileHandle hDevice, UInt32 dwIoControlCode, IntPtr lpInBuffer, UInt32 nInBufferSize, out REPARSE_DATA_BUFFER rdb, UInt32 nOutBufferSize, out IntPtr lpBytesReturned, IntPtr lpOverlapped);
/// <summary> /// Takes a full path to a reparse point and finds the target. /// </summary> /// <param name="path">Full path of the reparse point</param> public ReparsePoint(string path) { Debug.Assert(!string.IsNullOrEmpty(path) && path.Length > 2 && path[1] == ':' && path[2] == '\\'); normalisedTarget = ""; tag = TagType.None; bool success; int lastError; // Apparently we need to have backup privileges IntPtr token; TOKEN_PRIVILEGES tokenPrivileges = new TOKEN_PRIVILEGES(); tokenPrivileges.Privileges = new LUID_AND_ATTRIBUTES[1]; success = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, out token); lastError = Marshal.GetLastWin32Error(); if (success) { success = LookupPrivilegeValue(null, SE_BACKUP_NAME, out tokenPrivileges.Privileges[0].Luid); // null for local system lastError = Marshal.GetLastWin32Error(); if (success) { tokenPrivileges.PrivilegeCount = 1; tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; success = AdjustTokenPrivileges(token, false, ref tokenPrivileges, Marshal.SizeOf(tokenPrivileges), IntPtr.Zero, IntPtr.Zero); lastError = Marshal.GetLastWin32Error(); } CloseHandle(token); } if (success) { // Open the file and get its handle IntPtr handle = CreateFile(path, FileAccess.Read, FileShare.None, 0, FileMode.Open, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero); lastError = Marshal.GetLastWin32Error(); if (handle.ToInt32() >= 0) { REPARSE_DATA_BUFFER buffer = new REPARSE_DATA_BUFFER(); // Make up the control code - see CTL_CODE on ntddk.h uint controlCode = (FILE_DEVICE_FILE_SYSTEM << 16) | (FILE_ANY_ACCESS << 14) | (FSCTL_GET_REPARSE_POINT << 2) | METHOD_BUFFERED; uint bytesReturned; success = DeviceIoControl(handle, controlCode, IntPtr.Zero, 0, out buffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, out bytesReturned, IntPtr.Zero); lastError = Marshal.GetLastWin32Error(); if (success) { string subsString = ""; string printString = ""; // Note that according to http://wesnerm.blogs.com/net_undocumented/2006/10/symbolic_links_.html // Symbolic links store relative paths, while junctions use absolute paths // however, they can in fact be either, and may or may not have a leading \. Debug.Assert(buffer.ReparseTag == IO_REPARSE_TAG_SYMLINK || buffer.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT, "Unrecognised reparse tag"); // We only recognise these two if (buffer.ReparseTag == IO_REPARSE_TAG_SYMLINK) { // for some reason symlinks seem to have an extra two characters on the front subsString = new string(buffer.ReparseTarget, (buffer.SubsNameOffset / 2 + 2), buffer.SubsNameLength / 2); printString = new string(buffer.ReparseTarget, (buffer.PrintNameOffset / 2 + 2), buffer.PrintNameLength / 2); tag = TagType.SymbolicLink; } else if (buffer.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) { // This could be a junction or a mounted drive - a mounted drive starts with "\\??\\Volume" subsString = new string(buffer.ReparseTarget, buffer.SubsNameOffset / 2, buffer.SubsNameLength / 2); printString = new string(buffer.ReparseTarget, buffer.PrintNameOffset / 2, buffer.PrintNameLength / 2); tag = subsString.StartsWith(@"\??\Volume") ? TagType.MountPoint : TagType.JunctionPoint; } Debug.Assert(!(string.IsNullOrEmpty(subsString) && string.IsNullOrEmpty(printString)), "Failed to retrieve parse point"); // the printstring should give us what we want if (!string.IsNullOrEmpty(printString)) { normalisedTarget = printString; } else { // if not we can use the substring with a bit of tweaking normalisedTarget = subsString; Debug.Assert(normalisedTarget.Length > 2, "Target string too short"); Debug.Assert( (normalisedTarget.StartsWith(@"\??\") && (normalisedTarget[5] == ':' || normalisedTarget.StartsWith(@"\??\Volume")) || (!normalisedTarget.StartsWith(@"\??\") && normalisedTarget[1] != ':')), "Malformed subsString"); // Junction points must be absolute Debug.Assert( buffer.ReparseTag == IO_REPARSE_TAG_SYMLINK || normalisedTarget.StartsWith(@"\??\Volume") || normalisedTarget[1] == ':', "Relative junction point"); if (normalisedTarget.StartsWith(@"\??\")) { normalisedTarget = normalisedTarget.Substring(4); } } actualTarget = normalisedTarget; // Symlinks can be relative. if (buffer.ReparseTag == IO_REPARSE_TAG_SYMLINK && (normalisedTarget.Length < 2 || normalisedTarget[1] != ':')) { // it's relative, we need to tack it onto the path if (normalisedTarget[0] == '\\') { normalisedTarget = normalisedTarget.Substring(1); } if (path.EndsWith(@"\")) { path = path.Substring(0, path.Length - 1); } // Need to take the symlink name off the path normalisedTarget = path.Substring(0, path.LastIndexOf('\\')) + @"\" + normalisedTarget; // Note that if the symlink target path contains any ..s these are not normalised but returned as is. } // Remove any final slash for consistency if (normalisedTarget.EndsWith("\\")) { normalisedTarget = normalisedTarget.Substring(0, normalisedTarget.Length - 1); } } CloseHandle(handle); } else { success = false; } } }
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 (SafeFileHandle handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericWrite)) { byte[] targetDirBytes = Encoding.Unicode.GetBytes(NonInterpretedPathPrefix + Path.GetFullPath(targetDir)); REPARSE_DATA_BUFFER reparseDataBuffer = new REPARSE_DATA_BUFFER(); reparseDataBuffer.ReparseTag = IoReparseTagMountPoint; reparseDataBuffer.ReparseDataLength = (ushort)(targetDirBytes.Length + 12); reparseDataBuffer.SubstituteNameOffset = 0; reparseDataBuffer.SubstituteNameLength = (ushort)targetDirBytes.Length; reparseDataBuffer.PrintNameOffset = (ushort)(targetDirBytes.Length + 2); reparseDataBuffer.PrintNameLength = 0; reparseDataBuffer.PathBuffer = new byte[0x3ff0]; Array.Copy(targetDirBytes, reparseDataBuffer.PathBuffer, targetDirBytes.Length); int inBufferSize = Marshal.SizeOf(reparseDataBuffer); IntPtr inBuffer = Marshal.AllocHGlobal(inBufferSize); try { Marshal.StructureToPtr(reparseDataBuffer, inBuffer, false); int bytesReturned; bool result = DeviceIoControl( handle.DangerousGetHandle(), FsctlSetReparsePoint, inBuffer, targetDirBytes.Length + 20, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero); if (!result) { ThrowLastWin32Error(Strings.UnableToCreateJunctionPoint); } } finally { Marshal.FreeHGlobal(inBuffer); } } }
//----------------------------------------------------------------------- // 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; }
/// <summary> /// Takes a full path to a reparse point and finds the target. /// </summary> /// <param name="path">Full path of the reparse point</param> public ReparsePoint(string path) { //Debug.Assert(!string.IsNullOrEmpty(path) && path.Length > 2 && path[1] == ':' && path[2] == '\\'); normalisedTarget = string.Empty; tag = TagType.None; bool success; int lastError; // Apparently we need to have backup privileges /*IntPtr token; * TOKEN_PRIVILEGES tokenPrivileges = new TOKEN_PRIVILEGES(); * tokenPrivileges.Privileges = new LUID_AND_ATTRIBUTES[1]; * success = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, out token); * lastError = Marshal.GetLastWin32Error(); * if (success) * { * success = LookupPrivilegeValue(null, SE_BACKUP_NAME, out tokenPrivileges.Privileges[0].Luid); // null for local system * lastError = Marshal.GetLastWin32Error(); * if (success) * { * tokenPrivileges.PrivilegeCount = 1; * tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; * success = AdjustTokenPrivileges(token, false, ref tokenPrivileges, Marshal.SizeOf(tokenPrivileges), IntPtr.Zero, IntPtr.Zero); * lastError = Marshal.GetLastWin32Error(); * } * CloseHandle(token); * }*/ //if (success) { // Open the file and get its handle using (SafeFileHandle handle = CreateFile(path, EFileAccess.GenericRead, FileShare.ReadWrite | FileShare.Delete, IntPtr.Zero, FileMode.Open, FileAttributesEx.OpenReparsePoint | FileAttributesEx.BackupSemantics, IntPtr.Zero)) { lastError = Marshal.GetLastWin32Error(); if (!handle.IsInvalid) { int outBufferSize = Marshal.SizeOf(typeof(REPARSE_DATA_BUFFER)); IntPtr outBuffer = Marshal.AllocHGlobal(outBufferSize); //REPARSE_DATA_BUFFER buffer = new REPARSE_DATA_BUFFER(); // Make up the control code - see CTL_CODE on ntddk.h //uint controlCode = (FILE_DEVICE_FILE_SYSTEM << 16) | (FILE_ANY_ACCESS << 14) | (FSCTL_GET_REPARSE_POINT << 2) | METHOD_BUFFERED; success = DeviceIoControl(handle.DangerousGetHandle(), EIOControlCode.FsctlGetReparsePoint, IntPtr.Zero, 0, outBuffer, outBufferSize, out _, IntPtr.Zero); //lastError = Marshal.GetLastWin32Error(); if (success) { REPARSE_DATA_BUFFER buffer = (REPARSE_DATA_BUFFER) Marshal.PtrToStructure(outBuffer, typeof(REPARSE_DATA_BUFFER)); string subsString = string.Empty; string printString = string.Empty; // Note that according to http://wesnerm.blogs.com/net_undocumented/2006/10/symbolic_links_.html // Symbolic links store relative paths, while junctions use absolute paths // however, they can in fact be either, and may or may not have a leading \. /*Debug.Assert(buffer.ReparseTag == IO_REPARSE_TAG_SYMLINK || buffer.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT, * "Unrecognised reparse tag"); // We only recognise these two */ if (buffer.ReparseTag == IO_REPARSE_TAG_SYMLINK) { // for some reason symlinks seem to have an extra two characters on the front subsString = new string(buffer.ReparseTarget, (buffer.SubsNameOffset / 2 + 2), buffer.SubsNameLength / 2); printString = new string(buffer.ReparseTarget, (buffer.PrintNameOffset / 2 + 2), buffer.PrintNameLength / 2); tag = TagType.SymbolicLink; } else if (buffer.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) { // This could be a junction or a mounted drive - a mounted drive starts with "\\??\\Volume" subsString = new string(buffer.ReparseTarget, buffer.SubsNameOffset / 2, buffer.SubsNameLength / 2); printString = new string(buffer.ReparseTarget, buffer.PrintNameOffset / 2, buffer.PrintNameLength / 2); tag = subsString.StartsWith(@"\??\Volume", StringComparison.InvariantCultureIgnoreCase) ? TagType.MountPoint : TagType.JunctionPoint; } //Debug.Assert(!(string.IsNullOrEmpty(subsString) && string.IsNullOrEmpty(printString)), "Failed to retrieve parse point"); // the printstring should give us what we want if (printString.Length > 0) { normalisedTarget = printString; } else { // if not we can use the substring with a bit of tweaking normalisedTarget = subsString; /*Debug.Assert(normalisedTarget.Length > 2, "Target string too short"); * Debug.Assert( * (normalisedTarget.StartsWith(@"\??\") && (normalisedTarget[5] == ':' || normalisedTarget.StartsWith(@"\??\Volume")) || * (!normalisedTarget.StartsWith(@"\??\") && normalisedTarget[1] != ':')), * "Malformed subsString"); * // Junction points must be absolute * Debug.Assert( * buffer.ReparseTag == IO_REPARSE_TAG_SYMLINK || * normalisedTarget.StartsWith(@"\??\Volume") || * normalisedTarget[1] == ':', * "Relative junction point");*/ if (normalisedTarget.StartsWith(@"\??\", StringComparison.Ordinal)) { normalisedTarget = normalisedTarget.Substring(4); } } actualTarget = normalisedTarget; // Symlinks can be relative. if (buffer.ReparseTag == IO_REPARSE_TAG_SYMLINK && (normalisedTarget.Length < 2 || normalisedTarget[1] != ':')) { // it's relative, we need to tack it onto the path if (normalisedTarget[0] == '\\') { normalisedTarget = normalisedTarget.Substring(1); } if (path.EndsWith(@"\", StringComparison.Ordinal)) { path = path.Substring(0, path.Length - 1); } // Need to take the symlink name off the path normalisedTarget = path.Substring(0, path.LastIndexOf('\\')) + @"\" + normalisedTarget; // Note that if the symlink target path contains any ..s these are not normalised but returned as is. } // Remove any final slash for consistency if (normalisedTarget.EndsWith("\\", StringComparison.Ordinal)) { normalisedTarget = normalisedTarget.Substring(0, normalisedTarget.Length - 1); } } Marshal.FreeHGlobal(outBuffer); handle.Close(); } /*else if (lastError == 5) * { * success = false; * }*/ else { throw new Win32Exception(lastError); } } } }