How to create a hardlink in C#?

后端 未结 5 1106
无人共我
无人共我 2020-12-14 07:31

How to create a hardlink in C#? Any code snippet, please?

5条回答
  •  情书的邮戳
    2020-12-14 08:19

    If you only want to create a hardlink to a File the answer

    using System.Runtime.InteropServices;
    ...
    [DllImport("Kernel32.dll", CharSet = CharSet.Unicode )]
      static extern bool CreateHardLink(
          string lpFileName,
          string lpExistingFileName,
          IntPtr lpSecurityAttributes
      );
    

    seems to be best.

    But if you want to create a hardlink of a Folder then it's a little bit more tricky.
    Here I found some code to create/delete Folder hardlinks (junctions). I tested the code and it worked properly.
    I post the code here, just in case the site goes offline:

    JunctionPoint.cs

    // File: RollThroughLibrary/CreateMaps/JunctionPoint.cs
    // User: Adrian Hum/
    // 
    // Created:  2017-11-19 2:46 PM
    // Modified: 2017-11-19 6:10 PM
    
    using System;
    using System.Diagnostics.CodeAnalysis;
    using System.IO;
    using System.Runtime.InteropServices;
    using System.Text;
    using Microsoft.Win32.SafeHandles;
    
    namespace CreateMaps
    {
        /// 
        ///     Provides access to NTFS junction points in .Net.
        /// 
        [SuppressMessage("ReSharper", "InconsistentNaming")]
        [SuppressMessage("ReSharper", "UnusedMember.Local")]
        [SuppressMessage("ReSharper", "MemberCanBePrivate.Local")]
        [SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Local")]
        public static class JunctionPoint
        {
            /// 
            ///     The file or directory is not a reparse point.
            /// 
            private const int ERROR_NOT_A_REPARSE_POINT = 4390;
    
            /// 
            ///     The reparse point attribute cannot be set because it conflicts with an existing attribute.
            /// 
            private const int ERROR_REPARSE_ATTRIBUTE_CONFLICT = 4391;
    
            /// 
            ///     The data present in the reparse point buffer is invalid.
            /// 
            private const int ERROR_INVALID_REPARSE_DATA = 4392;
    
            /// 
            ///     The tag present in the reparse point buffer is invalid.
            /// 
            private const int ERROR_REPARSE_TAG_INVALID = 4393;
    
            /// 
            ///     There is a mismatch between the tag specified in the request and the tag present in the reparse point.
            /// 
            private const int ERROR_REPARSE_TAG_MISMATCH = 4394;
    
            /// 
            ///     Command to set the reparse point data block.
            /// 
            private const int FSCTL_SET_REPARSE_POINT = 0x000900A4;
    
            /// 
            ///     Command to get the reparse point data block.
            /// 
            private const int FSCTL_GET_REPARSE_POINT = 0x000900A8;
    
            /// 
            ///     Command to delete the reparse point data base.
            /// 
            private const int FSCTL_DELETE_REPARSE_POINT = 0x000900AC;
    
            /// 
            ///     Reparse point tag used to identify mount points and junction points.
            /// 
            private const uint IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003;
    
            /// 
            ///     This prefix indicates to NTFS that the path is to be treated as a non-interpreted
            ///     path in the virtual file system.
            /// 
            private const string NonInterpretedPathPrefix = @"\??\";
    
            [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            private static extern bool DeviceIoControl(IntPtr hDevice, uint dwIoControlCode,
                IntPtr InBuffer, int nInBufferSize,
                IntPtr OutBuffer, int nOutBufferSize,
                out int pBytesReturned, IntPtr lpOverlapped);
    
            [DllImport("kernel32.dll", SetLastError = true)]
            private static extern IntPtr CreateFile(
                string lpFileName,
                EFileAccess dwDesiredAccess,
                EFileShare dwShareMode,
                IntPtr lpSecurityAttributes,
                ECreationDisposition dwCreationDisposition,
                EFileAttributes dwFlagsAndAttributes,
                IntPtr hTemplateFile);
    
            /// 
            ///     Creates a junction point from the specified directory to the specified target directory.
            /// 
            /// 
            ///     Only works on NTFS.
            /// 
            /// The junction point path
            /// The target directory
            /// If true overwrites an existing reparse point or empty directory
            /// 
            ///     Thrown when the junction point could not be created or when
            ///     an existing directory was found and  if 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 (var handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericWrite))
                {
                    var targetDirBytes = Encoding.Unicode.GetBytes(NonInterpretedPathPrefix + Path.GetFullPath(targetDir));
    
                    var reparseDataBuffer =
                        new REPARSE_DATA_BUFFER
                        {
                            ReparseTag = IO_REPARSE_TAG_MOUNT_POINT,
                            ReparseDataLength = (ushort)(targetDirBytes.Length + 12),
                            SubstituteNameOffset = 0,
                            SubstituteNameLength = (ushort)targetDirBytes.Length,
                            PrintNameOffset = (ushort)(targetDirBytes.Length + 2),
                            PrintNameLength = 0,
                            PathBuffer = new byte[0x3ff0]
                        };
    
                    Array.Copy(targetDirBytes, reparseDataBuffer.PathBuffer, targetDirBytes.Length);
    
                    var inBufferSize = Marshal.SizeOf(reparseDataBuffer);
                    var inBuffer = Marshal.AllocHGlobal(inBufferSize);
    
                    try
                    {
                        Marshal.StructureToPtr(reparseDataBuffer, inBuffer, false);
    
                        int bytesReturned;
                        var result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_SET_REPARSE_POINT,
                            inBuffer, targetDirBytes.Length + 20, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero);
    
                        if (!result)
                            ThrowLastWin32Error("Unable to create junction point.");
                    }
                    finally
                    {
                        Marshal.FreeHGlobal(inBuffer);
                    }
                }
            }
    
            /// 
            ///     Deletes a junction point at the specified source directory along with the directory itself.
            ///     Does nothing if the junction point does not exist.
            /// 
            /// 
            ///     Only works on NTFS.
            /// 
            /// The junction point path
            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);
                    }
                }
            }
    
            /// 
            ///     Determines whether the specified path exists and refers to a junction point.
            /// 
            /// The junction point path
            /// True if the specified path represents a junction point
            /// 
            ///     Thrown if the specified path is invalid
            ///     or some other error occurs
            /// 
            public static bool Exists(string path)
            {
                if (!Directory.Exists(path))
                    return false;
    
                using (var handle = OpenReparsePoint(path, EFileAccess.GenericRead))
                {
                    var target = InternalGetTarget(handle);
                    return target != null;
                }
            }
    
            /// 
            ///     Gets the target of the specified junction point.
            /// 
            /// 
            ///     Only works on NTFS.
            /// 
            /// The junction point path
            /// The target of the junction point
            /// 
            ///     Thrown when the specified path does not
            ///     exist, is invalid, is not a junction point, or some other error occurs
            /// 
            public static string GetTarget(string junctionPoint)
            {
                using (var handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericRead))
                {
                    var target = InternalGetTarget(handle);
                    if (target == null)
                        throw new IOException("Path is not a junction point.");
    
                    return target;
                }
            }
    
            private static string InternalGetTarget(SafeFileHandle handle)
            {
                var outBufferSize = Marshal.SizeOf(typeof(REPARSE_DATA_BUFFER));
                var outBuffer = Marshal.AllocHGlobal(outBufferSize);
    
                try
                {
                    int bytesReturned;
                    var result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_GET_REPARSE_POINT,
                        IntPtr.Zero, 0, outBuffer, outBufferSize, out bytesReturned, IntPtr.Zero);
    
                    if (!result)
                    {
                        var error = Marshal.GetLastWin32Error();
                        if (error == ERROR_NOT_A_REPARSE_POINT)
                            return null;
    
                        ThrowLastWin32Error("Unable to get information about junction point.");
                    }
    
                    var reparseDataBuffer = (REPARSE_DATA_BUFFER)
                        Marshal.PtrToStructure(outBuffer, typeof(REPARSE_DATA_BUFFER));
    
                    if (reparseDataBuffer.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT)
                        return null;
    
                    var targetDir = Encoding.Unicode.GetString(reparseDataBuffer.PathBuffer,
                        reparseDataBuffer.SubstituteNameOffset, reparseDataBuffer.SubstituteNameLength);
    
                    if (targetDir.StartsWith(NonInterpretedPathPrefix))
                        targetDir = targetDir.Substring(NonInterpretedPathPrefix.Length);
    
                    return targetDir;
                }
                finally
                {
                    Marshal.FreeHGlobal(outBuffer);
                }
            }
    
            private static SafeFileHandle OpenReparsePoint(string reparsePoint, EFileAccess accessMode)
            {
                var reparsePointHandle = new SafeFileHandle(CreateFile(reparsePoint, accessMode,
                    EFileShare.Read | EFileShare.Write | EFileShare.Delete,
                    IntPtr.Zero, ECreationDisposition.OpenExisting,
                    EFileAttributes.BackupSemantics | EFileAttributes.OpenReparsePoint, IntPtr.Zero), true);
    
                if (Marshal.GetLastWin32Error() != 0)
                    ThrowLastWin32Error("Unable to open reparse point.");
    
                return reparsePointHandle;
            }
    
            private static void ThrowLastWin32Error(string message)
            {
                throw new IOException(message, Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()));
            }
    
            [Flags]
            private enum EFileAccess : uint
            {
                GenericRead = 0x80000000,
                GenericWrite = 0x40000000,
                GenericExecute = 0x20000000,
                GenericAll = 0x10000000
            }
    
            [Flags]
            private enum EFileShare : uint
            {
                None = 0x00000000,
                Read = 0x00000001,
                Write = 0x00000002,
                Delete = 0x00000004
            }
    
            private enum ECreationDisposition : uint
            {
                New = 1,
                CreateAlways = 2,
                OpenExisting = 3,
                OpenAlways = 4,
                TruncateExisting = 5
            }
    
            [Flags]
            private enum EFileAttributes : uint
            {
                Readonly = 0x00000001,
                Hidden = 0x00000002,
                System = 0x00000004,
                Directory = 0x00000010,
                Archive = 0x00000020,
                Device = 0x00000040,
                Normal = 0x00000080,
                Temporary = 0x00000100,
                SparseFile = 0x00000200,
                ReparsePoint = 0x00000400,
                Compressed = 0x00000800,
                Offline = 0x00001000,
                NotContentIndexed = 0x00002000,
                Encrypted = 0x00004000,
                Write_Through = 0x80000000,
                Overlapped = 0x40000000,
                NoBuffering = 0x20000000,
                RandomAccess = 0x10000000,
                SequentialScan = 0x08000000,
                DeleteOnClose = 0x04000000,
                BackupSemantics = 0x02000000,
                PosixSemantics = 0x01000000,
                OpenReparsePoint = 0x00200000,
                OpenNoRecall = 0x00100000,
                FirstPipeInstance = 0x00080000
            }
    
            [StructLayout(LayoutKind.Sequential)]
            private struct REPARSE_DATA_BUFFER
            {
                /// 
                ///     Reparse point tag. Must be a Microsoft reparse point tag.
                /// 
                public uint ReparseTag;
    
                /// 
                ///     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);
                /// 
                public ushort ReparseDataLength;
    
                /// 
                ///     Reserved; do not use.
                /// 
                public ushort Reserved;
    
                /// 
                ///     Offset, in bytes, of the substitute name string in the PathBuffer array.
                /// 
                public ushort SubstituteNameOffset;
    
                /// 
                ///     Length, in bytes, of the substitute name string. If this string is null-terminated,
                ///     SubstituteNameLength does not include space for the null character.
                /// 
                public ushort SubstituteNameLength;
    
                /// 
                ///     Offset, in bytes, of the print name string in the PathBuffer array.
                /// 
                public ushort PrintNameOffset;
    
                /// 
                ///     Length, in bytes, of the print name string. If this string is null-terminated,
                ///     PrintNameLength does not include space for the null character.
                /// 
                public ushort PrintNameLength;
    
                /// 
                ///     A buffer containing the unicode-encoded path string. The path string contains
                ///     the substitute name string and print name string.
                /// 
                [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3FF0)]
                public byte[] PathBuffer;
            }
        }
    }
    

    Usage:

    using CreateMaps;
    ...
    JunctionPoint.Create(@"C:\Temp\HardlinkFolderToCreate", @"C:\Temp\ExistingFolder", false);
    

提交回复
热议问题