我正在尝试创建一个NTFS Junction。从cmd行我可以使用junction.exe tool from sysinternals执行此操作。结点的DIR cmd输出如下所示:
Volume in drive C has no label.
Volume Serial Number is C8BC-2EBD
Directory of c:\users\cheeso\Documents
03/22/2009 09:45 PM <JUNCTION> My Music [\??\c:\users\cheeso\Music]
05/11/2007 05:42 PM <DIR> My Received Files
03/22/2009 09:46 PM <JUNCTION> my videos [\??\c:\users\cheeso\Videos]
我在某处读过Junctions是Symbolic Links的子集。
所以我尝试使用CreateSymbolicLink创建一个Junction。当我这样做时,我实际上得到了一个符号链接,而不是一个交汇点。
09/09/2009 11:50 AM <SYMLINKD> newLink [.\]
还有CreateHardLink。那里的文档说交汇点(又名“重新分析点”)是硬链接的子集。但我似乎无法接受这项工作。它完成但没有创建硬链接或联结。
我正在使用.NET / C#,导入如下所示:
[Interop.DllImport("kernel32.dll", EntryPoint="CreateSymbolicLinkW", CharSet=Interop.CharSet.Unicode)]
public static extern int CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, int dwFlags);
[Interop.DllImport("kernel32.dll", EntryPoint="CreateHardLinkW", CharSet=Interop.CharSet.Unicode)]
public static extern bool CreateHardLink(string lpFileName,
string lpExistingFileName,
IntPtr mustBeNull);
我做错了什么? 如何在C#中创建一个Junction?
答案 0 :(得分:13)
看起来你可以,并且有人在CodeProject上创建了一个库,它有许多用C#构建的函数来处理连接点。
http://www.codeproject.com/KB/files/JunctionPointsNet.aspx
看起来他实际上正在使用以下DllImport来完成它:
[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);
答案 1 :(得分:1)
我已经简化/更新了 CreateJunction code by Jeff Brown on CodeProject,例如使用自动编组将结构传递给 DeviceIoControl 而不必手动管理内存。我这样做只是为了创建一个连接点,因为您可以使用 Directory.Delete() 进行删除,并且 .Net 的 GetAttributes 返回它是否具有重新解析点。
我还删除了目标目录存在检查,因为我发现能够创建到不存在或稍后将存在的文件夹的连接很有用。 (不同的驱动器等)
我无法弄清楚的一件事是,添加到结构成员和 nInBufferSize DeviceIoControl 参数的字符串长度中的大小,它们似乎不等于 Marshal.SizeOf 返回值。
我在 VB.Net 中做到了这一点,所以我使用 IC#Code 的 CodeConverter 扩展将其转换为 C#:
using System;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
public class Junction
{
[Flags]
private enum Win32FileAccess : uint
{
GenericRead = 0x80000000U,
GenericWrite = 0x40000000U,
GenericExecute = 0x20000000U,
GenericAll = 0x10000000U
}
[Flags]
private enum Win32FileAttribute : uint
{
AttributeReadOnly = 0x1U,
AttributeHidden = 0x2U,
AttributeSystem = 0x4U,
AttributeDirectory = 0x10U,
AttributeArchive = 0x20U,
AttributeDevice = 0x40U,
AttributeNormal = 0x80U,
AttributeTemporary = 0x100U,
AttributeSparseFile = 0x200U,
AttributeReparsePoint = 0x400U,
AttributeCompressed = 0x800U,
AttributeOffline = 0x1000U,
AttributeNotContentIndexed = 0x2000U,
AttributeEncrypted = 0x4000U,
AttributeIntegrityStream = 0x8000U,
AttributeVirtual = 0x10000U,
AttributeNoScrubData = 0x20000U,
AttributeEA = 0x40000U,
AttributeRecallOnOpen = 0x40000U,
AttributePinned = 0x80000U,
AttributeUnpinned = 0x100000U,
AttributeRecallOnDataAccess = 0x400000U,
FlagOpenNoRecall = 0x100000U,
/// <summary>
/// Normal reparse point processing will not occur; CreateFile will attempt to open the reparse point. When a file is opened, a file handle is returned,
/// whether or not the filter that controls the reparse point is operational.
/// <br />This flag cannot be used with the <see cref="FileMode.Create"/> flag.
/// <br />If the file is not a reparse point, then this flag is ignored.
/// </summary>
FlagOpenReparsePoint = 0x200000U,
FlagSessionAware = 0x800000U,
FlagPosixSemantics = 0x1000000U,
/// <summary>
/// You must set this flag to obtain a handle to a directory. A directory handle can be passed to some functions instead of a file handle.
/// </summary>
FlagBackupSemantics = 0x2000000U,
FlagDeleteOnClose = 0x4000000U,
FlagSequentialScan = 0x8000000U,
FlagRandomAccess = 0x10000000U,
FlagNoBuffering = 0x20000000U,
FlagOverlapped = 0x40000000U,
FlagWriteThrough = 0x80000000U
}
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern SafeFileHandle CreateFile(string lpFileName, Win32FileAccess dwDesiredAccess,
FileShare dwShareMode, IntPtr lpSecurityAttributes, FileMode dwCreationDisposition,
Win32FileAttribute dwFlagsAndAttributes, IntPtr hTemplateFile);
// Because the tag we're using is IO_REPARSE_TAG_MOUNT_POINT, we use the MountPointReparseBuffer struct in the DUMMYUNIONNAME union.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct ReparseDataBuffer
{
/// <summary>Reparse point tag. Must be a Microsoft reparse point tag.</summary>
public uint ReparseTag;
/// <summary>Size, in bytes, of the reparse data in the buffer that <see cref="PathBuffer"/> points to.</summary>
public ushort ReparseDataLength;
/// <summary>Reserved; do not use.</summary>
private ushort Reserved;
/// <summary>Offset, in bytes, of the substitute name string in the <see cref="PathBuffer"/> array.</summary>
public ushort SubstituteNameOffset;
/// <summary>Length, in bytes, of the substitute name string. If this string is null-terminated, <see cref="SubstituteNameLength"/> does not include space for the null character.</summary>
public ushort SubstituteNameLength;
/// <summary>Offset, in bytes, of the print name string in the <see cref="PathBuffer"/> array.</summary>
public ushort PrintNameOffset;
/// <summary>Length, in bytes, of the print name string. If this string is null-terminated, <see cref="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. The substitute name and print name strings can appear in any order.
/// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8184)]
internal string PathBuffer;
// with <MarshalAs(UnmanagedType.ByValArray, SizeConst:=16368)> Public PathBuffer As Byte()
// 16368 is the amount of bytes. since a unicode string uses 2 bytes per character, constrain to 16368/2 = 8184 characters.
}
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern bool DeviceIoControl(SafeFileHandle hDevice, uint dwIoControlCode,
[In] ReparseDataBuffer lpInBuffer, uint nInBufferSize,
IntPtr lpOutBuffer, uint nOutBufferSize,
[Out] uint lpBytesReturned, IntPtr lpOverlapped);
public static void Create(string junctionPath, string targetDir, bool overwrite = false)
{
const uint IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003U;
const uint FSCTL_SET_REPARSE_POINT = 0x900A4U;
// This prefix indicates to NTFS that the path is to be treated as a non-interpreted path in the virtual file system.
const string NonInterpretedPathPrefix = @"\??\";
if (Directory.Exists(junctionPath))
{
if (!overwrite)
throw new IOException("Directory already exists and overwrite parameter is false.");
}
else
{
Directory.CreateDirectory(junctionPath);
}
targetDir = NonInterpretedPathPrefix + Path.GetFullPath(targetDir);
using (var reparsePointHandle = CreateFile(junctionPath, Win32FileAccess.GenericWrite,
FileShare.Read | FileShare.Write | FileShare.Delete, IntPtr.Zero, FileMode.Open,
Win32FileAttribute.FlagBackupSemantics | Win32FileAttribute.FlagOpenReparsePoint, IntPtr.Zero))
{
if (reparsePointHandle.IsInvalid || Marshal.GetLastWin32Error() != 0)
{
throw new IOException("Unable to open reparse point.", new Win32Exception());
}
// unicode string is 2 bytes per character, so *2 to get byte length
ushort byteLength = (ushort)(targetDir.Length * 2);
var reparseDataBuffer = new ReparseDataBuffer()
{
ReparseTag = IO_REPARSE_TAG_MOUNT_POINT,
ReparseDataLength = (ushort)(byteLength + 12u),
SubstituteNameOffset = 0,
SubstituteNameLength = byteLength,
PrintNameOffset = (ushort)(byteLength + 2u),
PrintNameLength = 0,
PathBuffer = targetDir
};
bool result = DeviceIoControl(reparsePointHandle, FSCTL_SET_REPARSE_POINT, reparseDataBuffer, (uint)(byteLength + 20), IntPtr.Zero, 0u, 0u, IntPtr.Zero);
if (!result)
throw new IOException("Unable to create junction point.", new Win32Exception());
}
}
}
和源VB.Net:
Imports System
Imports System.ComponentModel
Imports System.IO
Imports System.Runtime.InteropServices
Imports Microsoft.Win32.SafeHandles
Public Class Junction
<Flags>
Private Enum Win32FileAccess As UInteger
GenericRead = &H80000000UI
GenericWrite = &H40000000
GenericExecute = &H20000000
GenericAll = &H10000000
End Enum
<Flags>
Private Enum Win32FileAttribute As UInteger
AttributeReadOnly = &H1
AttributeHidden = &H2
AttributeSystem = &H4
AttributeDirectory = &H10
AttributeArchive = &H20
AttributeDevice = &H40
AttributeNormal = &H80
AttributeTemporary = &H100
AttributeSparseFile = &H200
AttributeReparsePoint = &H400
AttributeCompressed = &H800
AttributeOffline = &H1000
AttributeNotContentIndexed = &H2000
AttributeEncrypted = &H4000
AttributeIntegrityStream = &H8000
AttributeVirtual = &H10000
AttributeNoScrubData = &H20000
AttributeEA = &H40000
AttributeRecallOnOpen = &H40000
AttributePinned = &H80000
AttributeUnpinned = &H100000
AttributeRecallOnDataAccess = &H400000
FlagOpenNoRecall = &H100000
''' <summary>
''' Normal reparse point processing will not occur; CreateFile will attempt to open the reparse point. When a file is opened, a file handle is returned,
''' whether or not the filter that controls the reparse point is operational.
''' <br />This flag cannot be used with the <see cref="FileMode.Create"/> flag.
''' <br />If the file is not a reparse point, then this flag is ignored.
''' </summary>
FlagOpenReparsePoint = &H200000
FlagSessionAware = &H800000
FlagPosixSemantics = &H1000000
''' <summary>
''' You must set this flag to obtain a handle to a directory. A directory handle can be passed to some functions instead of a file handle.
''' </summary>
FlagBackupSemantics = &H2000000
FlagDeleteOnClose = &H4000000
FlagSequentialScan = &H8000000
FlagRandomAccess = &H10000000
FlagNoBuffering = &H20000000
FlagOverlapped = &H40000000
FlagWriteThrough = &H80000000UI
End Enum
<DllImport("kernel32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
Private Shared Function CreateFile(lpFileName As String, dwDesiredAccess As Win32FileAccess,
dwShareMode As FileShare, lpSecurityAttributes As IntPtr,
dwCreationDisposition As FileMode, dwFlagsAndAttributes As Win32FileAttribute,
hTemplateFile As IntPtr) As SafeFileHandle
End Function
' Because the tag we're using is IO_REPARSE_TAG_MOUNT_POINT, we use the MountPointReparseBuffer struct in the DUMMYUNIONNAME union.
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)>
Private Structure ReparseDataBuffer
''' <summary>Reparse point tag. Must be a Microsoft reparse point tag.</summary>
Public ReparseTag As UInteger
''' <summary>Size, in bytes, of the reparse data in the buffer that <see cref="PathBuffer"/> points to.</summary>
Public ReparseDataLength As UShort
''' <summary>Reserved; do not use.</summary>
Private Reserved As UShort
''' <summary>Offset, in bytes, of the substitute name string in the <see cref="PathBuffer"/> array.</summary>
Public SubstituteNameOffset As UShort
''' <summary>Length, in bytes, of the substitute name string. If this string is null-terminated, <see cref="SubstituteNameLength"/> does not include space for the null character.</summary>
Public SubstituteNameLength As UShort
''' <summary>Offset, in bytes, of the print name string in the <see cref="PathBuffer"/> array.</summary>
Public PrintNameOffset As UShort
''' <summary>Length, in bytes, of the print name string. If this string is null-terminated, <see cref="PrintNameLength"/> does not include space for the null character.</summary>
Public PrintNameLength As UShort
''' <summary>
''' A buffer containing the unicode-encoded path string. The path string contains the substitute name
''' string and print name string. The substitute name and print name strings can appear in any order.
''' </summary>
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=8184)>
Friend PathBuffer As String
' with <MarshalAs(UnmanagedType.ByValArray, SizeConst:=16368)> Public PathBuffer As Byte()
' 16368 is the amount of bytes. since a unicode string uses 2 bytes per character, constrain to 16368/2 = 8184 characters.
End Structure
<DllImport("kernel32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
Private Shared Function DeviceIoControl(hDevice As SafeFileHandle, dwIoControlCode As UInteger,
<[In]> ByRef lpInBuffer As ReparseDataBuffer, nInBufferSize As UInteger,
lpOutBuffer As IntPtr, nOutBufferSize As UInteger,
<Out> ByRef lpBytesReturned As UInteger, lpOverlapped As IntPtr) As Boolean
End Function
Public Shared Sub Create(junctionPath As String, targetDir As String, Optional overwrite As Boolean = False)
Const IO_REPARSE_TAG_MOUNT_POINT As UInteger = &HA0000003UI
Const FSCTL_SET_REPARSE_POINT As UInteger = &H900A4
'This prefix indicates to NTFS that the path is to be treated as a non-interpreted path in the virtual file system.
Const NonInterpretedPathPrefix As String = "\??\"
If Directory.Exists(junctionPath) Then
If Not overwrite Then Throw New IOException("Directory already exists and overwrite parameter is false.")
Else
Directory.CreateDirectory(junctionPath)
End If
targetDir = NonInterpretedPathPrefix & Path.GetFullPath(targetDir)
Using reparsePointHandle As SafeFileHandle = CreateFile(junctionPath, Win32FileAccess.GenericWrite,
FileShare.Read Or FileShare.Write Or FileShare.Delete, IntPtr.Zero, FileMode.Open,
Win32FileAttribute.FlagBackupSemantics Or Win32FileAttribute.FlagOpenReparsePoint, IntPtr.Zero)
If reparsePointHandle.IsInvalid OrElse Marshal.GetLastWin32Error() <> 0 Then
Throw New IOException("Unable to open reparse point.", New Win32Exception())
End If
' unicode string is 2 bytes per character, so *2 to get byte length
Dim byteLength As UShort = CType(targetDir.Length * 2, UShort)
Dim reparseDataBuffer As New ReparseDataBuffer With {
.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT,
.ReparseDataLength = byteLength + 12US,
.SubstituteNameOffset = 0,
.SubstituteNameLength = byteLength,
.PrintNameOffset = byteLength + 2US,
.PrintNameLength = 0,
.PathBuffer = targetDir
}
Dim result As Boolean = DeviceIoControl(reparsePointHandle, FSCTL_SET_REPARSE_POINT, reparseDataBuffer, byteLength + 20US, IntPtr.Zero, 0, 0, IntPtr.Zero)
If Not result Then Throw New IOException("Unable to create junction point.", New Win32Exception())
End Using
End Sub
End Class
答案 2 :(得分:0)
该代码来自http://www.codeproject.com/KB/files/JunctionPointsNet.aspx,以作为无法访问此链接的人员的快捷方式,以防万一原始页面出现故障时进行备份。
请勿投票支持此答案,因为我不是该代码的作者。
感谢原始作者jeff.brown@codeproject。
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32.SafeHandles;
namespace Monitor.Core.Utilities
{
/// <summary>
/// Provides access to NTFS junction points in .Net.
/// </summary>
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 = @"\??\";
[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;
}
[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 (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 = 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);
}
}
}
/// <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);
}
}
}
/// <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 (SafeFileHandle handle = OpenReparsePoint(path, EFileAccess.GenericRead))
{
string 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 (SafeFileHandle handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericRead))
{
string target = InternalGetTarget(handle);
if (target == null)
throw new IOException("Path is not a junction point.");
return target;
}
}
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);
}
}
private static SafeFileHandle OpenReparsePoint(string reparsePoint, EFileAccess accessMode)
{
SafeFileHandle 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()));
}
}
}
答案 3 :(得分:0)
对于使用本文的人https://www.codeproject.com/Articles/15633/Manipulating-NTFS-Junction-Points-in-NET
正如@Walkman 和@SqlRyan 的回答中提到的,我在 Jeff Brown 的文章中添加了一个修复程序,用于尝试为自身创建连接点。
例如:
目录结构为:
应用文件夹
当前(连接点指向 appFolder)
当尝试创建一个指向“当前”的连接点“当前”(带有覆盖标志)时,连接点会被弄乱
修复方法如下:
if (Directory.Exists(junctionPoint))
{
if (!overwrite)
throw new IOException("Directory already exists and overwrite parameter is false.");
var junctionPointFullPath = Path.GetFullPath(junctionPoint);
var targetDirFullPath = Path.GetFullPath(targetDir);
if (junctionPointFullPath.Equals(targetDirFullPath))
throw new RecursiveJunctionPointException("Junction point path and target dir can't be identical");
}