2

I noticed that the method IShellLinkW::GetPath returns an empty string if the target of a shortcut points to a special folder, like 'My PC', or a Control Panel applet.

Sample code

' CLSID_ShellLink from ShlGuid.h
Dim cShellLink As New CShellLink() 

' https://msdn.microsoft.com/en-us/library/windows/desktop/ms687223%28v=vs.85%29.aspx
Dim persistFile As IPersistFile = DirectCast(cShellLink, IPersistFile) 
persistFile.Load(lnkFilePath, 0)

Dim target As New StringBuilder(260)

' https://learn.microsoft.com/en-us/windows/desktop/api/shobjidl_core/nn-shobjidl_core-ishelllinkw
Dim lnkW As IShellLinkW = DirectCast(cShellLink, IShellLinkW)
lnkW.GetPath(target, target.Capacity, Nothing, IShellLinkGetPathFlags.RawPath)

( Sorry, are too many P/Invokes to share here, but you can find them in this repository. )

The code above works correctly to retrieve the target of any .lnk file that points to a file or directory. It can't retrieve a target that points to special item / virtual folders.

My questions are:

Is this by design?. If not, what I'm missing or doing wrong? (maybe are my definitions wrong?). And what I have to do in order to retrieve the special target?. Note that I would like to retrieve the target to display it in a PropertyGrid, and also to create a new target with a clone of the special target.

Please note that the programming language on which I developed this code does not matter, because I'm firstly asking for orientation, and also I accept any solution written in C#.

ElektroStudios
  • 19,105
  • 33
  • 200
  • 417
  • 1
    Paths only work for file system objects. You'd have to get familiar with PIDLs, IShellLinkW::GetIDList() returns them. Not recommended. – Hans Passant May 27 '19 at 21:39
  • 1
    See [SHGetPathFromIDList](https://docs.microsoft.com/en-us/windows/desktop/api/shlobj_core/nf-shlobj_core-shgetpathfromidlistw), [SHGetFileInfo](https://docs.microsoft.com/en-us/windows/desktop/api/shellapi/nf-shellapi-shgetfileinfow) (note that it accepts a `LPCWSTR` but also a `pidl`), [SHGetSpecialFolderLocation](https://docs.microsoft.com/en-us/windows/desktop/api/shlobj_core/nf-shlobj_core-shgetspecialfolderlocation). The `IShellLinkW` interface also provides a `.Resolve()` method that may come in handy. Use it on the same shell object, then get the `out` address returned by `GetIDList()` – Jimi May 28 '19 at 00:03
  • Thanks for the comments, but If I call **IShellLinkW.GetIDList**, then **SHGetPathFromIDListW** with the (valid, non-zero) PIDL obtained, I always get an empty string, and the function returns **false** but last win32 error code is zero (**SetLastError** is set to **true**). Why?. I tried various signatures, also the ones from pinvoke.net website. – ElektroStudios May 28 '19 at 00:37
  • Also, I don't get what is the point of using **SHGetFileInfo**, because it seems I only could obtain the filename (**SHGFI_DISPLAYNAME**) and the file type description (**SHGFI_TYPENAME**). By the way, this function accepts the PIDL that I obtain with **IShellLinkW.GetIDList**, but as I mentioned It seems I can't retrieve the 'raw' lnk target string with this function... just the filename or the file type's description. – ElektroStudios May 28 '19 at 00:39
  • Analyzing these lnk files in a hexadecimal editor, they have a CLSID for the target (eg. **::{F02C1A0D-BE21-4350-88B0-7367FC96EF3C}** which points to My PC), and If I use that CLSID when creating the shortcut calling SetPath (without messing with SetIDList and anything trivial), it works as expected. This discover seems makes the problem easier to solve, because I imagine I can obtain the CLSID from the PIDL. PIDL is a concept I didn't read much at all, so excuse me if I'm saying stupid things. But I will still investigating through the available win32 functions to get the CLSID value... – ElektroStudios May 28 '19 at 01:12
  • After I analyzed the purpose of each documented SH* function, I still don't know how to do this.Also,I'm not sure if the PIDL returned by **IShellLinkW.GetIDList** is an 'absolute' or 'relative' PIDL, which confuses me more,and I don't see a way to determine which of the three types of PIDL it is, appart from calling **SHGetRealIDL** and forget. Also, I tried **SHGetPathFromIDListEx** function but I have the same exact problems as I described when calling **SHGetPathFromIDList**. Damn, I just need to obtain the CLSID written in the lnk file...I don't see how to retrieve this CLSID from a PIDL. – ElektroStudios May 28 '19 at 02:25

1 Answers1

2

As mentioned in the comments box of the main post, in order to retrieve the raw target of a shell link that points to a special, non-file system path (eg. 'My PC'), first we need to retrieve the PIDL by calling IShellLinkW::GetIDList().

Next, we pass that (absolute)PIDL to SHGetNameFromIDList with the flag SIGDN_DESKTOPABSOLUTEPARSING, and finally this will return a CLSID string that we can specify for the target of a .lnk file with IShellLinkW::SetPath(). It works as expected.

Note that I'm not sure if there could exist special scenarios (rare targets of a lnk file) on which this could not work as expected. I didn't found that error case.

And if needed, the normal display name can be obtained with the flag SIGDN_NORMALDISPLAY, or also by calling the SHGetFileInfo function as mentioned too in the comments box of the main post.


Credits for the users that suggested me the PIDL retrieval and its usage, because I was not sure about what to investigate in order to solve this problem.

I also will give credits to Vanara open-source library, that helped me to find the right P/Invokes of every SH* function in their source-code, specially the signature for SHGetNameFromIDList.

ElektroStudios
  • 19,105
  • 33
  • 200
  • 417
  • 1
    Many ways to get the DisplayName from a `pidl`, some exposed by the ShellItem interface (`GetDisplayName()`, for example), for bound items - relative or absolute. What you use is related to the context and the type of path. One note about the marshaller: you often see in sample code the use of `Marshal.AllocHGlobal`. You should use [Marshal.AllocCoTaskMem](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.alloccotaskmem) (native [CoTaskMemAlloc](https://docs.microsoft.com/en-us/windows/desktop/api/combaseapi/nf-combaseapi-cotaskmemalloc)) to free as described. – Jimi May 28 '19 at 11:46
  • 1
    If you see someone use [SHGetMalloc](https://learn.microsoft.com/en-us/windows/desktop/api/shlobj_core/nf-shlobj_core-shgetmalloc), to initialize a `IMalloc` Interface, that's old code: that method is superseded by `CoTaskMemAlloc`. – Jimi May 28 '19 at 11:50
  • 1
    Thanks for comment and for the tips!. Maybe the next is off-topic but I would like to say that for me **WindowsAPICodePack** seems a much better (consistent) source-code to take as example (and copy) any interface related with IShellItem/IShellFolder/IShellView and etc. Anyways, Vanara has a vast collection of P/Invokes and that is really useful in some cases. – ElektroStudios May 28 '19 at 12:18