Page 1 of 2
Information from *.lnk
Posted: Sun Feb 18, 2018 1:26 am
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.
Re: Information from *.lnk
Posted: Sun Feb 18, 2018 1:57 am
by Dude
Re: Information from *.lnk
Posted: Sun Feb 18, 2018 8:57 am
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

Re: Information from *.lnk
Posted: Sun Feb 18, 2018 3:31 pm
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

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.
Re: Information from *.lnk
Posted: Sun Feb 18, 2018 3:49 pm
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.
Re: Information from *.lnk
Posted: Sun Feb 18, 2018 5:16 pm
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.
Re: Information from *.lnk
Posted: Sun Feb 18, 2018 6:18 pm
by Marc56us

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
Hope this help

Re: Information from *.lnk
Posted: Mon Feb 19, 2018 12:32 am
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")
Re: Information from *.lnk
Posted: Fri Feb 23, 2018 10:24 pm
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.
Re: Information from *.lnk
Posted: Wed Jun 05, 2019 8:14 am
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?
Re: Information from *.lnk
Posted: Wed Jun 05, 2019 12:02 pm
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.

Re: Information from *.lnk
Posted: Wed Jun 05, 2019 12:54 pm
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.

Re: Information from *.lnk
Posted: Wed Jun 05, 2019 8:24 pm
by infratec
I have no 'This PC' icon on my desktop.
So I can not make a link of it.
Re: Information from *.lnk
Posted: Wed Jun 05, 2019 11:06 pm
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
Re: Information from *.lnk
Posted: Thu Jun 06, 2019 2:58 am
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.