How do I create a shortcut whose target is specified by a relative path?


Commenter Richard wonders if there's such a thing as a "relative" shortcut whose target is specified by a path relative to the shortcut itself.

Let's start with a program that creates a normal shortcut. This is a Little Program which does little to no error checking.

#define UNICODE
#define _UNICODE
#define STRICT
#include <windows.h>
#include <shlobj.h>
#include <atlbase.h>
#include <pathcch.h>

int __cdecl wmain(int, wchar_t **)
{
 CCoInitialize init;

 CComPtr<IShellLink> link;
 link.CoCreateInstance(CLSID_ShellLink);

 wchar_t path[MAX_PATH];
 GetModuleFileName(GetModuleHandle(nullptr), path, ARRAYSIZE(path));
 link->SetPath(path);

 PathCchRemoveFileSpec(path, ARRAYSIZE(path));
 PathCchAppend(path, ARRAYSIZE(path), L"Awesome.lnk");

 CComQIPtr<IPersistFile>(link)->Save(path, FALSE);

 return 0;
}

When run, this program creates a shortcut to itself in the same directory as the program.

Here are the changes necessary to make the shortcut remember that the target's location relative to the shortcut itself:

//

It's a trick. Normal shortcuts already remember the target's location relative to the shortcut itself.

However, the relative path is not used until other avenues have been exhausted. To give the relative path more prominence, let's disable the other avenues.

#define UNICODE
#define _UNICODE
#define STRICT
#include <windows.h>
#include <shlobj.h>
#include <atlbase.h>
#include <pathcch.h>

int __cdecl wmain(int, wchar_t **)
{
 CCoInitialize init;

 CComPtr<IShellLink> link;
 link.CoCreateInstance(CLSID_ShellLink);

 // Disable other ways of resolving the shortcut
 CComQIPtr<IShellLinkDataList> dataList(link);
 DWORD flags;
 dataList->GetFlags(&flags);
 flags |= SLDF_FORCE_NO_LINKINFO;
 flags |= SLDF_FORCE_NO_LINKTRACK;
 dataList->SetFlags(flags);

 wchar_t path[MAX_PATH];
 GetModuleFileName(GetModuleHandle(nullptr), path, ARRAYSIZE(path));
 link->SetPath(path);

 PathCchRemoveFileSpec(path, ARRAYSIZE(path));
 PathCchAppend(path, ARRAYSIZE(path), L"Awesome.lnk");

 CComQIPtr<IPersistFile>(link)->Save(path, FALSE);

 return 0;
}

The SLDF_FORCE_NO_LINKINFO flag disables the information the shell normally creates to identify the volume that contains the shortcut target. This is used, for example, if the target was on a network volume, so the shell can reconnect to that volume in order to find the target.

The SLDF_FORCE_NO_LINKTRACK flag disables the information the shell normally creates to identify the object with the assistance of the distributed link tracking service.

Deleting those two pieces of information means that the shell cannot use them to help find the shortcut. If the file doesn't exist at the specified absolute path, then the relative path will be applied to the location of the shortcut itself, and the shell will look for the file at the resulting location.

Bonus chatter: The IShell­Link::Set­RelativePath method is for the benefit of code which stores shortcuts in places other than files. You call IShell­Link::Set­RelativePath before saving the shortcut to memory, passing a path to a location you want to pretend the shortcut file is saved. The shell will remember the path to the shortcut target relative to the path you pass in. Conversely, after you load the shortcut from memory, you call IShell­Link::Set­RelativePath to specify the path to pretend the shortcut was loaded from. The relative path remembered when the shortcut was saved will be applied to this path to help find the target.

Double bonus chatter: You can use a simple pidl to make the shortcut target reside at any path you like.

Comments (6)
  1. Medinoc says:

    So SetRelativePath() actually sets an absolute path from which relative paths will be resolved?

    1. You have to be wearing the correct-colored glasses. SetRelativePath means “Please set the relative path, using this absolute path as a reference point.”

      1. florian says:

        The example from the MSDN documentation does not use SetRelativePath() for a shortcut that is stored in “places other than files”, but for a shortcut stored in a .lnk file. Is this recommended/necessary, or is this carried out automatically if Resolve() is called, and therefore obsolete?

        1. florian says:

          IShellLink knows nothing about whether it comes from an IPersistFile or some other storage facility, so SetRelativePath() before Resolve() should be good practice? But that contradicts your bonus chatter. I’m confused.

          1. Someone says:

            The example for SetRelativePath() (https://msdn.microsoft.com/de-de/library/windows/desktop/bb761054(v=vs.85).aspx) suggests to call it *two* times: After the creation of the link and also before resolving the link.

            The naming as also the description of SetRelativePath() in MSDN is poor. This method does not set a relative path at all, and it is not clear, how the given file name is used. There is only some very vague text (“can be used to help the link resolution process”), but no functional or declarative description, when and how to given filename is evaluated, and when and how it is really used.

  2. Richard says:

    Thank you.
    I guess now I’m nearly famous :)

Comments are closed.

Skip to main content