Search code examples
c#pinvokentfsjunctionreparsepoint

Modifying attributes of a NTFS junction point


I am using this libray in order to create or get my junction points. Once I have got such a JunctionPoint instance I am able to create a DirectoryInfo from the Link's (*) path and read the LastWriteTimeUtc property. Now I would like to set this timestamp to another date, but it doesn't work: The value stays the same and no exception is thrown.

(*): That's the path to the location, where you find the junction point.

I'm suspecting, that you will need to make use of some PInvoke in order to get this done. Either by specifying the desired timestamp on creation or afterwards using a second method. So I looked up the doc for CreateFile(), which is used for creating the junction point, hoping to find something helpful. The dwFlagsAndAttributes parameter seems to accept some flags/numeric values and the doc for the SetFileAttributes() method doesn't look better.

I haven't much experience on working with PInvoke, some good advice would REALLY be appreciated.

Edit

"I don't want to download code. I'd like to see enough code in the question to be able to answer"

This is the relevant code used for creating a junction. It's slightly different from the code of codeproject, because I needed to make some adaptations in order to embed this into my own library. Link is of type DirectoryInfo:

public void Create(bool overwrite = true)
    {
        Link.Refresh();
        if (Link.Exists)
        {
            if (!overwrite) throw new IOException("Directory already exists and overwrite parameter is false.");
        }
        else Link.Create();

        using (var handle = OpenReparsePoint(Link.FullName, EFileAccess.GenericWrite))
        {
            var targetDirBytes = Encoding.Unicode.GetBytes(NonInterpretedPathPrefix + Target.FullName);

            var reparseDataBuffer = new REPARSE_DATA_BUFFER();

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

            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);
            }
        }
    }

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;
    }

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

    [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);

Solution

  • Recently I had to come back to this old question and found following solution by making use of the Win32 API:

    For this you need the SafeFileHandle of the created junction. So I modified the Create() method to return the result of OpenReparsePoint() without disposing it (that will be done afterwards). In order to access the Win32 API, you need to declare an extern method, which matches the API's method to be called.

    [DllImport("kernel32.dll", SetLastError = true)]
    [ResourceExposure(ResourceScope.None)]
    private static extern bool SetFileTime(SafeFileHandle hFile,
                                           ref long lpCreationTime,
                                           ref long lpLastAccessTime,
                                           ref long lpLastWriteTime);
    

    Now you can set the file time like this

    string link;
    string target;
    var junctionPoint = new JunctionPoint(ling, target);
    
    long creationTime; // ticks in FileTime format
    long accessTime;
    long lastWriteTime
    using(SafeFileHandle hFile = junctionPoint.Create()) 
    { 
         SetFileTime(SafeFileHandle hFile, 
                         ref creationTime,
                       ref lastAccessTime,
                        ref lastWriteTime);
    }