Get the Shortcut target

Just starting out? Need help? Post your questions and find answers here.
oftit
User
User
Posts: 52
Joined: Sun Apr 25, 2010 5:08 am

Get the Shortcut target

Post 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
infratec
Always Here
Always Here
Posts: 6866
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Get the Shortcut target

Post 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
Last edited by infratec on Sat Aug 21, 2010 8:41 pm, edited 2 times in total.
infratec
Always Here
Always Here
Posts: 6866
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Get the Shortcut target

Post 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
Last edited by infratec on Sun Aug 22, 2010 12:53 pm, edited 12 times in total.
infratec
Always Here
Always Here
Posts: 6866
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Get the Shortcut target

Post 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
User avatar
Crusiatus Black
Enthusiast
Enthusiast
Posts: 389
Joined: Mon May 12, 2008 1:25 pm
Location: The Netherlands
Contact:

Re: Get the Shortcut target

Post 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
Image
Bas Groothedde,
Imagine Programming

I live in a philosophical paradoxal randome filled with enigma's!
infratec
Always Here
Always Here
Posts: 6866
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Get the Shortcut target

Post 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
infratec
Always Here
Always Here
Posts: 6866
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Get the Shortcut target

Post 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
infratec
Always Here
Always Here
Posts: 6866
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Get the Shortcut target

Post 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
infratec
Always Here
Always Here
Posts: 6866
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Get the Shortcut target

Post by infratec »

Updated to 1.04:

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

Bernd
infratec
Always Here
Always Here
Posts: 6866
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Get the Shortcut target

Post 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
infratec
Always Here
Always Here
Posts: 6866
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Get the Shortcut target

Post 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
oftit
User
User
Posts: 52
Joined: Sun Apr 25, 2010 5:08 am

Re: Get the Shortcut target

Post 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??
infratec
Always Here
Always Here
Posts: 6866
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Get the Shortcut target

Post 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
User avatar
Crusiatus Black
Enthusiast
Enthusiast
Posts: 389
Joined: Mon May 12, 2008 1:25 pm
Location: The Netherlands
Contact:

Re: Get the Shortcut target

Post by Crusiatus Black »

Ah, thank you Bernd for the date fix and all the extra information :)
Image
Bas Groothedde,
Imagine Programming

I live in a philosophical paradoxal randome filled with enigma's!
oftit
User
User
Posts: 52
Joined: Sun Apr 25, 2010 5:08 am

Re: Get the Shortcut target

Post by oftit »

Thank you soooo much. Got it to work now :DDD
Post Reply