{"id":111885,"date":"2025-12-19T07:00:00","date_gmt":"2025-12-19T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=111885"},"modified":"2025-12-19T14:53:41","modified_gmt":"2025-12-19T22:53:41","slug":"20251219-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20251219-00\/?p=111885","title":{"rendered":"A shortcut gives me a weird path for a program shortcut that doesn&#8217;t point to the executable, so what is it?"},"content":{"rendered":"<p>Say you have a shortcut file and you want to see what its target is. The natural thing to do is to call <code>IShellLink::<wbr \/>GetPath<\/code>, which returns <code>S_FALSE<\/code> if the shortcut does not target a path, or returns <code>S_OK<\/code> and a path to the target. But for some shortcuts, it returns <code>S_OK<\/code> but produces a weird path to a file inside the <tt>C:\\Windows\\<wbr \/>Installer<\/tt> directory that is definitely not the shortcut target. So what is it?<\/p>\n<p>You&#8217;re seeing a backward compatibility consequence of shortcuts to MSI applications.<\/p>\n<p>The Microsoft Windows Installer (MSI) was introduced in Windows 2000. It supports various types of installation functionality, but the interesting one here is install-on-demand. A shortcut to an MSI-installed application is not a shortcut to an executable, but rather it is a shortcut that encodes the MSI information to identify an MSI application. When the user double-clicks the shortcut, the shortcut file asks MSI to get the application ready to run, which involves installing the application and all its dependencies (if they aren&#8217;t already installed), and possibly verifying that the files are not corrupted and performing self-repair. Only after everything is ready to go does the shortcut actually launch the program. This install-on-demand behavior means that if the program hasn&#8217;t been installed, there is no target to execute.<\/p>\n<p>The initial implementation of MSI shortcuts had the <code>Get\u00adPath<\/code> method ask MSI for the application&#8217;s executable, and MSI would go off and say, &#8220;Hang on, I need to install it first,&#8221; and that might display some installation UI, possibly asking the user to reinsert the original install media if the package is not cached. We discovered during testing that lots of programs ask for the application&#8217;s executable path without any intent to launch the resulting application. As a result, you would find that your system would suddenly go bonkers installing every program on the Start menu just because some program wanted to enumerate the contents of the Start menu and get information about all of the shortcuts. These sorts of program are popular in managed environments where the system administrator wrote a tool to take an inventory of all the programs that have been installed across their organization. This also means that <code>Get\u00adPath<\/code> can&#8217;t return <code>S_FALSE<\/code> to say &#8220;This does not refer to a file,&#8221; because those inventory programs insisted that they get a file path back so they can determine, say, how many of their users have <tt>CONTOSO.EXE<\/tt> installed.<\/p>\n<p>The <code>IShellLink::<wbr \/>GetPath<\/code> and <code>IShellLink::<wbr \/>Resolve<\/code> methods were modified so that they didn&#8217;t actually give you the path to the executable (which would trigger an on-demand install) but rather gave you the path to a placeholder file.\u00b9 This placeholder file usually had just enough information to keep most programs happy with what they found. The executable name matched that of the final program, like <tt>WINWORD.EXE<\/tt>, which tended to satisfy a lot of system inventory-type programs. And that placeholder also contained the icon for the final program, so that programs that tried to be fancy and show an icon for the program got the icon they wanted (even if not from the file they thought they were getting it from).<\/p>\n<p>You can detect that you are being lied to by checking the shortcut flags returned by <code>IShell\u00adLink\u00adData\u00adList::<wbr \/>Get\u00adFlags<\/code> for the <code>SLDF_<wbr \/>HAS_<wbr \/>DARWIN\u00adID<\/code> flag. Darwin was the code name for what became the Microsoft Windows Installer.<\/p>\n<p>Once you realize that you have an MSI shortcut, you can obtain more information about them by calling <code>Msi\u00adGet\u00adShortcut\u00adTarget<\/code>, which will give you the MSI product code, feature ID, and component code that the shortcut refers to. You can pass the product code and component code to <code>Msi\u00adGet\u00adComponent\u00adPath<\/code> to get the path to the component if it is installed.\u00b2<\/p>\n<p>If you are a real glutton for punishment, you can get the raw MSI descriptor by calling <code>IShell\u00adLink\u00adData\u00adList::<wbr \/>Copy\u00adData\u00adBlock<\/code> and asking for the <code>EXP_<wbr \/>DARWIN_<wbr \/>ID_<wbr \/>SIG<\/code> data block. The descriptor is returned in the <code>EXP_<wbr \/>DARWIN_<wbr \/>LINK<\/code>&#8216;s <code>szDarwinID<\/code> and <code>szwDarwinID<\/code> members. What can you do with that descriptor? I have no idea, but you did ask for it.<\/p>\n<p>\u00b9 The <code>Resolve<\/code> method lets you override this by passing the <code>SLR_<wbr \/>INVOKE_<wbr \/>MSI<\/code> flag, which means &#8220;No really, I want you to install the shortcut target.&#8221; Use with extreme caution or you will be one of those programs that causes the user&#8217;s machine to go berzerk installing every program on the Start menu.<\/p>\n<p>\u00b2 Note that the component path is not the same as what the shortcut will launch when you double-click it. The shortcut includes command line arguments that the MSI package specified when it defined the shortcut.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>It&#8217;s a placeholder because the shortcut is to an MSI application.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-111885","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>It&#8217;s a placeholder because the shortcut is to an MSI application.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111885","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=111885"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111885\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=111885"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=111885"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=111885"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}