Lazarus

Programming => Operating Systems => Windows => Topic started by: jojo86 on February 24, 2019, 01:37:51 pm

Title: Problem with GetShellLinkInfo
Post by: jojo86 on February 24, 2019, 01:37:51 pm
Hi,
I have found this topic : http://forum.lazarus-ide.org/index.php?topic=1709.0 (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
Title: Re: Problem with GetShellLinkInfo
Post by: Bart 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
Title: Re: Problem with GetShellLinkInfo
Post by: jojo86 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 (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 (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)
Title: Re: Problem with GetShellLinkInfo
Post by: ASerge 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
Title: Re: Problem with GetShellLinkInfo
Post by: jojo86 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) (https://www.casimages.com/i/190301031240312624.jpg.html)

And the path returned by your code :
(https://nsa40.casimages.com/img/2019/03/01/190301031510293709.jpg) (https://www.casimages.com/i/190301031510293709.jpg.html)
Title: Re: Problem with GetShellLinkInfo
Post by: marcov 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 (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)
Title: Re: Problem with GetShellLinkInfo
Post by: ASerge 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.
Title: Re: Problem with GetShellLinkInfo
Post by: ASBzone 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 (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)


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


I'll have to test this on Windows 10 and see...
Title: Re: Problem with GetShellLinkInfo
Post by: jojo86 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...
Title: Re: Problem with GetShellLinkInfo
Post by: jojo86 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 (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)


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 !
Title: Re: Problem with GetShellLinkInfo
Post by: Remy Lebeau 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? (https://blogs.msdn.microsoft.com/oldnewthing/20160229-00/?p=93093)

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() (https://msdn.microsoft.com/en-us/library/windows/desktop/ms724265(v=vs.85).aspx) to expand the actual path.
Title: Re: Problem with GetShellLinkInfo
Post by: ASBzone 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.
Title: Re: Problem with GetShellLinkInfo
Post by: Remy Lebeau 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?
TinyPortal © 2005-2018