18

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); 
jww
  • 97,681
  • 90
  • 411
  • 885
Cheeso
  • 189,189
  • 101
  • 473
  • 713

4 Answers4

17

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);
        }
    }
}
Knaģis
  • 20,827
  • 7
  • 66
  • 80
  • 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. – Scott Chamberlain Nov 02 '15 at 22:03
  • 1
    @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). – Knaģis Nov 03 '15 at 09:04
  • 2
    If you're like me, the next thing you'll want to know is this: https://stackoverflow.com/questions/31439011/getfinalpathnamebyhandle-without-prepended – datguy Nov 09 '17 at 18:16
  • 1
    @datguy 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 responsee – Basic Feb 25 '19 at 18:53
  • `GetFinalPathNameByHandle` can fail on a volume that did not register with the volume manager, this includes Passmark OSFMount. – Dwedit Apr 29 '22 at 20:06
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.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • 1
    This led me to the source code for the Powershell Community Extensions (PSCX), which has good code for handling ReparsePoints. – Cheeso Feb 21 '10 at 16:12
10

In .NET 6 you can use the property LinkTarget

bool IsLink(string path)
{
    var fi = new FileInfo(path);
    return fi.LinkTarget != null
}
Marin
  • 101
  • 1
  • 2
5

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

Mark Brackett
  • 84,552
  • 17
  • 108
  • 152