How to obtain the target of a symbolic link (or Reparse Point) using .Net?
Asked Answered
E

4

20

In .NET, I think I can determine if a file is a symbolic link by calling System.IO.File.GetAttributes(), and checking for the ReparsePoint bit. like so:

var a = System.IO.File.GetAttributes(fileName);
if ((a & FileAttributes.ReparsePoint) != 0)
{
    // it's a symlink
}

How can I obtain the target of the symbolic link, in this case?


ps: I know how to create a symbolic link. It requires P/Invoke:

[Interop.DllImport("kernel32.dll", EntryPoint="CreateSymbolicLinkW", CharSet=Interop.CharSet.Unicode)] 
public static extern int CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, int dwFlags); 
Elfrieda answered 20/2, 2010 at 13:59 Comment(0)
G
10

You have to use DeviceIoControl() and send the FSCTL_GET_REPARSE_POINT control code. The P/Invoke and API usage details are quite gritty, but it Googles really well.

Genus answered 20/2, 2010 at 14:27 Comment(1)
This led me to the source code for the Powershell Community Extensions (PSCX), which has good code for handling ReparsePoints.Elfrieda
K
18

Based on the answer that mentioned GetFinalPathNameByHandle here is the C# code that does this (since all other answers were just pointers):

Usage

var path = NativeMethods.GetFinalPathName(@"c:\link");

Code:

public static class NativeMethods
{
    private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);

    private const uint FILE_READ_EA = 0x0008;
    private const uint FILE_FLAG_BACKUP_SEMANTICS = 0x2000000;

    [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    static extern uint GetFinalPathNameByHandle(IntPtr hFile, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder lpszFilePath, uint cchFilePath, uint dwFlags);

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool CloseHandle(IntPtr hObject);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CreateFile(
            [MarshalAs(UnmanagedType.LPTStr)] string filename,
            [MarshalAs(UnmanagedType.U4)] uint access,
            [MarshalAs(UnmanagedType.U4)] FileShare share,
            IntPtr securityAttributes, // optional SECURITY_ATTRIBUTES struct or IntPtr.Zero
            [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
            [MarshalAs(UnmanagedType.U4)] uint flagsAndAttributes,
            IntPtr templateFile);

    public static string GetFinalPathName(string path)
    {
        var h = CreateFile(path, 
            FILE_READ_EA, 
            FileShare.ReadWrite | FileShare.Delete, 
            IntPtr.Zero, 
            FileMode.Open, 
            FILE_FLAG_BACKUP_SEMANTICS,
            IntPtr.Zero);
        if (h == INVALID_HANDLE_VALUE)
            throw new Win32Exception();

        try
        {
            var sb = new StringBuilder(1024);
            var res = GetFinalPathNameByHandle(h, sb, 1024, 0);
            if (res == 0)
                throw new Win32Exception();

            return sb.ToString();
        }
        finally
        {
            CloseHandle(h);
        }
    }
}
Keramics answered 2/11, 2015 at 21:41 Comment(5)
I think (but I have not tested) but you may be able to simplify your code using a .NET FileStream object then using var h = yourStream.SafeFileHandle.DangerousGetHandle(), when you close the stream you also release the handle so you don't need to call CloseHandle(h) on that variable. You could even make the function take in a FileStream instead of a string.Millwright
@ScottChamberlain - the reasons why I did not use FileStream is a) I am not sure if it would pass through the attributes that are not defined in .NET and b) I am not sure if it would work for directories as well (CreateFile does work). Plus this should be faster (though I did not measure it).Dru
If you're like me, the next thing you'll want to know is this: #31439511Coercion
@Coercion Are you aware that the paths prepended with \\?\ are actually valid and usable? It also has some benefits (telling Windows to use the Unicode extensions where possible, bypassingthe MAX_PATH_LENGTH limit) By all means, strip it for aesthetic reasons, but it's a perfectly valid responseeDragonroot
GetFinalPathNameByHandle can fail on a volume that did not register with the volume manager, this includes Passmark OSFMount.Wenz
H
14

In .NET 6 you can use the property LinkTarget

bool IsLink(string path)
{
    var fi = new FileInfo(path);
    return fi.LinkTarget != null
}
Hhour answered 18/1, 2022 at 21:3 Comment(0)
G
10

You have to use DeviceIoControl() and send the FSCTL_GET_REPARSE_POINT control code. The P/Invoke and API usage details are quite gritty, but it Googles really well.

Genus answered 20/2, 2010 at 14:27 Comment(1)
This led me to the source code for the Powershell Community Extensions (PSCX), which has good code for handling ReparsePoints.Elfrieda
C
5

Open the file using CreateFile, and then pass the handle to GetFinalPathNameByHandle.

Callous answered 20/2, 2010 at 14:27 Comment(6)
@Cheeso: symlinks to files debuted in Vista, AFAIK. So, all of the file based symlink functins will have that same restriction.Callous
Reparse points have been around since Win2k; it's only symbolic links to files that debuted in Vista.Upstroke
For some reason, I consistently get an Access Violation when calling this method... not sure whyTheretofore
Regarding Vista, anything is better.Batten
9 years later, the first link is broken.Colas
@Colas - can't find (and don't remember) the Raymond Chen commentary, so updated link to the CreateFile function needed to openCallous

© 2022 - 2024 — McMap. All rights reserved.