Recent

Author Topic: Problem with GetShellLinkInfo  (Read 2626 times)

jojo86

  • Jr. Member
  • **
  • Posts: 52
Problem with GetShellLinkInfo
« on: February 24, 2019, 01:37:51 pm »
Hi,
I have found this topic : http://forum.lazarus-ide.org/index.php?topic=1709.0
But I have a problem with GetShellLinkInfo.

PathName return me "Programmes files (x86)" for a shortcut, but the real path is : C:\Program Files\Google\Google Earth Pro\client

This procedure can't see the diference between the real Program Files (x86) and the Program Files.
How can I solve my problem ?
Thanks you
« Last Edit: February 24, 2019, 02:50:53 pm by jojo86 »

Bart

  • Hero Member
  • *****
  • Posts: 3452
    • Bart en Mariska's Webstek
Re: Problem with GetShellLinkInfo
« Reply #1 on: February 24, 2019, 02:36:48 pm »
Without code it is impossble to say.
Note that when you run a 32-bit program %ProgramFiles% will return the (x86) variant.

So, show us the code and tell if you use 32 or 64 bit compiler.

Bart

jojo86

  • Jr. Member
  • **
  • Posts: 52
Re: Problem with GetShellLinkInfo
« Reply #2 on: February 24, 2019, 02:57:02 pm »
Without code it is impossble to say.
Note that when you run a 32-bit program %ProgramFiles% will return the (x86) variant.

So, show us the code and tell if you use 32 or 64 bit compiler.

Bart
Hi did you looked on the link ?
And this on too : http://www.informit.com/articles/article.aspx?p=26940&seqNum=4

But I can post the code here :
Code: Pascal  [Select]
  1. unit WinShell;
  2.  
  3. interface
  4.  
  5. uses SysUtils, Windows, Registry, ActiveX, ShlObj;
  6.  
  7. type
  8.  EShellOleError = class(Exception);
  9.  
  10.  TShellLinkInfo = record
  11.   PathName: string;
  12.   Arguments: string;
  13.   Description: string;
  14.   WorkingDirectory: string;
  15.   IconLocation: string;
  16.   IconIndex: integer;
  17.   ShowCmd: integer;
  18.   HotKey: word;
  19.  end;
  20.  
  21.  TSpecialFolderInfo = record
  22.   Name: string;
  23.   ID: Integer;
  24.  end;
  25.  
  26. const
  27.  SpecialFolders: array[0..29] of TSpecialFolderInfo = (
  28.   (Name: 'Alt Startup'; ID: CSIDL_ALTSTARTUP),
  29.   (Name: 'Application Data'; ID: CSIDL_APPDATA),
  30.   (Name: 'Recycle Bin'; ID: CSIDL_BITBUCKET),
  31.   (Name: 'Common Alt Startup'; ID: CSIDL_COMMON_ALTSTARTUP),
  32.   (Name: 'Common Desktop'; ID: CSIDL_COMMON_DESKTOPDIRECTORY),
  33.   (Name: 'Common Favorites'; ID: CSIDL_COMMON_FAVORITES),
  34.   (Name: 'Common Programs'; ID: CSIDL_COMMON_PROGRAMS),
  35.   (Name: 'Common Start Menu'; ID: CSIDL_COMMON_STARTMENU),
  36.   (Name: 'Common Startup'; ID: CSIDL_COMMON_STARTUP),
  37.   (Name: 'Controls'; ID: CSIDL_CONTROLS),
  38.   (Name: 'Cookies'; ID: CSIDL_COOKIES),
  39.   (Name: 'Desktop'; ID: CSIDL_DESKTOP),
  40.   (Name: 'Desktop Directory'; ID: CSIDL_DESKTOPDIRECTORY),
  41.   (Name: 'Drives'; ID: CSIDL_DRIVES),
  42.   (Name: 'Favorites'; ID: CSIDL_FAVORITES),
  43.   (Name: 'Fonts'; ID: CSIDL_FONTS),
  44.   (Name: 'History'; ID: CSIDL_HISTORY),
  45.   (Name: 'Internet'; ID: CSIDL_INTERNET),
  46.   (Name: 'Internet Cache'; ID: CSIDL_INTERNET_CACHE),
  47.   (Name: 'Network Neighborhood'; ID: CSIDL_NETHOOD),
  48.   (Name: 'Network Top'; ID: CSIDL_NETWORK),
  49.   (Name: 'Personal'; ID: CSIDL_PERSONAL),
  50.   (Name: 'Printers'; ID: CSIDL_PRINTERS),
  51.   (Name: 'Printer Links'; ID: CSIDL_PRINTHOOD),
  52.   (Name: 'Programs'; ID: CSIDL_PROGRAMS),
  53.   (Name: 'Recent Documents'; ID: CSIDL_RECENT),
  54.   (Name: 'Send To'; ID: CSIDL_SENDTO),
  55.   (Name: 'Start Menu'; ID: CSIDL_STARTMENU),
  56.   (Name: 'Startup'; ID: CSIDL_STARTUP),
  57.   (Name: 'Templates'; ID: CSIDL_TEMPLATES));
  58.  
  59. function CreateShellLink(const AppName, Desc: string; Dest: Integer): string;
  60. function GetSpecialFolderPath(Folder: Integer; CanCreate: Boolean): string;
  61. procedure GetShellLinkInfo(const LinkFile: WideString;
  62.  var SLI: TShellLinkInfo);
  63. procedure SetShellLinkInfo(const LinkFile: WideString;
  64.  const SLI: TShellLinkInfo);
  65.  
  66. implementation
  67.  
  68. uses ComObj;
  69.  
  70. function GetSpecialFolderPath(Folder: Integer; CanCreate: Boolean): string;
  71. var
  72.  FilePath: array[0..MAX_PATH] of char;
  73. begin
  74.  { Get path of selected location }
  75.  SHGetSpecialFolderPathW(0, FilePath, Folder, CanCreate);
  76.  Result := FilePath;
  77. end;
  78.  
  79. function CreateShellLink(const AppName, Desc: string; Dest: Integer): string;
  80. { Creates a shell link for application or document specified in }
  81. { AppName with description Desc. Link will be located in folder }
  82. { specified by Dest, which is one of the string constants shown }
  83. { at the top of this unit. Returns the full path name of the  }
  84. { link file. }
  85. var
  86.  SL: IShellLink;
  87.  PF: IPersistFile;
  88.  LnkName: WideString;
  89. begin
  90.  OleCheck(CoCreateInstance(CLSID_ShellLink, nil, CLSCTX_INPROC_SERVER,
  91.   IShellLink, SL));
  92.  { The IShellLink implementer must also support the IPersistFile }
  93.  { interface. Get an interface pointer to it. }
  94.  PF := SL as IPersistFile;
  95.  OleCheck(SL.SetPath(PChar(AppName))); // set link path to proper file
  96.  if Desc <> '' then
  97.   OleCheck(SL.SetDescription(PChar(Desc))); // set description
  98.  { create a path location and filename for link file }
  99.  LnkName := GetSpecialFolderPath(Dest, True) + '\' +
  100.        ChangeFileExt(AppName, 'lnk');
  101.  PF.Save(PWideChar(LnkName), True);     // save link file
  102.  Result := LnkName;
  103. end;
  104.  
  105. procedure GetShellLinkInfo(const LinkFile: WideString;
  106.  var SLI: TShellLinkInfo);
  107. { Retrieves information on an existing shell link }
  108. var
  109.  SL: IShellLink;
  110.  PF: IPersistFile;
  111.  FindData: TWin32FindData;
  112.  AStr: array[0..MAX_PATH] of char;
  113. begin
  114.  OleCheck(CoCreateInstance(CLSID_ShellLink, nil, CLSCTX_INPROC_SERVER,
  115.   IShellLink, SL));
  116.  { The IShellLink implementer must also support the IPersistFile }
  117.  { interface. Get an interface pointer to it. }
  118.  PF := SL as IPersistFile;
  119.  { Load file into IPersistFile object }
  120.  OleCheck(PF.Load(PWideChar(LinkFile), STGM_READ));
  121.  { Resolve the link by calling the Resolve interface function. }
  122.  OleCheck(SL.Resolve(0, SLR_ANY_MATCH or SLR_NO_UI));
  123.  { Get all the info! }
  124.  with SLI do
  125.  begin
  126.   OleCheck(SL.GetPath(AStr, MAX_PATH, FindData, SLGP_SHORTPATH));
  127.   PathName := AStr;
  128.   OleCheck(SL.GetArguments(AStr, MAX_PATH));
  129.   Arguments := AStr;
  130.   OleCheck(SL.GetDescription(AStr, MAX_PATH));
  131.   Description := AStr;
  132.   OleCheck(SL.GetWorkingDirectory(AStr, MAX_PATH));
  133.   WorkingDirectory := AStr;
  134.   OleCheck(SL.GetIconLocation(AStr, MAX_PATH, IconIndex));
  135.   IconLocation := AStr;
  136.   OleCheck(SL.GetShowCmd(ShowCmd));
  137.   OleCheck(SL.GetHotKey(HotKey));
  138.  end;
  139. end;
  140.  
  141. procedure SetShellLinkInfo(const LinkFile: WideString;
  142.  const SLI: TShellLinkInfo);
  143. { Sets information for an existing shell link }
  144. var
  145.  SL: IShellLink;
  146.  PF: IPersistFile;
  147. begin
  148.  OleCheck(CoCreateInstance(CLSID_ShellLink, nil, CLSCTX_INPROC_SERVER,
  149.   IShellLink, SL));
  150.  { The IShellLink implementer must also support the IPersistFile }
  151.  { interface. Get an interface pointer to it. }
  152.  PF := SL as IPersistFile;
  153.  { Load file into IPersistFile object }
  154.  OleCheck(PF.Load(PWideChar(LinkFile), STGM_SHARE_DENY_WRITE));
  155.  { Resolve the link by calling the Resolve interface function. }
  156.  OleCheck(SL.Resolve(0, SLR_ANY_MATCH or SLR_UPDATE or SLR_NO_UI));
  157.  { Set all the info! }
  158.  with SLI, SL do
  159.  begin
  160.   OleCheck(SetPath(PChar(PathName)));
  161.   OleCheck(SetArguments(PChar(Arguments)));
  162.   OleCheck(SetDescription(PChar(Description)));
  163.   OleCheck(SetWorkingDirectory(PChar(WorkingDirectory)));
  164.   OleCheck(SetIconLocation(PChar(IconLocation), IconIndex));
  165.   OleCheck(SetShowCmd(ShowCmd));
  166.   OleCheck(SetHotKey(HotKey));
  167.  end;
  168.  PF.Save(PWideChar(LinkFile), True);  // save file
  169. end;
  170.  
  171. end.

To use it, just add WinShell to the uses of my main form and :
Code: Pascal  [Select]
  1. procedure TForm1.Test;
  2. var MyVar:TShellLinkInfo;
  3. begin
  4.   WinShell.GetShellLinkInfo(FileNames[i],MyVar);
  5. Showmessage(MyVar.PathName);
  6. end;
  7.  

Exemple of my problem :
""
Code: Pascal  [Select]
  1. procedure TForm1.Test;
  2. var MyVar:TShellLinkInfo;
  3. begin
  4.   WinShell.GetShellLinkInfo('C:\Program Files\Google\Google Earth Pro\client\googleearth.exe',MyVar);
  5. Showmessage(MyVar.PathName);//Return : C:\Program Files (x86)\Google\Google Earth Pro\client\googleearth.exe
  6. end;
  7.  

I have found this subject on microsoft forums, but no issues for lazarus :
https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/6f2e7920-50a9-459d-bfdd-316e459e87c0/ishelllink-getpath-returns-wrong-folder-for-64-bit-application-when-called-from-32-bit-application?forum=windowsgeneraldevelopmentissues

ASerge

  • Hero Member
  • *****
  • Posts: 1300
Re: Problem with GetShellLinkInfo
« Reply #3 on: February 24, 2019, 07:58:11 pm »
If I understand correctly, the 32-bit program runs in 64-bit Windows, and the link is made to 64-bit program.
First, the file name in your example does not look like a link filename.
Second, maybe the path in the link contains environment variables?
Example (on my computer, 32bit FPC on Win 64x)
Code: Pascal  [Select]
  1. {$APPTYPE CONSOLE}
  2. {$MODE OBJFPC}
  3.  
  4. uses ActiveX, ShlObj, ComObj;
  5.  
  6. function ShowLinkPath(const LinkName: string): string;
  7. var
  8.   Common: IUnknown;
  9.   Link: IShellLinkW;
  10.   Persist: IPersistFile;
  11.   Buffer: array[0..260] of WideChar;
  12. begin
  13.   Result := '';
  14.   Common := CreateComObject(CLSID_ShellLink);
  15.   Link := Common as IShellLinkW;
  16.   Persist := Common as IPersistFile;
  17.   if (Persist.Load(PWideChar(UnicodeString(LinkName)), STGM_READ) = S_OK) and
  18.     (Link.GetPath(Buffer, Length(Buffer), nil, SLGP_RAWPATH) = S_OK) then  // SLGP_RAWPATH - not expand env!
  19.   begin
  20.     Result := string(UnicodeString(Buffer));
  21.   end;
  22. end;
  23.  
  24. begin
  25.   Writeln(ShowLinkPath('c:\Program Files\Microsoft Games\Chess\ChessMCE.lnk'));
  26.   Readln;
  27. end.
Show
Quote
%ProgramFiles%\Microsoft Games\chess\chess.exe

jojo86

  • Jr. Member
  • **
  • Posts: 52
Re: Problem with GetShellLinkInfo
« Reply #4 on: March 01, 2019, 03:10:12 pm »
If I understand correctly, the 32-bit program runs in 64-bit Windows, and the link is made to 64-bit program.
First, the file name in your example does not look like a link filename.
Second, maybe the path in the link contains environment variables?
Example (on my computer, 32bit FPC on Win 64x)
Code: Pascal  [Select]
  1. {$APPTYPE CONSOLE}
  2. {$MODE OBJFPC}
  3.  
  4. uses ActiveX, ShlObj, ComObj;
  5.  
  6. function ShowLinkPath(const LinkName: string): string;
  7. var
  8.   Common: IUnknown;
  9.   Link: IShellLinkW;
  10.   Persist: IPersistFile;
  11.   Buffer: array[0..260] of WideChar;
  12. begin
  13.   Result := '';
  14.   Common := CreateComObject(CLSID_ShellLink);
  15.   Link := Common as IShellLinkW;
  16.   Persist := Common as IPersistFile;
  17.   if (Persist.Load(PWideChar(UnicodeString(LinkName)), STGM_READ) = S_OK) and
  18.     (Link.GetPath(Buffer, Length(Buffer), nil, SLGP_RAWPATH) = S_OK) then  // SLGP_RAWPATH - not expand env!
  19.   begin
  20.     Result := string(UnicodeString(Buffer));
  21.   end;
  22. end;
  23.  
  24. begin
  25.   Writeln(ShowLinkPath('c:\Program Files\Microsoft Games\Chess\ChessMCE.lnk'));
  26.   Readln;
  27. end.
Show
Quote
%ProgramFiles%\Microsoft Games\chess\chess.exe

This work doesn't works too...
Please look on my problem because I'm not sure that you undestood well...

So :
My Link properties :
(https://nsa40.casimages.com/img/2019/03/01/190301031240312624.jpg)

And the path returned by your code :
(https://nsa40.casimages.com/img/2019/03/01/190301031510293709.jpg)

marcov

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 7230
Re: Problem with GetShellLinkInfo
« Reply #5 on: March 01, 2019, 03:52:59 pm »
Seems to be a common problem. Googling for "shortcut getpath gives wrong program files"  gives amongst others this this

ASerge

  • Hero Member
  • *****
  • Posts: 1300
Re: Problem with GetShellLinkInfo
« Reply #6 on: March 01, 2019, 07:27:03 pm »
This work doesn't works too...
Please look on my problem because I'm not sure that you undestood well...
The graphical shell (which you give) does not get the answer how path is written in the .lnk file. It always expands environment variables. So try my code or even just "type PathtoLink.lnk" in the command line and in the resulting garbage you can try to see the contents of your shortcut.
If used %ProgramFiles%, you will have to go the hard way - replace the variable with different values (C:\Program Files or C:\Program Files (x86)) and check the real existence of the file at the specified path.

ASBzone

  • Full Member
  • ***
  • Posts: 226
  • Automation leads to relaxation...
    • BrainWaveCC Utilities
Re: Problem with GetShellLinkInfo
« Reply #7 on: March 01, 2019, 07:37:02 pm »
Seems to be a common problem. Googling for "shortcut getpath gives wrong program files"  gives amongst others this this


Wow... That is a mind-boggling article.


I'll have to test this on Windows 10 and see...
-ASB: https://www.BrainWaveCC.com

Lazarus v2.0.3 r61485 / FPC v3.2.0-beta-r42306 (via FpcUpDeluxe) -- Windows 64-bit install w/32-bit cross-compile
Primary System: Windows 10 Pro x64, Version 1903 (Build 18362.116)
Other Systems: Windows 10 Pro x64, Version 1809 or greater

jojo86

  • Jr. Member
  • **
  • Posts: 52
Re: Problem with GetShellLinkInfo
« Reply #8 on: March 02, 2019, 03:41:57 pm »
This work doesn't works too...
Please look on my problem because I'm not sure that you undestood well...
The graphical shell (which you give) does not get the answer how path is written in the .lnk file. It always expands environment variables. So try my code or even just "type PathtoLink.lnk" in the command line and in the resulting garbage you can try to see the contents of your shortcut.
If used %ProgramFiles%, you will have to go the hard way - replace the variable with different values (C:\Program Files or C:\Program Files (x86)) and check the real existence of the file at the specified path.
The pics that I shared are from your code... So your code give me the same problem...

jojo86

  • Jr. Member
  • **
  • Posts: 52
Re: Problem with GetShellLinkInfo
« Reply #9 on: March 02, 2019, 03:42:46 pm »
Seems to be a common problem. Googling for "shortcut getpath gives wrong program files"  gives amongst others this this


Wow... That is a mind-boggling article.


I'll have to test this on Windows 10 and see...
If you could try on win 10 why not, I would want to see if you have the same result as me...
Thank for testing !

Remy Lebeau

  • Hero Member
  • *****
  • Posts: 626
    • Lebeau Software
Re: Problem with GetShellLinkInfo
« Reply #10 on: March 06, 2019, 12:19:30 am »
I just found this:

How can I get the original shortcut target path with environment variables unexpanded?

Quote
IShellLink will automatically save c”:\program files\…” as “%ProgramFiles%\…” in this extra block but this causes WOW64 issues when a 64-bit app reads this information if it was written by a 32-bit app, %ProgramFiles% will expand to the wrong directory. The shortcut properties change icon feature has this issue for example.

So, as the article explains, you could try retrieving the link's EXP_SZ_LINK data block, which will give you the unexpanded target path, and then you can replace %ProgramFiles% with %ProgramFiles(x86)% or %ProgramW6432% as needed, before then using ExpandEnvironmentStrings() to expand the actual path.
« Last Edit: March 06, 2019, 12:22:52 am by Remy Lebeau »
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) - Admin, Developer (Support forum)

ASBzone

  • Full Member
  • ***
  • Posts: 226
  • Automation leads to relaxation...
    • BrainWaveCC Utilities
Re: Problem with GetShellLinkInfo
« Reply #11 on: March 06, 2019, 03:50:25 am »
If you could try on win 10 why not, I would want to see if you have the same result as me...
Thank for testing !

Okay, so I tested on Microsoft Windows [Version 10.0.17763.195]

This is the x64 edition of Windows 10 Pro, version 1809.

I ran the program looking for: Writeln(ShowLinkPath('C:\Program Files\Classic Shell\Start Menu Settings.lnk'));

And I received: C:\Program Files\Classic Shell\ClassicStartMenu.exe

So, on my system, the code worked properly.
-ASB: https://www.BrainWaveCC.com

Lazarus v2.0.3 r61485 / FPC v3.2.0-beta-r42306 (via FpcUpDeluxe) -- Windows 64-bit install w/32-bit cross-compile
Primary System: Windows 10 Pro x64, Version 1903 (Build 18362.116)
Other Systems: Windows 10 Pro x64, Version 1809 or greater

Remy Lebeau

  • Hero Member
  • *****
  • Posts: 626
    • Lebeau Software
Re: Problem with GetShellLinkInfo
« Reply #12 on: March 06, 2019, 09:23:55 pm »
Okay, so I tested on Microsoft Windows [Version 10.0.17763.195]

This is the x64 edition of Windows 10 Pro, version 1809.

I ran the program looking for: Writeln(ShowLinkPath('C:\Program Files\Classic Shell\Start Menu Settings.lnk'));

And I received: C:\Program Files\Classic Shell\ClassicStartMenu.exe

So, on my system, the code worked properly.

But, did you perform your test using a 32bit or 64bit app?
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) - Admin, Developer (Support forum)