Page 1 of 2

Get the Shortcut target

Posted: Sat Aug 21, 2010 5:20 pm
by oftit
Hello all
Iv'e been searching in the forum and exploring the .lnk file by Reading the file (ReadFile()). But I still can't access the target filename????

Please help

Thank in advance

Re: Get the Shortcut target

Posted: Sat Aug 21, 2010 6:13 pm
by infratec
For informations about the LNK format look here:
http://www.stdlib.com/art6-Shortcut-Fil ... t-lnk.html

To find something about the UniCode-Flag:
http://evilcodecave.blogspot.com/2010/0 ... t-and.html

There is a file on the net which is called MS-SHLLINK.pdf
That's the best 'original' source.
But not directly available from MS$, they removed it :(

Bernd

Re: Get the Shortcut target

Posted: Sat Aug 21, 2010 6:52 pm
by infratec
A first result out of the above documentation: LnkInfo.pb

Code: Select all

#Version = "1.06"

Structure ShellLinkHeaderStr
  HeaderSize.l
  LinkCLSID.a[16]
  LinkFlags.l
  FileAttributes.l
  CreationTime.q
  AccessTime.q
  WriteTime.q
  FileSize.l
  IconIndex.l
  ShowCommand.l
  HotKey.w
  Reserved1.w
  Reserved2.l
  Reserved3.l
EndStructure

Enumeration ; LinkFlags
  #HasLinkTargetIDList
  #HasLinkInfo
  #HasName
  #HasRelativePath
  #HasWorkingDir
  #HasArguments
  #HasIconLocation
  #IsUnicode
  #ForceNoLinkInfo
  #HasExpString
  #RunInSeparateProcess
  #Unused1
  #HasDarwinID
  #RunAsUser
  #HasExpIcon
  #NoPidAlias
  #Unused2
  #RunWithShimLayer
  #ForceNoLinkTrack
  #EnableTargetMetadata
  #DisableLinkPathTracking
  #DisableKnownFolderTracking
  #DisableKnownFolderAlias
  #AllowLinkToLink
  #UnaliasOnSave
  #PreferEnvironmentPath
  #KeepLocalIDListForUNCTarget
EndEnumeration

; already defined
;Enumeration ; FileAttributesFlag
;  #FILE_ATTRIBUTE_READONLY
;  #FILE_ATTRIBUTE_HIDDEN
;  #FILE_ATTRIBUTE_SYSTEM
;  #Reserved1
;  #FILE_ATTRIBUTE_DIRECTORY
;  #FILE_ATTRIBUTE_ARCHIVE
;  #Reserved2
;  #FILE_ATTRIBUTE_NORMAL
;  #FILE_ATTRIBUTE_TEPORARY
;  #FILE_ATTRIBUTE_SPARSE_FILE
;  #FILE_ATTRIBUTE_REPARSE_POINT
;  #FILE_ATTRIBUTE_COMPRESSED
;  #FILE_ATTRIBUTE_OFFLINE
;  #FILE_ATTRIBUTE_NOT_CONTENT_INDEXED
;  #FILE_ATTRIBUTE_ENCRYPTED
;EndEnumeration

Structure LinkInfoStr
  LinkInfoSize.l
  LinkInfoHeaderSize.l
  LinkInfoFlags.l
  VolumeIDOffset.l
  LocalBasePathOffset.l
  CommonNetworkRelativeLinkOffset.l
  CommonPathSuffixOffset.l
  LocalBasePathOffsetUnicode.l
  CommonPathSuffixOffsetUnicode.l
EndStructure

Structure LocalVolumeTableStr
  Length.l
  TypeOfVolume.l
  VolumeSerialNumber.l
  OffsetOfVolumeName.l
  
EndStructure

Enumeration
  #DRIVE_UNKNOWN
  #DRIVE_NO_ROOT_DIR
  #DRIVE_REMOVABLE
  #DRIVE_FIXED
  #DRIVE_REMOTE
  #DRIVE_CDROM
  #DRIVE_RAMDISK
EndEnumeration

Structure NetworkVolumeStr
  Length.l
  Always2.l
  OffsetNetworkShareName.l
  Reserved0.l
  AlwaysHex20000.l
  
EndStructure


Procedure Usage()
  PrintN("")
  PrintN(" LnkInfo V" + #Version)
  PrintN("")
  PrintN(" usage: LnkInfo filename")
  PrintN("")
  End 1
EndProcedure


Procedure TimeZoneOffset()  ; stolen from rescator, thanks!
  
  Protected result,mode
  
  
  mode = GetTimeZoneInformation_(@TZ.TIME_ZONE_INFORMATION)
  If mode = 1
    result = TZ \ Bias
  ElseIf mode = 2
    result = TZ \ Bias + TZ \ DaylightBias
  EndIf
  
  ProcedureReturn result  * 60
  
EndProcedure


Procedure.q MSTimeToUnixTime(Time.q)
  
  ProcedureReturn Time / 10000000 - 11644473600 - TimeZoneOffset()
  
EndProcedure


If OpenConsole()

  If CountProgramParameters() <> 1 : Usage() : EndIf
  
  Filename$ = ProgramParameter(0)
  
  If ReadFile(0, Filename$)
    
    Define ByteLengthW.w, ByteLengthL.l, CharLength.w
    
    ReadData(0, @ByteLengthL, 4)
    FileSeek(0, 0)
    *Header = AllocateMemory(ByteLengthL)
    If ReadData(0, *Header, ByteLengthL) = ByteLengthL
      
      *ShellLinkHeader.ShellLinkHeaderStr = *Header
      
      If *ShellLinkHeader\CreationTime
        PrintN("CreationTime of the target: " + FormatDate("%dd.%mm.%yyyy %hh:%ii:%ss", MSTimeToUnixTime(*ShellLinkHeader\CreationTime)))
      EndIf
      
      If *ShellLinkHeader\AccessTime
        PrintN("AccessTime of the target  : " + FormatDate("%dd.%mm.%yyyy %hh:%ii:%ss", MSTimeToUnixTime(*ShellLinkHeader\AccessTime)))
      EndIf
      
      If *ShellLinkHeader\WriteTime
        PrintN("WriteTime of the target   : " + FormatDate("%dd.%mm.%yyyy %hh:%ii:%ss", MSTimeToUnixTime(*ShellLinkHeader\WriteTime)))
      EndIf
      
      If *ShellLinkHeader\LinkFlags & (1 << #IsUnicode)
        UniCode = #True
        PeekSFlag = #PB_Unicode
      Else
        UniCode = #False
        PeekSFlag = #PB_Ascii
      EndIf
      
      If *ShellLinkHeader\LinkFlags & (1 << #HasLinkTargetIDList)
        ReadData(0, @ByteLengthW, 2)
        ; skip the LinkTargetIDList for now
        FileSeek(0, Loc(0) + ByteLengthW)
      EndIf
      
      Pos = Loc(0)
      
      If *ShellLinkHeader\LinkFlags & (1 << #HasLinkInfo)
        ReadData(0, @BytelengthL, 4)
        If ByteLengthL > 0
          FileSeek(0, Pos)
          *Buffer = AllocateMemory(ByteLengthL)
          If ReadData(0, *Buffer, ByteLengthL) = ByteLengthL
            *LinkInfo.LinkInfoStr = *Buffer
            Target$ = PeekS(*Buffer + *LinkInfo\LocalBasePathOffset)
            Target$ + PeekS(*Buffer + *LinkInfo\CommonPathSuffixOffset)
            PrintN("Target: " + Target$)
          Else
            End 2
          EndIf
          FreeMemory(*Buffer)
        EndIf
      EndIf
      
      If *ShellLinkHeader\LinkFlags & (1 << #HasName)
        ReadData(0, @ByteLengthW, 2)
        *Buffer = AllocateMemory(ByteLengthW)
        If ReadData(0, *Buffer, ByteLengthW) = ByteLengthW
          PrintN("Description: " + PeekS(*Buffer))
        Else
          End 3
        EndIf
        FreeMemory(*Buffer)
      EndIf
      
      If *ShellLinkHeader\LinkFlags & (1 << #HasRelativePath)
        ReadData(0, @CharLength, 2)
        If UniCode
          ByteLengthW = CharLength * 2
        Else
          ByteLengthW = CharLength
        EndIf
        *Buffer = AllocateMemory(ByteLengthW)
        If ReadData(0, *Buffer, ByteLengthW) = ByteLengthW
          PrintN("RelativePath: " + PeekS(*Buffer, CharLength, PeekSFlag))
        Else
          End 4
        EndIf
        FreeMemory(*Buffer)
      EndIf
      
      If *ShellLinkHeader\LinkFlags & (1 << #HasWorkingDir)
        ReadData(0, @CharLength, 2)
        If UniCode
          ByteLengthW = CharLength * 2
        Else
          ByteLengthW = CharLength
        EndIf
        *Buffer = AllocateMemory(ByteLengthW)
        If ReadData(0, *Buffer, ByteLengthW) = ByteLengthW
          PrintN("WorkingDirectory: " + PeekS(*Buffer, CharLength, PeekSFlag))
        Else
          End 5
        EndIf
        FreeMemory(*Buffer)
      EndIf
      
      If *ShellLinkHeader\LinkFlags & (1 << #HasArguments)
        ReadData(0, @CharLength, 2)
        If UniCode
          ByteLengthW = CharLength * 2
        Else
          ByteLengthW = CharLength
        EndIf
        *Buffer = AllocateMemory(ByteLengthW)
        If ReadData(0, *Buffer, ByteLengthW) = ByteLengthW
          PrintN("CommandlineArguments: " + PeekS(*Buffer, CharLength, PeekSFlag))
        Else
          End 6
        EndIf
        FreeMemory(*Buffer)
      EndIf
      
      If *ShellLinkHeader\LinkFlags & (1 << #HasIconLocation)
        ReadData(0, @CharLength, 2)
        If UniCode
          ByteLengthW = CharLength * 2
        Else
          ByteLengthW = CharLength
        EndIf
        *Buffer = AllocateMemory(ByteLengthW)
        If ReadData(0, *Buffer, ByteLengthW) = ByteLengthW
          PrintN("CustomIcon: " + PeekS(*Buffer, CharLength, PeekSFlag))
        Else
          End 7
        EndIf
        FreeMemory(*Buffer)
      EndIf
      
      ; there is more stuff, but not interresting for now.
    Else
      End 1
    EndIf
    FreeMemory(*Header)
    CloseFile(0)
  Else
    PrintN("Was not able to open " + Filename$)
  EndIf
  
EndIf
Compile it as console program.

Bernd

Re: Get the Shortcut target

Posted: Sat Aug 21, 2010 8:34 pm
by infratec
I updated the file above, since I detected that there are also lnk files with
unicode strings inside.

I also updated the posting with the information links.

Bernd

Re: Get the Shortcut target

Posted: Sat Aug 21, 2010 11:42 pm
by Crusiatus Black
Using the header information provided in the first link, I can successfully obtain the creationdate.
However, PB's FormatDate() always return's 0 for all masks over here. eg:

Code: Select all

FormatDate("%mm/%dd/%yyyy at %hh:%ii:%ss",LnkHeader\qwCreationDate)
will output

Code: Select all

00/00/0000 at 00:00:00
Why is this?

The header is identical to yours, and qwCreationDate is also a quad with the same offset as your
structure. I have not yet tested your console application, but I'll do that and get back here.

I have tested your application and it also returns 00 for everything, here's the qwCreationDate
this shortcut contains. I've tried others, same results.

Code: Select all

Header\CreationTime was 129267286587343750

Re: Get the Shortcut target

Posted: Sun Aug 22, 2010 9:06 am
by infratec
@Crusiatus

I tested it against several of my lnk files.
Sometimes I get the right date, sometimes not.

The MS$ documentation says:
If the value is zero,
there is no creation time set on the link target.
But if I print out the
value, the value is not 0 :?

I'll take a look on this.

Bernd

Re: Get the Shortcut target

Posted: Sun Aug 22, 2010 9:12 am
by infratec
Found something:
The FILETIME structure is a 64-bit value that represents the number of 100-nanosecond intervals
that have elapsed since January 1, 1601, Coordinated Universal Time (UTC).
So we have to modify the value before we can use it, because PB uses 1.1.1970 as start date.

Bernd

Re: Get the Shortcut target

Posted: Sun Aug 22, 2010 10:15 am
by infratec
Updated to V1.03:

I added a procedure to calculate the 'correct' PB time out of MS$ time.

But to my surprise is creation time not the creation time of the link,
it is the creation time of the target.

Bernd

Re: Get the Shortcut target

Posted: Sun Aug 22, 2010 10:28 am
by infratec
Updated to 1.04:

added rescators procedure to get the timezone, because who want's to know
the time in UTC :?:

Bernd

Re: Get the Shortcut target

Posted: Sun Aug 22, 2010 10:56 am
by infratec
Updated to V1.05:

Fixed a bug with UniCode:
The length parameter of PeekS() is always the character length and not the
length of the string in bytes :!:

So I learned something new :mrgreen:
(Yes, it is written in the help of PeekS(), but who reads always everything ? )

Bernd

Re: Get the Shortcut target

Posted: Sun Aug 22, 2010 12:57 pm
by infratec
Updated to V1.06:

I hope that's my last version.
I renamed some stuff to MS$ names.
I made it more robust for different sizes of the structs,
because some members are marked as 'optional', so I can not read in a fixed size.
I had to read in the length which is given inside the file itself.
I made use of pointers which overlaps now the buffer which is read in.
This saves space :mrgreen: and so I can handle the optional structure members.

Bernd

Re: Get the Shortcut target

Posted: Sun Aug 22, 2010 1:53 pm
by oftit
I really can't nothing out???? No output. But I want it as a procedure that only returns the target??? It is too complicated for me??

Re: Get the Shortcut target

Posted: Sun Aug 22, 2010 2:25 pm
by infratec
It is too complicated for me??
Maybe :mrgreen: :mrgreen: :mrgreen:

Or you can not read :!:
Compile it as console program.

Code: Select all

Structure ShellLinkHeaderStr
  HeaderSize.l
  LinkCLSID.a[16]
  LinkFlags.l
  FileAttributes.l
  CreationTime.q
  AccessTime.q
  WriteTime.q
  FileSize.l
  IconIndex.l
  ShowCommand.l
  HotKey.w
  Reserved1.w
  Reserved2.l
  Reserved3.l
EndStructure

Structure LinkInfoStr
  LinkInfoSize.l
  LinkInfoHeaderSize.l
  LinkInfoFlags.l
  VolumeIDOffset.l
  LocalBasePathOffset.l
  CommonNetworkRelativeLinkOffset.l
  CommonPathSuffixOffset.l
  LocalBasePathOffsetUnicode.l
  CommonPathSuffixOffsetUnicode.l
EndStructure


Enumeration ; neccessary LinkFlags
  #HasLinkTargetIDList
  #HasLinkInfo
EndEnumeration


Procedure.s GetLinkTarget(FileName$)

  If ReadFile(0, Filename$)
    
    Define ByteLengthW.w, ByteLengthL.l, CharLength.w
    
    ReadData(0, @ByteLengthL, 4)
    FileSeek(0, 0)
    *Header = AllocateMemory(ByteLengthL)
    If ReadData(0, *Header, ByteLengthL) = ByteLengthL
      
      *ShellLinkHeader.ShellLinkHeaderStr = *Header
      
      If *ShellLinkHeader\LinkFlags & (1 << #HasLinkTargetIDList)
        ReadData(0, @ByteLengthW, 2)
        ; skip the LinkTargetIDList for now
        FileSeek(0, Loc(0) + ByteLengthW)
      EndIf
      
      Pos = Loc(0)
      
      If *ShellLinkHeader\LinkFlags & (1 << #HasLinkInfo)
        ReadData(0, @BytelengthL, 4)
        If ByteLengthL > 0
          FileSeek(0, Pos)
          *Buffer = AllocateMemory(ByteLengthL)
          If ReadData(0, *Buffer, ByteLengthL) = ByteLengthL
            *LinkInfo.LinkInfoStr = *Buffer
            Target$ = PeekS(*Buffer + *LinkInfo\LocalBasePathOffset)
            Target$ + PeekS(*Buffer + *LinkInfo\CommonPathSuffixOffset)
          Else
            Target$ = "Error: A fault occured"
          EndIf
          FreeMemory(*Buffer)
        EndIf
      Else
        Target$ = "Error: " + Filename$ + " has no LinkInfo"
      EndIf
      
    EndIf
      
    FreeMemory(*Header)
    CloseFile(0)
  Else
    Target$ = "Error: Was not able to open " + Filename$
  EndIf
  
  ProcedureReturn Target$
  
EndProcedure

Re: Get the Shortcut target

Posted: Sun Aug 22, 2010 4:36 pm
by Crusiatus Black
Ah, thank you Bernd for the date fix and all the extra information :)

Re: Get the Shortcut target

Posted: Mon Aug 23, 2010 12:13 pm
by oftit
Thank you soooo much. Got it to work now :DDD