Instead of trying to figure out what shortcut class to use, just ask the shell to do it for you


If a shell namespace item has the SFGAO_LINK attribute, then it is a shortcut to another location. The most common type of shortcut is the .lnk file, which you can load by creating the CLSID_Shell­Link object and using IPersist­File::Load, but what if you have some other type of shortcut? How do you know what CLSID to use?

Since anybody can create their own shortcut file types, a hard-coded list mapping file extensions to CLSIDs is not going to work for long. But fortunately, you don't have to know how to look up the CLSID for a particular shortcut; you can just ask the namespace to do it for you by asking for the IShell­Link UI object.

#include <windows.h>
#include <shlobj.h>
#include <ole2.h>
#include <stdio.h>
#include <tchar.h>
#include <shellapi.h>

// GetUIObjectOfFile function incorporated by reference

int __cdecl _tmain()
{
  int argc;
  LPWSTR *argv = CommandLineToArgvW(GetCommandLineW(), &argc);
  if (argv == NULL || argc != 2) return 0;
  if (SUCCEEDED(CoInitialize(NULL))) {
    IShellLink *psl;
    if (SUCCEEDED(GetUIObjectOfFile(NULL, argv[1], IID_PPV_ARGS(&psl)))) {
      TCHAR sz[MAX_PATH];
      if (SUCCEEDED(psl->GetPath(sz, MAX_PATH, NULL, 0))) {
        _tprintf(TEXT("-> %ls\n"), sz);
      }
      else _tprintf(TEXT("GetPath failed\n"));
      psl->Release();
     }
     else _tprintf(TEXT("GetUIObjectOf failed\n"));
    CoUninitialize();
  }
  LocalFree(argv);
  return 0;
}

I've limited myself to files here for simplicity of exposition, and I assume that you've passed a fully-qualified path on the command line. Of course, you can have shortcuts to non-file objects as well, and for those shortcuts, IShell­Link::Get­Path is unlikely to return an actual file path. (In fact, for things like shortcuts to the Control Panel, they're unlikely to return anything at all.) I've also used the Command­Line­To­ArgvW function instead of the built-in argc and argv because the Get­UI­Object­Of­File function wants a Unicode file name, but the C runtime's argv is a TCHAR * string, which might not be Unicode.

Let's take this program for a spin.

Warning: I am using hard-coded paths. In real life, you would use appropriate functions to obtain the paths to the files you care about. (Actually, in real life, you probably will have a pidl to the item rather than a path, so the issue of paths disappears.)

>set STARTMENU=%APPDATA%\Microsoft\Windows\Start Menu\Programs
>scratch "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Accessories\Calculator.lnk"
-> C:\Windows\System32\calc.exe

>scratch "%STARTMENU%\Internet Explorer.lnk"
-> C:\Program Files\Internet Explorer\iexplore.exe

Okay, these are your regular .lnk files, so there's nothing special going on here. Let's try something fancier, like a symbolic link.

>echo > blah.txt
>mklink other blah.txt
symbolic link created for other <<===>> blah.txt

>scratch "%CD%\other"
-> C:\test\blah.txt

Via the Add Network Location wizard, I created a network location (which is internally represented as a Folder Shortcut). Let's see what happens with that:

> scratch "%APPDATA%\Microsoft\Windows\Network Shortcuts\Tools"
-> \\live.sysinternals.com\tools

How about Internet shortcuts?

> scratch "%USERPROFILE%\Favorites\MSN Websites\MSN.url"
-> http://go.microsoft.com/fwlink/?LinkId=54729

OneClick shortcuts? (MS Space is an internal application which lets you view floor plans of every Microsoft building, book conference rooms, reserve touchdown space, that sort of thing.)

> scratch "%STARTMENU%\MS Space.appref-ms"
GetUIObjectOf failed

Huh? What happened?

It so happens that the people who wrote the shortcut handler for OneClick applications only bothered to implement the Unicode version of the IShell­Link interface. We built our application as ANSI, so our attempt to get the IShell­LinkA interface failed. But that's easily worked around:

#define _UNICODE
#define UNICODE
#include <windows.h>
#include <shlobj.h>
#include <ole2.h>
...

(In real life, your program would probably first ask for the Unicode interface, and if the call fails, then ask for the ANSI interface.)

With the Unicode version of the program, the shortcut resolves:

> scratch "%STARTMENU%\MS Space.appref-ms"
-> C:\Users\...\MSSpaceDeploy.exe

(I elided some of the ugly path because, well, it's ugly. The full unabbreviated path is 139 characters, most of which is just hex digits.)

Anyway, the point for today wasn't the minutiae of obtaining shortcut targets from shell namespace items. It was the principle that if you want something from the shell namespace, the IShell­Folder::Get­UI­Object­Of method will often get it for you.

Comments (17)
  1. MH says:

    It would have been better if the GetUIObjectOf() implementation used public API's exposed by win32 instead of hiding the implementation internally.

    The described way is sufficient for most needs but windows also permits anyone to implement their own IShellFolders in which you might want to mimic some std IShellFolder behaviour.

    [I'm confused. The public API is IShellFolder::GetUIObjectOf! Just delegate the call from your own IShellFolder::GetUIObjectOf to the shell folder you want to mimic. -Raymond]
  2. MH says:

    How do I get hold of a std IShellFolder implementation for contents that is beneath a custom NSE?

    ( Prefferable without copying the contents to another folder (if they are normal files ) and query that.  )

    [Remember, this Web site is not for developer support. I'll consider this a topic suggestion. In the meantime, you might want to study the Parsing with Parameters sample in the SDK. -Raymond]
  3. MH says:

    Ok, that's fair, I was not after any support just commenting on your first comment to delegate the call.

  4. unekdoud says:

    I actually had to look up the meaning of "elided".

  5. Maurits says:

    I actually had to look up the meaning of "elided".

    I tried to look it up but someone had torn that page out of my dictionary.

    the C runtime's argv is a TCHAR * string, which might not be Unicode.

    For _tmain this is true, but you could use wmain.

    We built our application as ANSI

    For the love of God, Montresor!

  6. Rup says:

    I had some code refused review a few years back because I'd written "elided" in a comment and the reviewer had to look it up.

  7. Kyle S. says:

    Thanks for choosing not to elide the name of the internal app, Raymond. Tidbits like that are interesting to outsiders like me.

  8. David Walker says:

    Nice word, "elided".  Just like "eschew"; it's not used often enough.

  9. Alexandre Grigoriev says:

    @DW:

    "eschew" = "chew your way to escape"? Not?

  10. Yuhong Bao says:

    Why not use wmain?

  11. Brandon Paddock says:

    The preferred way to do this is to use IShellItem::BindToHandler.  Then you need not concern yourself with creating the item's parent or dealing with PIDLs directly.

    pseudo-code:

    CComPtr<IShellItem> spItem;

    HRESULT hr = SHCreateItemFromParsingName(pszPath, NULL, IID_PPV_ARGS(&spItem));

    if (SUCCEEDED(hr))

    {

       CComPtr<IShellLink> spLink

       hr = spItem->BindToHandler(NULL, BHID_SFUIObject, IID_PPV_ARGS(&spLink));

    }

    Or in this case you can do BindToHandler with BHID_LinkTargetItem, and get another IShellItem pointing to the target of the link.

  12. 640k says:

    main() is the c/c++ standard. anything else is proprietary and non-portable.

    Instead of implementing new char types (wchar_t), the compiler should have used char as a unicode character, instead of wchar_t. wchar_t is still not enough for all unicode character. Currently you should use ansi because "ms unicode" cannot handle true unicode anyway. Using ms unicode will not solve the problem, only make your code non-portable, and not solving the problem anyway.

  13. make it so says:

    Remember, this Web site is not for developer support.

    Yes it is. You are just stubborn.

  14. @640k, which brand of time machine would you recommend?

  15. Ivo says:

    main() is the c/c++ standard. anything else is proprietary and non-portable.

    Then what do you say about CommandLineToArgvW or GetUIObjectOfFile? If you care about portability, _tmain is the least of your problems. :)

  16. Dean Harding says:

    @640k needs to look up "UTF-16"

  17. Mike Dimmick says:

    @640k:

    >Instead of implementing new char types (wchar_t), the compiler should have used char as a unicode character, instead of wchar_t.

    You lost that argument in 1995 with the publishing of ISO 9899 Amendment 1, where wchar_t was defined.

    The C standard has always said that the basic unit of measure is a 'char' – sizeof is defined in terms of chars – and redefining char to be a 16-bit quantity would have the unfortunate side effect of being unable to manipulate bytes.

Comments are closed.