How to create a hardlink in C#?
Asked Answered
R

5

29

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

Renelle answered 2/8, 2010 at 12:23 Comment(4)
What do you mean by "Hardlink"? It is not a common expression.Helsinki
Hardlink? Hardcoded HTML links, or NTFS file system Hardlinks?Tactical
@Helsinki en.wikipedia.org/wiki/Hard_linkDagda
Given the (apparent) level of the question, I think I want to be sure. :)Tactical
A
42
[DllImport("Kernel32.dll", CharSet = CharSet.Unicode )]
  static extern bool CreateHardLink(
  string lpFileName,
  string lpExistingFileName,
  IntPtr lpSecurityAttributes
  );

Usage:

CreateHardLink(newLinkPath,sourcePath, IntPtr.Zero);

Anasarca answered 2/8, 2010 at 12:32 Comment(2)
May want to prepend "\\?\" to that newLinkPath as well to avoid MAX_PATH errors. learn.microsoft.com/en-us/windows/desktop/api/winbase/…Pennyworth
Be aware that CreateHardLink throws on MAC, as kernel32.dll likely doesn't exist there. Really need to find a more cross-platform way to do rare file system stuff.Thracian
S
38

The BCL doesn't provide this, so you'll have to resort to p/invoke

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

And use it e.g. like

 CreateHardLink(@"c:\temp\New Link", @"c:\temp\Original File",IntPtr.Zero);
Scribble answered 2/8, 2010 at 12:29 Comment(3)
The lpSecurityAttributes parameter must be NULL, hence the use of IntPtr.Zero.Withal
And how do I create a hard link to a folder? "mklink /J Link Target"Unsettled
@Unsettled BOOL DeviceIoControl(hDevice, FSCTL_SET_REPARSE_POINT, lpInBuffer, nInBufferSize, NULL, 0, NULL, lpOverlapped) linkNadia
T
12

If you mean the NTFS Hardlinks:

The following is (an introduction of text on dotnetspark) :

Unfortunately, neither hard links nor soft links are supported by the .NET Framework. Therefore, you will need to dig into the Windows API to allow your application to consume this feature. You can create a hard link using a single line of code using a simple call to the Win32 function, CreateHardLink(), that resides in the Kernel32.dll library. The definition of this function is as follows:

BOOL CreateHardLink(
  LPCTSTR lpFileName,
  LPCTSTR lpExistingFileName,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes
);
Tactical answered 2/8, 2010 at 12:28 Comment(2)
First post from someone who actually knows what he talks about. Gratulations. Sadly the answer is "hard" on the user, but then it is true - no managed API, sadly.Oarlock
@Vash: The dotnetspark article seems to be a bit misleading, as it confuses directory vs. file with symbolic vs. hard. There are actually four types of links (not including short-cut files, or things like DFS.)Dermis
D
3

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
{
    /// <summary>
    ///     Provides access to NTFS junction points in .Net.
    /// </summary>
    [SuppressMessage("ReSharper", "InconsistentNaming")]
    [SuppressMessage("ReSharper", "UnusedMember.Local")]
    [SuppressMessage("ReSharper", "MemberCanBePrivate.Local")]
    [SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Local")]
    public static class JunctionPoint
    {
        /// <summary>
        ///     The file or directory is not a reparse point.
        /// </summary>
        private const int ERROR_NOT_A_REPARSE_POINT = 4390;

        /// <summary>
        ///     The reparse point attribute cannot be set because it conflicts with an existing attribute.
        /// </summary>
        private const int ERROR_REPARSE_ATTRIBUTE_CONFLICT = 4391;

        /// <summary>
        ///     The data present in the reparse point buffer is invalid.
        /// </summary>
        private const int ERROR_INVALID_REPARSE_DATA = 4392;

        /// <summary>
        ///     The tag present in the reparse point buffer is invalid.
        /// </summary>
        private const int ERROR_REPARSE_TAG_INVALID = 4393;

        /// <summary>
        ///     There is a mismatch between the tag specified in the request and the tag present in the reparse point.
        /// </summary>
        private const int ERROR_REPARSE_TAG_MISMATCH = 4394;

        /// <summary>
        ///     Command to set the reparse point data block.
        /// </summary>
        private const int FSCTL_SET_REPARSE_POINT = 0x000900A4;

        /// <summary>
        ///     Command to get the reparse point data block.
        /// </summary>
        private const int FSCTL_GET_REPARSE_POINT = 0x000900A8;

        /// <summary>
        ///     Command to delete the reparse point data base.
        /// </summary>
        private const int FSCTL_DELETE_REPARSE_POINT = 0x000900AC;

        /// <summary>
        ///     Reparse point tag used to identify mount points and junction points.
        /// </summary>
        private const uint IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003;

        /// <summary>
        ///     This prefix indicates to NTFS that the path is to be treated as a non-interpreted
        ///     path in the virtual file system.
        /// </summary>
        private const string NonInterpretedPathPrefix = @"\??\";

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern bool DeviceIoControl(IntPtr hDevice, uint dwIoControlCode,
            IntPtr InBuffer, int nInBufferSize,
            IntPtr OutBuffer, int nOutBufferSize,
            out int pBytesReturned, IntPtr lpOverlapped);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern IntPtr CreateFile(
            string lpFileName,
            EFileAccess dwDesiredAccess,
            EFileShare dwShareMode,
            IntPtr lpSecurityAttributes,
            ECreationDisposition dwCreationDisposition,
            EFileAttributes dwFlagsAndAttributes,
            IntPtr hTemplateFile);

        /// <summary>
        ///     Creates a junction point from the specified directory to the specified target directory.
        /// </summary>
        /// <remarks>
        ///     Only works on NTFS.
        /// </remarks>
        /// <param name="junctionPoint">The junction point path</param>
        /// <param name="targetDir">The target directory</param>
        /// <param name="overwrite">If true overwrites an existing reparse point or empty directory</param>
        /// <exception cref="IOException">
        ///     Thrown when the junction point could not be created or when
        ///     an existing directory was found and <paramref name="overwrite" /> if false
        /// </exception>
        public static void Create(string junctionPoint, string targetDir, bool overwrite)
        {
            targetDir = Path.GetFullPath(targetDir);

            if (!Directory.Exists(targetDir))
                throw new IOException("Target path does not exist or is not a directory.");

            if (Directory.Exists(junctionPoint))
            {
                if (!overwrite)
                    throw new IOException("Directory already exists and overwrite parameter is false.");
            }
            else
            {
                Directory.CreateDirectory(junctionPoint);
            }

            using (var handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericWrite))
            {
                var targetDirBytes = Encoding.Unicode.GetBytes(NonInterpretedPathPrefix + Path.GetFullPath(targetDir));

                var reparseDataBuffer =
                    new REPARSE_DATA_BUFFER
                    {
                        ReparseTag = IO_REPARSE_TAG_MOUNT_POINT,
                        ReparseDataLength = (ushort)(targetDirBytes.Length + 12),
                        SubstituteNameOffset = 0,
                        SubstituteNameLength = (ushort)targetDirBytes.Length,
                        PrintNameOffset = (ushort)(targetDirBytes.Length + 2),
                        PrintNameLength = 0,
                        PathBuffer = new byte[0x3ff0]
                    };

                Array.Copy(targetDirBytes, reparseDataBuffer.PathBuffer, targetDirBytes.Length);

                var inBufferSize = Marshal.SizeOf(reparseDataBuffer);
                var inBuffer = Marshal.AllocHGlobal(inBufferSize);

                try
                {
                    Marshal.StructureToPtr(reparseDataBuffer, inBuffer, false);

                    int bytesReturned;
                    var result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_SET_REPARSE_POINT,
                        inBuffer, targetDirBytes.Length + 20, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero);

                    if (!result)
                        ThrowLastWin32Error("Unable to create junction point.");
                }
                finally
                {
                    Marshal.FreeHGlobal(inBuffer);
                }
            }
        }

        /// <summary>
        ///     Deletes a junction point at the specified source directory along with the directory itself.
        ///     Does nothing if the junction point does not exist.
        /// </summary>
        /// <remarks>
        ///     Only works on NTFS.
        /// </remarks>
        /// <param name="junctionPoint">The junction point path</param>
        public static void Delete(string junctionPoint)
        {
            if (!Directory.Exists(junctionPoint))
            {
                if (File.Exists(junctionPoint))
                    throw new IOException("Path is not a junction point.");

                return;
            }

            using (var handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericWrite))
            {
                var reparseDataBuffer = new REPARSE_DATA_BUFFER
                {
                    ReparseTag = IO_REPARSE_TAG_MOUNT_POINT,
                    ReparseDataLength = 0,
                    PathBuffer = new byte[0x3ff0]
                };


                var inBufferSize = Marshal.SizeOf(reparseDataBuffer);
                var inBuffer = Marshal.AllocHGlobal(inBufferSize);
                try
                {
                    Marshal.StructureToPtr(reparseDataBuffer, inBuffer, false);

                    int bytesReturned;
                    var result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_DELETE_REPARSE_POINT,
                        inBuffer, 8, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero);

                    if (!result)
                        ThrowLastWin32Error("Unable to delete junction point.");
                }
                finally
                {
                    Marshal.FreeHGlobal(inBuffer);
                }

                try
                {
                    Directory.Delete(junctionPoint);
                }
                catch (IOException ex)
                {
                    throw new IOException("Unable to delete junction point.", ex);
                }
            }
        }

        /// <summary>
        ///     Determines whether the specified path exists and refers to a junction point.
        /// </summary>
        /// <param name="path">The junction point path</param>
        /// <returns>True if the specified path represents a junction point</returns>
        /// <exception cref="IOException">
        ///     Thrown if the specified path is invalid
        ///     or some other error occurs
        /// </exception>
        public static bool Exists(string path)
        {
            if (!Directory.Exists(path))
                return false;

            using (var handle = OpenReparsePoint(path, EFileAccess.GenericRead))
            {
                var target = InternalGetTarget(handle);
                return target != null;
            }
        }

        /// <summary>
        ///     Gets the target of the specified junction point.
        /// </summary>
        /// <remarks>
        ///     Only works on NTFS.
        /// </remarks>
        /// <param name="junctionPoint">The junction point path</param>
        /// <returns>The target of the junction point</returns>
        /// <exception cref="IOException">
        ///     Thrown when the specified path does not
        ///     exist, is invalid, is not a junction point, or some other error occurs
        /// </exception>
        public static string GetTarget(string junctionPoint)
        {
            using (var handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericRead))
            {
                var target = InternalGetTarget(handle);
                if (target == null)
                    throw new IOException("Path is not a junction point.");

                return target;
            }
        }

        private static string InternalGetTarget(SafeFileHandle handle)
        {
            var outBufferSize = Marshal.SizeOf(typeof(REPARSE_DATA_BUFFER));
            var outBuffer = Marshal.AllocHGlobal(outBufferSize);

            try
            {
                int bytesReturned;
                var result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_GET_REPARSE_POINT,
                    IntPtr.Zero, 0, outBuffer, outBufferSize, out bytesReturned, IntPtr.Zero);

                if (!result)
                {
                    var error = Marshal.GetLastWin32Error();
                    if (error == ERROR_NOT_A_REPARSE_POINT)
                        return null;

                    ThrowLastWin32Error("Unable to get information about junction point.");
                }

                var reparseDataBuffer = (REPARSE_DATA_BUFFER)
                    Marshal.PtrToStructure(outBuffer, typeof(REPARSE_DATA_BUFFER));

                if (reparseDataBuffer.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT)
                    return null;

                var targetDir = Encoding.Unicode.GetString(reparseDataBuffer.PathBuffer,
                    reparseDataBuffer.SubstituteNameOffset, reparseDataBuffer.SubstituteNameLength);

                if (targetDir.StartsWith(NonInterpretedPathPrefix))
                    targetDir = targetDir.Substring(NonInterpretedPathPrefix.Length);

                return targetDir;
            }
            finally
            {
                Marshal.FreeHGlobal(outBuffer);
            }
        }

        private static SafeFileHandle OpenReparsePoint(string reparsePoint, EFileAccess accessMode)
        {
            var reparsePointHandle = new SafeFileHandle(CreateFile(reparsePoint, accessMode,
                EFileShare.Read | EFileShare.Write | EFileShare.Delete,
                IntPtr.Zero, ECreationDisposition.OpenExisting,
                EFileAttributes.BackupSemantics | EFileAttributes.OpenReparsePoint, IntPtr.Zero), true);

            if (Marshal.GetLastWin32Error() != 0)
                ThrowLastWin32Error("Unable to open reparse point.");

            return reparsePointHandle;
        }

        private static void ThrowLastWin32Error(string message)
        {
            throw new IOException(message, Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()));
        }

        [Flags]
        private enum EFileAccess : uint
        {
            GenericRead = 0x80000000,
            GenericWrite = 0x40000000,
            GenericExecute = 0x20000000,
            GenericAll = 0x10000000
        }

        [Flags]
        private enum EFileShare : uint
        {
            None = 0x00000000,
            Read = 0x00000001,
            Write = 0x00000002,
            Delete = 0x00000004
        }

        private enum ECreationDisposition : uint
        {
            New = 1,
            CreateAlways = 2,
            OpenExisting = 3,
            OpenAlways = 4,
            TruncateExisting = 5
        }

        [Flags]
        private enum EFileAttributes : uint
        {
            Readonly = 0x00000001,
            Hidden = 0x00000002,
            System = 0x00000004,
            Directory = 0x00000010,
            Archive = 0x00000020,
            Device = 0x00000040,
            Normal = 0x00000080,
            Temporary = 0x00000100,
            SparseFile = 0x00000200,
            ReparsePoint = 0x00000400,
            Compressed = 0x00000800,
            Offline = 0x00001000,
            NotContentIndexed = 0x00002000,
            Encrypted = 0x00004000,
            Write_Through = 0x80000000,
            Overlapped = 0x40000000,
            NoBuffering = 0x20000000,
            RandomAccess = 0x10000000,
            SequentialScan = 0x08000000,
            DeleteOnClose = 0x04000000,
            BackupSemantics = 0x02000000,
            PosixSemantics = 0x01000000,
            OpenReparsePoint = 0x00200000,
            OpenNoRecall = 0x00100000,
            FirstPipeInstance = 0x00080000
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct REPARSE_DATA_BUFFER
        {
            /// <summary>
            ///     Reparse point tag. Must be a Microsoft reparse point tag.
            /// </summary>
            public uint ReparseTag;

            /// <summary>
            ///     Size, in bytes, of the data after the Reserved member. This can be calculated by:
            ///     (4 * sizeof(ushort)) + SubstituteNameLength + PrintNameLength +
            ///     (namesAreNullTerminated ? 2 * sizeof(char) : 0);
            /// </summary>
            public ushort ReparseDataLength;

            /// <summary>
            ///     Reserved; do not use.
            /// </summary>
            public ushort Reserved;

            /// <summary>
            ///     Offset, in bytes, of the substitute name string in the PathBuffer array.
            /// </summary>
            public ushort SubstituteNameOffset;

            /// <summary>
            ///     Length, in bytes, of the substitute name string. If this string is null-terminated,
            ///     SubstituteNameLength does not include space for the null character.
            /// </summary>
            public ushort SubstituteNameLength;

            /// <summary>
            ///     Offset, in bytes, of the print name string in the PathBuffer array.
            /// </summary>
            public ushort PrintNameOffset;

            /// <summary>
            ///     Length, in bytes, of the print name string. If this string is null-terminated,
            ///     PrintNameLength does not include space for the null character.
            /// </summary>
            public ushort PrintNameLength;

            /// <summary>
            ///     A buffer containing the unicode-encoded path string. The path string contains
            ///     the substitute name string and print name string.
            /// </summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3FF0)]
            public byte[] PathBuffer;
        }
    }
}

Usage:

using CreateMaps;
...
JunctionPoint.Create(@"C:\Temp\HardlinkFolderToCreate", @"C:\Temp\ExistingFolder", false);
Dialogist answered 29/8, 2018 at 13:5 Comment(0)
U
-3
Process.Start("mklink /H", String.Format("{0} {1}", link, target));
Uintathere answered 2/8, 2010 at 12:35 Comment(3)
Better to use the API, and this solution also has problems reg. spaces in file names (and dependency on an external program).Clementia
@Michael there is no problems, you can escape spaces. This this the right approach, Unix way, why to call complex C API when you can call simple exe? That's right, call simple exe which always presents in Windows.Uintathere
@abatishchev: Why not use it? Because it's very slow if you hardlink a tree with 1000 items (yes that's possible, simply create the tree structure manually and hardlink the files). But it's a good solution otherwise, I gave you a "+1" as this is definitely an option in some cases.Sclerenchyma

© 2022 - 2024 — McMap. All rights reserved.