Too bad GetFinalPathNameByHandle only works if the handle is a file that has something in it. For a directory or file with zero bytes it fails in a bad way. So you can't get the name of a directory or a zero byte file. Kind of a bummer especially since this is a new to Vista API and would be really nice to have when used in conjunction with CreateSymbolicLink. There are ways of getting around the odd failure by creating a file map, but the directory and zero byte file is a bit more difficult requiring some rather serious hoop jumping.
5 comments:
Hi Chris, I had the same situation where I wanted to get the symbolic link target of a directory (from C#). I came across your post in my search. Eventually, I was able to solve my problem using the following code:
private const int FILE_SHARE_READ = 1;
private const int FILE_SHARE_WRITE = 2;
private const int CREATION_DISPOSITION_OPEN_EXISTING = 3;
private const int FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
// http://msdn.microsoft.com/en-us/library/aa364962%28VS.85%29.aspx
[DllImport("kernel32.dll", EntryPoint = "GetFinalPathNameByHandleW", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int GetFinalPathNameByHandle(IntPtr handle, [In, Out] StringBuilder path, int bufLen, int flags);
// http://msdn.microsoft.com/en-us/library/aa363858(VS.85).aspx
[DllImport("kernel32.dll", EntryPoint = "CreateFileW", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode,
IntPtr SecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile);
public static string GetSymbolicLinkTarget(DirectoryInfo symlink)
{
SafeFileHandle directoryHandle = CreateFile(symlink.FullName, 0, 2, System.IntPtr.Zero, CREATION_DISPOSITION_OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, System.IntPtr.Zero);
if(directoryHandle.IsInvalid)
throw new Win32Exception(Marshal.GetLastWin32Error());
StringBuilder path = new StringBuilder(512);
int size = GetFinalPathNameByHandle(directoryHandle.DangerousGetHandle(), path, path.Capacity, 0);
if (size<0)
throw new Win32Exception(Marshal.GetLastWin32Error());
// The remarks section of GetFinalPathNameByHandle mentions the return being prefixed with "\\?\"
// More information about "\\?\" here -> http://msdn.microsoft.com/en-us/library/aa365247(v=VS.85).aspx
if (path[0] == '\\' && path[1] == '\\' && path[2] == '?' && path[3] == '\\')
return path.ToString().Substring(4);
else
return path.ToString();
}
I hope it helps you and whoever comes across this page :)
H, thanks for the comment. What about your code allows GetFinalPathNameByHandle to return the name of a directory? The last parameter to GetFinalPathNameByHandle is the only real choice when making the call but passing VOLUME_NAME_NT or VOLUME_NAME_DOS hasn't seemed to make any difference. Does your code work with zero byte files?
Here's a version that returns the proper values for symlinks to empty files and directories. Please use Delphi XE to compile or define missing function import etc.
I have noticed that one should always use the full path to a file when calling MKLINK, otherwise the call to "CreateFile" is going to fail (and DIR /AL as well).
Hope this helps,
Olaf
-------------------------------------------
{$WARN SYMBOL_PLATFORM OFF}
program FinalPathNameByHandle;
{$APPTYPE CONSOLE}
uses
SysUtils,
Windows;
(* ---- *)
var
Handle : THandle;
sLink, sTarget : String;
dwAttr, dwFlagsAndAttr : DWord;
uLen : UInt;
uResult : UInt = 1;
begin { FinalPathNameByHandle }
WriteLn;
if (ParamCount <> 1) then
begin
WriteLn ('FinalPathNameByHandle.exe [file or directory name]');
Halt ($FF);
end; { if }
sLink := ParamStr (1);
try
dwAttr := GetFileAttributes (PChar (sLink));
Win32Check (dwAttr <> INVALID_HANDLE_VALUE);
if ((dwAttr and FILE_ATTRIBUTE_REPARSE_POINT) <> 0) then
begin
WriteLn (Format ('"%s" is a Reparse Point', [sLink]));
if (FileExists (sLink)) then
dwFlagsAndAttr := FILE_ATTRIBUTE_NORMAL
else dwFlagsAndAttr := FILE_ATTRIBUTE_DIRECTORY or FILE_FLAG_BACKUP_SEMANTICS;
Handle := CreateFile (PChar (sLink), GENERIC_READ, FILE_SHARE_READ,
NIL, OPEN_EXISTING, dwFlagsAndAttr, 0);
if (Handle = INVALID_HANDLE_VALUE) and
(GetLastError = 5) then // Access denied
Handle := CreateFile (PChar (sLink), 0, FILE_SHARE_READ, NIL,
OPEN_EXISTING, dwFlagsAndAttr, 0);
Win32Check (Handle <> INVALID_HANDLE_VALUE);
try
SetLength (sTarget, MAX_PATH);
uLen := GetFinalPathNameByHandle (Handle, PChar (sTarget),
MAX_PATH,
FILE_NAME_NORMALIZED);
Win32Check (uLen > 0);
uResult := 0;
SetLength (sTarget, uLen);
WriteLn (Format ('File target: "%s"', [sTarget]));
finally
CloseHandle (Handle);
end; { try / finally }
end { if }
else WriteLn (Format ('"%s" is not a Reparse Point', [sLink]));
except
on E:Exception do
WriteLn (Format ('Exception %s: %s', [E.ClassName, E.Message]));
end; { try / except }
if (DebugHook > 0) then
begin
WriteLn;
Write ('Press [Enter] to continue ');
ReadLn;
end; { if }
Halt (uResult);
end.
@Olaf, thanks for investigating GetFinalPathNameByHandle. I'm on vacation at the moment but when I get back to my desk I take a look at what you've go here!
Post a Comment