18

我正在尝试创建一个 NTFS 连接。在 cmd 行中,我可以使用sysinternals 的 junction.exe 工具执行此操作。结点的 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]

我在某处读到交界处是符号链接的子集。

所以我尝试使用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# 中创建连接?

4

4 回答 4

15

看起来你可以,有人在 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);
于 2009-09-09T19:49:52.190 回答
3

我已经简化/更新了Jeff Brown 在 CodeProject 上的 CreateJunction 代码,例如使用自动编组将结构传递给 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
于 2021-04-19T15:07:11.670 回答
2

该代码来自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()));
        }
    }
}
于 2019-05-29T12:10:43.350 回答
0

对于使用这篇文章的人https://www.codeproject.com/Articles/15633/Manipulating-NTFS-Junction-Points-in-NET
正如@Walkman 和@SqlRyan 的回答中提到的,我在 Jeff Brown 的文章中添加了一个修复试图为自己创建一个连接点。

例如:
dir 结构是:
appFolder
current(连接点指向 appFolder)
当尝试创建一个指向“current”(带有覆盖标志)的连接点“current”时,连接点会变得混乱

这是修复:

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");
}
于 2021-07-28T07:52:42.163 回答