Why does my shortcut to a nonexistent file end up with spaces changed to underscores?


A customer wanted to create shortcuts to a network drive that hadn't yet been mapped. The idea is that they would create these shortcuts pointing at a network drive N: and deploy them. When the user logs in, a script will map the N: drive to an appropriate network server where the files will exist. The customer cannot have the shortcuts point directly at the server via a UNC because the UNC connection requires special credentials that users won't have.

The customer found that if they tried to create the shortcut to a nonexistent network drive, the path was being corrupted. Specifically, spaces were being changed to underscores. Here's a sample program. (Error checking elided for expository purposes.)

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

int __cdecl wmain(int, wchar_t**)
{
  CCoInitialize init;
  CComPtr<IShellLink> link;
  CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
                   IID_PPV_ARGS(&link));
  link->SetPath(L"N:\\dir\\some file that doesn't exist.txt");
  CComQIPtr<IPersistFile>(link)->Save(L"C:\\test\\test.lnk", TRUE);
  return 0;
}

Why is the shortcut being saved incorrectly?

The short answer is that the shortcut is being saved incorrectly because you set an invalid path. When you call Set­Path and pass a path to something that doesn't exist, the shortcut code panics and says, "Oh no, what is this thing?" It then goes into a series of compatibility heuristics to try to make sense of the invalid parameter.

  • Did the caller erroneously put quotation marks around the path? If so, then remove them.
  • Maybe the caller intended to link to a program but forgot the .exe extension. See if adding .exe helps.
  • Maybe the caller intended to link to a program but didn't pass a fully-qualified path. Search the path for a matching program and use the first one you find, if any.
  • Maybe the caller used slashes instead of backslashes. Fix any wayward slashes.
  • Maybe, maybe, maybe…

These are all heuristics and should not be relied upon.

The heuristic that is triggering in this case is one that says, "Well, I see a drive letter, and the drive won't tell me if it supports long file names, so I'll assume it doesn't, and I'm going to replace characters that aren't legal in short file names with underscores. Maybe that'll help." It doesn't help, but the damage is done. The spaces became underscores.

The critical step is the fact that there is no N: drive, which means that when the code checks whether the volume in drive N: supports long file names, the answer is "Volume? What volume?" That's why this occurs only when you try to create a shortcut to a nonexisting volume. If you try to create a shortcut to a nonexistent file on an existing volume, then this heuristic won't kick in, because the existing volume supports long file names.

The heuristics are constantly being tweaked. In the Windows 10 Creators Update, the heuristic about short file names was removed, presumably on the theory that everybody worth supporting supports long file names now.

Okay, so what is the correct thing to do if you want to create a shortcut to a file that doesn't exist yet?

Instead of using IShell­Link::Set­Path, use IShell­Link::Set­ID­List with a simple pidl.

int __cdecl wmain(int, wchar_t**)
{
  CCoInitialize init;
  CComPtr<IShellLink> link;
  CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
                   IID_PPV_ARGS(&link));

  WIN32_FIND_DATAW fd = {};

  // it's a file (not a directory)
  fd.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;

  CComHeapPtr<ITEMIDLIST_ABSOLUTE> simplePidl;
  CreateSimplePidl(&fd,
    L"N:\\dir\\some file that doesn't exist.txt",
    &simplePidl);

  link->SetIDList(simplePidl);

  CComQIPtr<IPersistFile>(link)->Save(L"C:\\test\\test.lnk", TRUE);
  return 0;
}

A simple pidl lets you talk about something that might not exist. We use it here to create a shortcut to a file that might not exist.

Comments (16)

  1. Gee Law says:

    I clicked the link to “It appears that some programmers think that Windows already ships with that time machine the Research division is working on”, and found that Microsoft Office was another offender that creates shortcut before resources it needs have been installed.

    Ping back: https://geelaw.blog/entries/desktop-app-tile-colour/

  2. Kevin says:

    Now I wonder what happens when the script breaks one day, the user clicks the shortcut, the drive is not mapped, and the shortcut fixup code gets confused. Would it just declare the shortcut “broken” and delete it?

    1. alegr1 says:

      It’s worse.

      The ever so wise “Desktop cleanup service” or whatever it’s called, runs in non-user account.
      If it encounters a shortcut to a network location (or user-specific mapped drive), it considers the shortcut pointing to a non-accessible location and deletes it. Smart, isn’t it?

  3. Peter Doubleday says:

    I’m probably missing something here, but why not just save the short-cut as the (completely unreachable) byte-by-byte string? After all, that’s what you would do if the short-cut was, in fact, reachable.

    You’d have to go through the same set of heuristics either way, and it’s not obvious to me why you’d want to elide the information given in the original (unreachable) path.

  4. Surely, any change that fails to accomplish the desired result should be backed out before proceeding?

  5. Joshua says:

    Good riddance. Short name demands turn up from time to time, but restricted characters seems to be only CDROMs these days.

  6. florian says:

    “… the heuristic about short file names was removed, presumably on the theory that everybody worth supporting supports long file names now.”

    “Long” file names means up to “MAX_PATH==260”, right?

    I sometimes wonder what’s the best practice for a program running on a system with “HKLM\SYSTEM\CurrentControlSet\Control\FileSystem→LongPathsEnabled==1”, an application manifest specifying “longPathAware==true”, that is using Windows Shell API functionality.

    Should any shell-related operations for “long-long (cch==32767)” file names fail, if they can’t be converted to “long (MAX_PATH)” file names? Will MAX_PATH be redefined to 32767, one day? Will there be a superset of Shell API functions (like, “SHAddToRecentDocs[A|W]L”, “ExtractIcon[A|W]L”, etc.), to support “long-long” file names?

    Human beings have very limited capabilities: we can only perceive a very small spectrum of light rays and audio frequencies, and we have restricted working memory (maybe Raymond you are the exception of the rule, here). So I believe a human being can’t do any really reasonable work with a collection of files with “long-long” file names, and I do not worry too much about my programs lacking support. Of course, 8.3 was too short, but 32767 excels our brain capacities, somehow.

    I would really appreciate your opinion on this topic!

    1. DWalker07 says:

      “So I believe a human being can’t do any really reasonable work with a collection of files with “long-long” file names,” … “32767 excels our brain capacities, somehow.”

      Yes, if you were manually creating these paths all at once, or typing them. But it’s EASY to get past 260 or so path characters when you have well-designed deep folder hierarchies (perhaps on a shared network drive), each level of which has a descriptive folder name… maybe including a 100-character division name, a 100-character department name, a 100-character project name, and some multi-level folders that contain project files. We don’t memorize the 32K path name.

      Computer programs can also create deep paths.

      1. florian says:

        But you can remember 327 subunits of 100 chars, each? :)

        Working memory of human brains seems to have only has a limited number of “slots” (likely <10, maybe 7±2). When thinking, we need to abstract facts and "merge" their slots, to make free slots for new information. Dealing with path names of 32767 characters kind of "binds too many resources", as it's more difficult to abstract that much information.

        Opposite to all technical possibilities, we live in the age of the ultra-short "twitter messages", even for news of world political importance.

        My point is that it was nice to overcome the low technical limits of the early computer systems, but not everything that is technically possible also makes sense with our sensory and mental abilities.

        I agree that sometimes 1024, or even 4096, would be nice to have. But since Windows 95, it's only been about one or two times that I hit a "path name too long" error – but maybe it was when copying files to a NAS with some weird limit, I don't remember any more – I have since reused that slot :)

    2. Gee Law says:

      I don’t think so… I believe “long file name” is the opposite of 8.3 naming convention, which dictates that each name in a path be 8.3 styled. “cch=32767” is “long path”.

    3. AndyCadley says:

      Given the way the existing support has been added in, I’d expect the W versions of functions to eventually all be extended to remove the MAX_PATH limitations.

        1. florian says:

          They are … what? Able to handle paths longer than MAX_PATH? But for many functions, the MAX_PATH limit is still mentioned on MSDN, for example SHGetFileInfo.

  7. Chris Crowther says:

    I can not read “pidl” without smirking.

    1. laonianren says:

      Oddly enough, pidls are made of shitemid.

  8. Reziac says:

    I misread that as “It then goes into a series of compatibility hysterics” but soon discovered that it didn’t change the meaning one whit. :)

Skip to main content