Information from *.lnk

Just starting out? Need help? Post your questions and find answers here.
Lubos
Enthusiast
Enthusiast
Posts: 167
Joined: Tue Feb 03, 2004 12:32 am
Contact:

Information from *.lnk

Post by Lubos »

I need extract information from file shortcut (*.lnk). I want a string containing full path and file name.
I suppose, it is possible after opening file shortcut by PureBasic command, but i don't know how read structure of file shortcut. Thank you for your help.
Windows 7 Professional / Service Pack 1 - 32bit, PureBasic 5.46 LTS (x86)
My mother tongue is Czech. I have a Czech version of Windows.
Who is not afraid of GOTO, the one need not afraid any things!
Dude
Addict
Addict
Posts: 1907
Joined: Mon Feb 16, 2015 2:49 pm

Re: Information from *.lnk

Post by Dude »

Marc56us
Addict
Addict
Posts: 1477
Joined: Sat Feb 08, 2014 3:26 pm

Re: Information from *.lnk

Post by Marc56us »

Lubos wrote:I need extract information from file shortcut (*.lnk). I want a string containing full path and file name.
I suppose, it is possible after opening file shortcut by PureBasic command, but i don't know how read structure of file shortcut. Thank you for your help.
Hi,

If you want only the full name, you can do it simply with RegEx

Code: Select all

; Extract full filename from a .lnk file
; Marc56us - http://mdacme.com - 2015-11-27, 2018-02-18

RegEx.s = "^[A-Z]\:\\[\w\d\:\\\s\.()\-]+"
Source_Dir.s = GetHomeDirectory() + "Desktop\"

If Not CreateRegularExpression(1, RegEx)
     Debug "Bad RegEx. Bye"
EndIf

If ExamineDirectory(0, Source_Dir, "*.lnk")
     Debug ~"Target File from .LNK file\n"
     While NextDirectoryEntry(0)
          If DirectoryEntryType(0) =#PB_DirectoryEntry_File
               File_Name.s = DirectoryEntryName(0)
               If ReadFile(2, Source_Dir + File_Name)
                    While Eof(2) = 0
                         Txt.s = ReadString(2)
                         If MatchRegularExpression(1, Txt)
                              Debug RSet(File_Name, 50, ".") + " > [" + Txt + "]"
                         EndIf
                    Wend   
                    CloseFile(2)   
               EndIf
          EndIf
     Wend
     FinishDirectory(0)
EndIf
:wink:
Lubos
Enthusiast
Enthusiast
Posts: 167
Joined: Tue Feb 03, 2004 12:32 am
Contact:

Re: Information from *.lnk

Post by Lubos »

Marc56us wrote: Hi,

If you want only the full name, you can do it simply with RegEx

Code: Select all

; Extract full filename from a .lnk file
; Marc56us - http://mdacme.com - 2015-11-27, 2018-02-18

; Hi Marc56us,
;Here is my example which use your code:
RegEx.s = "^[A-Z]\:\\[\w\d\:\\\s\.()\-]+"
Source_Dir.s = "C:\\Users\A\Desktop\"

If Not CreateRegularExpression(1, RegEx)
     Debug "Bad RegEx. Bye"
EndIf

If ExamineDirectory(0, Source_Dir, "*.lnk")
     Debug "Target File from .LNK file\n"
     While NextDirectoryEntry(0)
          If DirectoryEntryType(0) =#PB_DirectoryEntry_File
               File_Name.s = DirectoryEntryName(0)
               If ReadFile(2, Source_Dir + File_Name)
                    While Eof(2) = 0
                         Txt.s = ReadString(2)
                         If MatchRegularExpression(1, Txt)
                              Debug RSet(File_Name, 50, ".") + " > [" + Txt + "]"
                         EndIf
                    Wend   
                    CloseFile(2)   
               EndIf
          EndIf
     Wend
     FinishDirectory(0)
EndIf
:wink:
And here is result:

Target File from .LNK file\n
...........................agg25.pb – zástupce.lnk > [C:\Users\]
................................Crimson Editor.lnk > [C:\Program Files\Crimson Editor\cedt.exe]
....................................DirtySplit.lnk > [C:\Users\A\Saved Games\Dreamagination\DirtySplit\DirtySplit.exe]
..........................EVEREST Home Edition.lnk > [C:\Program Files\Lavalys\EVEREST Home Edition\everest.exe]
.........................................Flek!.lnk > [C:\Users\A\Desktop\FLEK\flekwin.exe]
..................FreeCommander.exe – zástupce.lnk > [C:\Program Files\FreeCommander\FreeCommander.exe]
..Hedvábník - Robert Galbraith .pdf – zástupce.lnk > [C:\Users\]
....................................I EXPLORER.lnk > [C:\Program Files\Internet Explorer\iexplore.exe]
.......................launcher.exe – zástupce.lnk > [C:\Program Files\Zakl?na?\launcher.exe]
...................miniREMINDER.txt – zástupce.lnk > [C:\Users\]
.........................mpc-hc.exe – zástupce.lnk > [C:\K-Lite Codec Pack\MPC-HC\mpc-hc.exe]
.....................................MV2Player.lnk > [C:\Program Files\Mv2Player\Mv2PlayerPlus.exe]
...............................Prohlížeč Opera.lnk > [C:\Users\]
......................PureBasic.exe – zástupce.lnk > [C:\Users\]
.........................SolveigMM AVI Trimmer.lnk > [C:\Program Files\Solveig Multimedia\SolveigMM AVI Trimmer\SMM_AVITrimmer.exe]
.............................Subtitle Workshop.lnk > [C:\Program Files\URUSoft\Subtitle Workshop\SubtitleWorkshop.exe]
....................................SumatraPDF.lnk > [C:\Program Files\SumatraPDF\SumatraPDF.exe]

It is clear that the results are not always satisfactory, but thank you for your effort and willingness.
Windows 7 Professional / Service Pack 1 - 32bit, PureBasic 5.46 LTS (x86)
My mother tongue is Czech. I have a Czech version of Windows.
Who is not afraid of GOTO, the one need not afraid any things!
Lubos
Enthusiast
Enthusiast
Posts: 167
Joined: Tue Feb 03, 2004 12:32 am
Contact:

Re: Information from *.lnk

Post by Lubos »

I tried:

Code: Select all

Procedure.s ShortcutTarget(ShortcutFile$)
  Result$ = ""
  
  CoInitialize_(0)
  If CoCreateInstance_(?CLSID_ShellLink, 0, 1,?IID_IShellLink, @ShellLink.IShellLinkA) = #S_OK
    
    If ShellLink\QueryInterface(?IID_IPersistFile, @LinkFile.IPersistFile) = #S_OK
    
      *buffer = AllocateMemory(1000)
      If *buffer
      
        MultiByteToWideChar_(#CP_ACP, 0, @ShortcutFile$, -1, *buffer, 1000) 
        
        If LinkFile\Load(*buffer, 0) = #S_OK
        
          If ShellLink\Resolve(0, 1) = #S_OK
          
            RtlZeroMemory_(*buffer, 1000)
            ShellLink\GetPath(*buffer, 1000, 0, 0)
            Result$ = PeekS(*buffer)              
          
          EndIf
        
        EndIf
      
        FreeMemory(*buffer)
      EndIf
    
      LinkFile\Release()
    EndIf

    ShellLink\Release()  
  EndIf
  
  CoUninitialize_()
  
  ProcedureReturn Result$

  DataSection
    CLSID_ShellLink:
      ; 00021401-0000-0000-C000-000000000046
      Data.l $00021401
      Data.w $0000,$0000      
      Data.b $C0,$00,$00,$00,$00,$00,$00,$46
      
    IID_IShellLink:
      ; DEFINE_SHLGUID(IID_IShellLinkA,         0x000214EEL, 0, 0);
      ; C000-000000000046
      Data.l $000214EE
      Data.w $0000,$0000
      Data.b $C0,$00,$00,$00,$00,$00,$00,$46
      
    IID_IPersistFile:
      ; 0000010b-0000-0000-C000-000000000046
      Data.l $0000010b
      Data.w $0000,$0000
      Data.b $C0,$00,$00,$00,$00,$00,$00,$46
  EndDataSection
EndProcedure

Shortcut$ =  "C:\Users\A\AppData\Roaming\Microsoft\Windows\Recent\001_George Jones - He Stopped Loving Her Today.mp3.lnk "

Target$ = ShortcutTarget(Shortcut$) 
Debug Target$
It gives error in line 14: Bad parametr type - a string is expected

:(

It's probably not your code, and so it's not an explanation. But in any case, thank you for your willingness.
Windows 7 Professional / Service Pack 1 - 32bit, PureBasic 5.46 LTS (x86)
My mother tongue is Czech. I have a Czech version of Windows.
Who is not afraid of GOTO, the one need not afraid any things!
User avatar
skywalk
Addict
Addict
Posts: 3960
Joined: Wed Dec 23, 2009 10:14 pm
Location: Boston, MA

Re: Information from *.lnk

Post by skywalk »

Unicode only.

Code: Select all

; NOTES:
; Header file winsdk-10.
; https://raw.githubusercontent.com/tpn/winsdk-10/master/Include/10.0.10240.0/um/ShObjIdl.h
; EXTERN_C const CLSID CLSID_ShellLink;
; 
; #ifdef __cplusplus
; 
; class DECLSPEC_UUID("00021401-0000-0000-C000-000000000046")
; ShellLink;
; #endif
; #ifndef __IShellLinkW_INTERFACE_DEFINED__
; #define __IShellLinkW_INTERFACE_DEFINED__
; 
; /* interface IShellLinkW */
; /* [unique][object][uuid] */ 
; 
; 
; EXTERN_C const IID IID_IShellLinkW;
; 
; #if defined(__cplusplus) && !defined(CINTERFACE)
;     
;     MIDL_INTERFACE("000214F9-0000-0000-C000-000000000046")
;     IShellLinkW : public IUnknown
;     {
;     public:
;         virtual HRESULT STDMETHODCALLTYPE GetPath( 
;             /* [size_is][string][out] */ __RPC__out_ecount_full_string(cch) LPWSTR pszFile,
;             /* [in] */ int cch,
;             /* [unique][out][in] */ __RPC__inout_opt WIN32_FIND_DATAW *pfd,
;             /* [in] */ DWORD fFlags) = 0;
Procedure.s SHL_Shortcut_Path(ShortcutFile$)
  ; Sicro, http://www.purebasic.fr/german/viewtopic.php?f=6&t=23674
  ; Unicode only.
  Protected ShellLink.IShellLinkW
  Protected LinkFile.IPersistFile
  Protected.i nBytes = #MAX_PATH * SizeOf(Character) + 2
  Protected.s ShortCutPath$ = Space(#MAX_PATH + 2)
  CoInitialize_(0)
  If CoCreateInstance_(?CLSID_ShellLink, 0, 1,?IID_IShellLink, @ShellLink) = #S_OK
    If ShellLink\QueryInterface(?IID_IPersistFile, @LinkFile) = #S_OK
      If LinkFile\Load(ShortcutFile$, 0) = #S_OK
        If ShellLink\Resolve(0, 1) = #S_OK
          ShellLink\GetPath(@ShortCutPath$, nBytes, 0, 0)
        EndIf
      EndIf
      LinkFile\Release()
    EndIf
    ShellLink\Release()
  EndIf
  CoUninitialize_()
  ProcedureReturn ShortCutPath$
  DataSection
    CLSID_ShellLink:
    ; 00021401-0000-0000-C000-000000000046
    Data.l $00021401
    Data.w $0000,$0000
    Data.b $C0,$00,$00,$00,$00,$00,$00,$46
    IID_IShellLink:
    ; DEFINE_SHLGUID(IID_IShellLinkW, 0x000214F9L, 0, 0); C000-000000000046
    Data.l $000214F9
    Data.w $0000,$0000
    Data.b $C0,$00,$00,$00,$00,$00,$00,$46
    IID_IPersistFile: 
    ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms687223(v=vs.85).aspx
    ; 0000010b-0000-0000-C000-000000000046
    Data.l $0000010b
    Data.w $0000,$0000
    Data.b $C0,$00,$00,$00,$00,$00,$00,$46
  EndDataSection
EndProcedure
Debug SHL_Shortcut_Path("C:\dev\favorites\dev.lnk")
EDIT: Updated code with Bisonte's post of Sicro's code showing the IShellLinkW GUID.
Last edited by skywalk on Mon Feb 19, 2018 3:14 am, edited 2 times in total.
The nice thing about standards is there are so many to choose from. ~ Andrew Tanenbaum
Marc56us
Addict
Addict
Posts: 1477
Joined: Sat Feb 08, 2014 3:26 pm

Re: Information from *.lnk

Post by Marc56us »

:oops: You're right Lubos, it's an old program I did in 2015, and it doesn't take all the cases.
It works for me (but not for all UNC). Character set ? (my Windows is in french)
I'll bend over again (and try not to fall) when I'll have time
In the meantime, here is another version that can also extract from LNK file using UNC paths.
(It contains the same bug)

Code: Select all

; Extract full filename from a .lnk file
; Marc56us - http://mdacme.com - 2015-11-27, 2018-02-18
; Standard and UNC version 

RegEx.s = "^([A-Z]\:\\|\\\\)[\w\d\:\\\s\.()\-]+" 

Source_Dir.s = GetHomeDirectory() + "Desktop\"

If Not CreateRegularExpression(1, RegEx)
     Debug "Bad RegEx. Bye."
Else
     If OpenWindow(0, x, y, 800, 600, "", #PB_Window_SystemMenu | #PB_Window_ScreenCentered) 
          ListIconGadget(1, 10, 10, 780, 580, "#", 50, #PB_ListIcon_GridLines)
          AddGadgetColumn(1, 1, "LNK File", 280)
          AddGadgetColumn(1, 2, "Target", 430)
          
          If ExamineDirectory(0, Source_Dir, "*.lnk")
               While NextDirectoryEntry(0)
                    If DirectoryEntryType(0) =#PB_DirectoryEntry_File
                         File_Name.s = DirectoryEntryName(0)
                         If ReadFile(2, Source_Dir + File_Name)
                              While Eof(2) = 0     
                                   Txt.s = ReadString(2)
                                   If MatchRegularExpression(1, Txt)
                                        i + 1
                                        AddGadgetItem(1, -1, Str(i) + Chr(10) + File_Name + Chr(10) + txt)
                                   EndIf
                              Wend   
                              CloseFile(2)   
                         EndIf
                    EndIf
               Wend
               FinishDirectory(0)
          EndIf
          
          Repeat : Until WaitWindowEvent(50) = #PB_Event_CloseWindow      
     EndIf
EndIf
I like to reinvent the wheel :P
Hope this help :wink:
User avatar
Bisonte
Addict
Addict
Posts: 1226
Joined: Tue Oct 09, 2007 2:15 am

Re: Information from *.lnk

Post by Bisonte »

From Sicro at the german forum :

Code: Select all

Procedure.s GetShellLinkTargetPath(ShellLinkFilePath.s)
  
  ; Sicro - German forum : http://www.purebasic.fr/german/viewtopic.php?f=6&t=23674
  
  Protected RetVal.s = Space(#MAX_PATH + 1)
  Protected LinkFile.IPersistFile
  
  CompilerSelect #PB_Compiler_Unicode
    CompilerCase #True:  Protected ShellLink.IShellLinkW
    CompilerCase #False: Protected ShellLink.IShellLinkA
  CompilerEndSelect
  
  CoInitialize_(0)
  
  If CoCreateInstance_(?CLSID_ShellLink, 0, 1, ?IID_IShellLink, @ShellLink) = #S_OK
    If ShellLink\QueryInterface(?IID_IPersistFile, @LinkFile) = #S_OK
      If LinkFile\Load(ShellLinkFilePath, 0) = #S_OK
        If ShellLink\Resolve(0, 1) = #S_OK
          CompilerSelect #PB_Compiler_Unicode
            CompilerCase #True:  ShellLink\GetPath(@RetVal, #MAX_PATH, 0, 0)
            CompilerCase #False: ShellLink\GetPath(RetVal, #MAX_PATH, 0, 0)
          CompilerEndSelect
        EndIf
      EndIf
      LinkFile\Release()
    EndIf
    ShellLink\Release()
  EndIf
  
  CoUninitialize_()
  
  ProcedureReturn RetVal
  
EndProcedure
DataSection
  
  CLSID_ShellLink:
  ; 00021401-0000-0000-C000-000000000046
  Data.l $00021401
  Data.w $0000,$0000
  Data.b $C0,$00,$00,$00,$00,$00,$00,$46
  
  IID_IShellLink:
  CompilerIf #PB_Compiler_Unicode ; IID_IShellLinkW : 000214F9-0000-0000-C000000000000046
    Data.l $000214F9
  CompilerElse                    ; IID_IShellLinkA : 000214EE-0000-0000-C000000000000046
    Data.l $000214EE
  CompilerEndIf
  Data.w $0000,$0000
  Data.b $C0,$00,$00,$00,$00,$00,$00,$46
  
  IID_IPersistFile:
  ; 0000010b-0000-0000-C000-000000000046
  Data.l $0000010b
  Data.w $0000,$0000
  Data.b $C0,$00,$00,$00,$00,$00,$00,$46
  
EndDataSection

Debug GetShellLinkTargetPath("C:\Users\Public\Desktop\Skype.lnk")
PureBasic 6.04 LTS (Windows x86/x64) | Windows10 Pro x64 | Asus TUF X570 Gaming Plus | R9 5900X | 64GB RAM | GeForce RTX 3080 TI iChill X4 | HAF XF Evo | build by vannicom​​
English is not my native language... (I often use DeepL to translate my texts.)
Lubos
Enthusiast
Enthusiast
Posts: 167
Joined: Tue Feb 03, 2004 12:32 am
Contact:

Re: Information from *.lnk

Post by Lubos »

Thanks to everyone

Thank you "Marc56us" for the inspirational use of regular express and the willingness to solve my problem. I also thank "skywalk" but I have to admit that his solution goes beyond my modest knowledge. Finally, I chose a solution from "Bisonte". The recommended procedure is applicable in my program practically without modification. Thank you very much.
Windows 7 Professional / Service Pack 1 - 32bit, PureBasic 5.46 LTS (x86)
My mother tongue is Czech. I have a Czech version of Windows.
Who is not afraid of GOTO, the one need not afraid any things!
BarryG
Addict
Addict
Posts: 3266
Joined: Thu Apr 18, 2019 8:17 am

Re: Information from *.lnk

Post by BarryG »

Hi, the code just above by Bisonte (who got it from Sicro) doesn't work for a shortcut on my desktop that I made to "This PC":

Code: Select all

Debug GetShellLinkTargetPath("C:\Users\{my-user-name}\Desktop\This PC.lnk") ; Returns null
From what I can see when I open the LNK file in a hex editor, it should return this:

Code: Select all

{20D04FE0-3AEA-1069-A2D8-08002B30309D}
Because when I put this into the Windows "Run" dialog, it opens "This PC":

Code: Select all

Shell:::{20D04FE0-3AEA-1069-A2D8-08002B30309D}
Any suggestions on how to get this string from GetShellLinkTargetPath() instead of null?
infratec
Always Here
Always Here
Posts: 6810
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Information from *.lnk

Post by infratec »

How should we know the detaiuls of your lnk file :?: :?: :?:

If you really want a solution, you have to provide a link where somone can download this file.
Then it is possible that someone looks into your problem.

:wink:
BarryG
Addict
Addict
Posts: 3266
Joined: Thu Apr 18, 2019 8:17 am

Re: Information from *.lnk

Post by BarryG »

infratec wrote:How should we know the detaiuls of your lnk file
Because I already told you what it is. :) Right-click the "This PC" icon on your desktop, and make a shortcut of it. That shortcut is the LNK file.

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

Re: Information from *.lnk

Post by infratec »

I have no 'This PC' icon on my desktop.
So I can not make a link of it.
RASHAD
PureBasic Expert
PureBasic Expert
Posts: 4622
Joined: Sun Apr 12, 2009 6:27 am

Re: Information from *.lnk

Post by RASHAD »

Modifying a snippet by "infratec"
Take it as a start and you can go from here

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,-1,#PB_Ascii)
            Target$ + PeekS(*Buffer + *LinkInfo\CommonPathSuffixOffset,-1,#PB_Ascii)
          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

target$ = "This_PC.lnk"
link$ = "C:\Users\Public\Desktop\"+target$
If FileSize(link$) < 0
  link$ = GetHomeDirectory()+"Desktop\"+target$
  If FileSize(link$) < 0
    Debug "Error : Link not found"
    link$ = ""
  EndIf
EndIf
If link$ <> ""
  Debug GetLinkTarget(link$)
EndIf

Egypt my love
BarryG
Addict
Addict
Posts: 3266
Joined: Thu Apr 18, 2019 8:17 am

Re: Information from *.lnk

Post by BarryG »

infratec wrote:I have no 'This PC' icon on my desktop.
It's Windows 10. On other versions, it's "Computer" or "My Computer". Just the icon that opens your PC info to show your drives, library folders, and so on.

https://support.microsoft.com/en-au/hel ... ow-this-pc

@Rashad: Thank you, I'll see what I can do.
Post Reply