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

A customer wanted to be able to get the target of a shortcut with environment variables unexpanded. The IShellLink::Get­Path method will expand environment variables.

The way to get the unexpanded target path is to go for the EXP_SZ_LINK data in the shell link data list. We briefly encountered the shell link data list a while back. Now we'll dig in a little more.

#include <windows.h>
#include <shlobj.h>
#include <stdio.h> // Horrors! Mixing stdio and C++!
#include <atlbase.h>
#include <atlalloc.h>

int __cdecl wmain(int argc, wchar_t**argv)
 CCoInitialize init;

 CComPtr<IShellLink> lnk;
 CoCreateInstance(CLSID_ShellLink, 0,
                  CLSCTX_ALL, IID_PPV_ARGS(&lnk));
 CComQIPtr<IPersistFile> pf(lnk);
 pf->Load(argv[1], STGM_READ);

 CComQIPtr<IShellLinkDataList> list(lnk);
 DWORD flags;
 if (flags & SLDF_HAS_EXP_SZ) {
  CHeapPtr<void, CLocalAllocator> rawData;
  list->CopyDataBlock(EXP_SZ_LINK_SIG, &rawData);
  auto linkData = reinterpret_cast<EXP_SZ_LINK *>(static_cast<void *>(rawData));
  printf("Unexpanded target = %ls\n", linkData->swzTarget);
 return 0;

After loading the shortcut file, we ask the IShell­Link­Data­List to inspect the shortcut flags. If the SLDF_HAS_EXP_SZ flag is set, then the path to the target contains an environment variable reference. To get the original unexpanded path, ask for the EXP_SZ_LINK_SIG data block. That returns a data block in the form of a EXP_SZ_LINK structure, from which you can extract the unexpanded paths.

Comments (2)
  1. skSdnW says:

    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.

  2. nathan_works says:

    I do wonder if this was one of the many cases where the customer is asking the wrong question. Here, only for our information, you answered their wrong question (at least, for a blog post) rather than telling us what the customer really wanted to do.

Comments are closed.

Skip to main content