The code works with 32bit and 64bit. It will compile/run on all NT based Windows versions, not just Vista. It will just return the original path on older systems.
Code: Select all
; WinIoCtl.h
;
#FILE_DEVICE_FILE_SYSTEM = $00000009
#METHOD_BUFFERED = 0
#FILE_ANY_ACCESS = 0
#FILE_SPECIAL_ACCESS = (#FILE_ANY_ACCESS)
Macro CTL_CODE( DeviceType, Function, Method, Access )
(((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))
EndMacro
#FSCTL_SET_REPARSE_POINT = CTL_CODE(#FILE_DEVICE_FILE_SYSTEM, 41, #METHOD_BUFFERED, #FILE_SPECIAL_ACCESS)
#FSCTL_GET_REPARSE_POINT = CTL_CODE(#FILE_DEVICE_FILE_SYSTEM, 42, #METHOD_BUFFERED, #FILE_ANY_ACCESS)
#FSCTL_DELETE_REPARSE_POINT = CTL_CODE(#FILE_DEVICE_FILE_SYSTEM, 43, #METHOD_BUFFERED, #FILE_SPECIAL_ACCESS)
; Winbase.h
;
#FILE_FLAG_OPEN_REPARSE_POINT = $00200000
; WinNT.h
;
#IO_REPARSE_TAG_MOUNT_POINT = $A0000003
#IO_REPARSE_TAG_HSM = $C0000004
#IO_REPARSE_TAG_HSM2 = $80000006
#IO_REPARSE_TAG_SIS = $80000007
#IO_REPARSE_TAG_DFS = $8000000A
#IO_REPARSE_TAG_SYMLINK = $A000000C
#IO_REPARSE_TAG_DFSR = $80000012
; From Windows Driver Kit.
; http://msdn.microsoft.com/en-us/library/ms791514.aspx
;
Structure SymbolicLinkReparseBuffer
SubstituteNameOffset.w
SubstituteNameLength.w
PrintNameOffset.w
PrintNameLength.w
Flags.l
PathBuffer.w[1]
EndStructure
Structure MountPointReparseBuffer
SubstituteNameOffset.w
SubstituteNameLength.w
PrintNameOffset.w
PrintNameLength.w
PathBuffer.w[1]
EndStructure
Structure GenericReparseBuffer
DataBuffer.b[1]
EndStructure
Structure REPARSE_DATA_BUFFER
ReparseTag.l
ReparseDataLength.w
Reserved.w
StructureUnion
SymbolicLinkReparseBuffer.SymbolicLinkReparseBuffer
MountPointReparseBuffer.MountPointReparseBuffer
GenericReparseBuffer.GenericReparseBuffer
EndStructureUnion
EndStructure
; Tries to follow a directory link on Windows Vista (should also work for files)
;
; - If the directory is no link, the result is the original directory
; - If the target cannot be read, the result is ""
;
Procedure.s GetDirectoryTarget(Directory$)
Protected TokenHandle, BufferSize, hDirectory, BytesReturned.l
Protected Privileges.TOKEN_PRIVILEGES
Protected *Buffer.REPARSE_DATA_BUFFER
Protected Result$ = ""
; Check if the directory is a reparse point (link or mount point)
;
If GetFileAttributes_(@Directory$) & #FILE_ATTRIBUTE_REPARSE_POINT
; The backup privilege is required to open a directory for io queries
; So try to set it on our process token. (usually it should be set already)
;
If OpenProcessToken_(GetCurrentProcess_(), #TOKEN_ADJUST_PRIVILEGES, @TokenHandle)
Privileges\PrivilegeCount = 1
Privileges\Privileges[0]\Attributes = #SE_PRIVILEGE_ENABLED
If LookupPrivilegeValue_(#Null, @"SeBackupPrivilege", @Privileges\Privileges[0]\Luid)
AdjustTokenPrivileges_(TokenHandle, #False, @Privileges, SizeOf(TOKEN_PRIVILEGES), #Null, #Null)
EndIf
CloseHandle_(TokenHandle)
EndIf
; Open the directory
; Have to pass 0 as access right (not #GENERIC_READ), as it fails otherwise
; http://www.codeproject.com/KB/vista/Windows_Vista.aspx
;
hDirectory = CreateFile_(@Directory$, 0, #FILE_SHARE_READ|#FILE_SHARE_WRITE, #Null, #OPEN_EXISTING, #FILE_FLAG_OPEN_REPARSE_POINT | #FILE_FLAG_BACKUP_SEMANTICS, #Null)
If hDirectory <> #INVALID_HANDLE_VALUE
; Allocate a buffer for the io query. 1000 bytes should be enough for the real path (in unicode)
;
BufferSize = SizeOf(REPARSE_DATA_BUFFER) + 1000
*Buffer = AllocateMemory(BufferSize)
If *Buffer
; Query the directory for reparse point information
;
If DeviceIoControl_(hDirectory, #FSCTL_GET_REPARSE_POINT, #Null, 0, *Buffer, BufferSize, @BytesReturned, #Null) <> 0
; Check the kind of reparse point (device drivers can create their own tags, so this is important)
; The "& $FFFFFFFF" is for 64bit, as the tags are negative when interpreted as quads
;
If *Buffer\ReparseTag & $FFFFFFFF = #IO_REPARSE_TAG_MOUNT_POINT
; Read the result. The offset and length are in bytes. PeekS needs length in characters
;
Result$ = PeekS(@*Buffer\MountPointReparseBuffer\PathBuffer[0] + *Buffer\MountPointReparseBuffer\SubstituteNameOffset, *Buffer\MountPointReparseBuffer\SubstituteNameLength / 2, #PB_Unicode)
ElseIf *Buffer\ReparseTag & $FFFFFFFF = #IO_REPARSE_TAG_SYMLINK
Result$ = PeekS(@*Buffer\SymbolicLinkReparseBuffer\PathBuffer[0] + *Buffer\SymbolicLinkReparseBuffer\SubstituteNameOffset, *Buffer\SymbolicLinkReparseBuffer\SubstituteNameLength / 2, #PB_Unicode)
EndIf
EndIf
FreeMemory(*Buffer)
EndIf
CloseHandle_(hDirectory)
EndIf
Else
; It is not a reparse point, so return the original path
;
Result$ = Directory$
EndIf
; Since the result is a unicode directory name, it can have the "\??\" prefix which allows a length of 32767 characters.
;
If Left(Result$, 4) = "\??\"
Result$ = Right(Result$, Len(Result$)-4)
EndIf
ProcedureReturn Result$
EndProcedure
; ----------------------------------------------------------------------------
Directory$ = "C:\Documents and Settings\"
Debug "Testing: " + Directory$
Debug "Target: " + GetDirectoryTarget(Directory$)