0

I have the following directory structure:

D:\LinkTest
├─Links
│ ├─LinkToTargetDirectory.lnk
│ └─LinkToTargetFile.lnk
└─Targets
  ├─TargetFile.txt
  └─TargetDirectory
    └─FileInTargetDirectory.txt

.lnk files are symbolic links. They show up in Windows Explorer without the .lnk extension, with the respective target's icon, and double-clicking LinkToTargetDirectory shows the contents of D:\LinkTest\Targets\TargetDirectory, and double-clicking LinkToTargetFile opens the file D:\LinkTest\Targets\TargetFile.txt in notepad.
.txt files are text files.
Everything else are directories.

I would like to use the following code taken from this answer to get the path of the file/directory pointed to:

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

What should happen:
Console.WriteLine(NativeMethods.GetFinalPathName(@"D:\LinkTest\Links\LinkToTargetDirectory.lnk"));
should print \\?\D:\LinkTest\Targets\TargetDirectory Console.WriteLine(NativeMethods.GetFinalPathName(@"D:\LinkTest\Links\LinkToTargetFile.lnk"));
should print \\?\D:\LinkTest\Targets\TargetFile.txt

What actually happens:
Console.WriteLine(NativeMethods.GetFinalPathName(@"D:\LinkTest\Links\LinkToTargetDirectory.lnk"));
prints \\?\D:\LinkTest\Links\LinkToTargetDirectory.lnk Console.WriteLine(NativeMethods.GetFinalPathName(@"D:\LinkTest\Links\LinkToTargetFile.lnk"));
prints \\?\D:\LinkTest\Links\LinkToTargetFile.lnk

So instead of getting the target's path, it just returns the path of the link file.

What is wrong with this code and how can I fix it?

I already found this: https://social.msdn.microsoft.com/forums/windowsdesktop/en-US/d8a26a7f-6661-48ce-b183-8e476dfffecd/am-i-using-getfinalpathnamebyhandle-incorrectly?forum=windowsgeneraldevelopmentissues
Passing 8 instead of 0 as the last argument to GetFinalPathNameByHandle seems to help that user, but it does not change the behavior for me.

djv
  • 15,168
  • 7
  • 48
  • 72
Niko O
  • 406
  • 3
  • 15
  • Have you tried [`System.IO.FileSystemInfo.LinkTarget`](https://learn.microsoft.com/en-us/dotnet/api/system.io.filesysteminfo.linktarget?view=net-6.0#system-io-filesysteminfo-linktarget)? – Hel O'Ween May 24 '22 at 14:38
  • 6
    Are you _really_ sure these are "symbolic links" (as in the file system feature)? `.lnk` files are conventionally a thing of the Windows Shell (i.e. File Explorer) - also called "shortcuts". They have nothing to do with symbolic links, junctions, reparse points, etc. of the file system. To resolve the target of a `.lnk` file you'll need to use the Shell API. – Christian.K May 24 '22 at 16:00
  • 1
    I'm not closing this as a duplicate, because doubt remains. But you may want to check [this](https://stackoverflow.com/questions/139010/how-to-resolve-a-lnk-in-c-sharp). – Christian.K May 24 '22 at 16:01
  • 3
    *"`.lnk` files are symbolic links"* - Er, no, they aren't. They are serialized shortcuts. See [Shortcuts are serializable objects, which means that they can be stored in places other than just a file](https://devblogs.microsoft.com/oldnewthing/20110224-00/?p=11403) for a bit of background information. They are an implementation detail of the Shell, not a filesystem feature. – IInspectable May 24 '22 at 16:28
  • 3
    @NikoO `.lnk` files are NOT symbolic links. That is a completely different feature of the file system. `.lnk` files are physical files, and your calls to `CreateFile()` are opening the `.lnk` files themselves, not their targets, so it makes sense that your calls to `GetFinalPathNameByHandle()` are returning the `.lnk` file paths. You need to use the [`IShellLink` interface](https://learn.microsoft.com/en-us/windows/win32/shell/links) to resolve `.lnk` targets. – Remy Lebeau May 24 '22 at 17:44
  • @HelO'Ween See .net-4.0 tag. LinkTarget is not available in .NET Framework 4.0 – Niko O May 25 '22 at 05:12
  • It seems I have misunderstood symbolic links. That would explain why the code is not working. @Christian.K Thanks! I'll try that and then check back here. – Niko O May 25 '22 at 05:15

0 Answers0