{"id":107807,"date":"2023-02-08T07:00:00","date_gmt":"2023-02-08T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=107807"},"modified":"2023-02-07T20:45:23","modified_gmt":"2023-02-08T04:45:23","slug":"20230208-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230208-00\/?p=107807","title":{"rendered":"How can I get the original target of a shortcut without applying any 32-bit adjustments?"},"content":{"rendered":"<p>If 64-bit code creates a shortcut to, say, <code>C:\\Program Files\\Contoso\\Contoso.exe<\/code>, and 32-bit code tries to read the target of that shortcut, the 32-bit code is told that the shortcut target is <code>C:\\Program Files <u>(x86)<\/u>\\Contoso\\Contoso.exe<\/code>. What&#8217;s going on, and how can I get the original path?<\/p>\n<p>What&#8217;s going on is that when you create a shortcut, the shortcut code says, &#8220;Oh, this path is relative to the current <code>%ProgramFiles%<\/code>. Let me remember that.&#8221; Later, when you open the shortcut, the shortcut code says, &#8220;Oh, this is an environment variable-relative path, so I&#8217;ll expand it relative to the current value of <code>%ProgramFiles%<\/code>.&#8221;<\/p>\n<p>Recording paths as relative to environment variables and special folders is handy when the shortcut moves to another machine with a different directory structure, or if the directory itself moves. For example, you might move your Documents folder to a new location. Tracking a shortcut target by its location relative to the <code>FOLDERID_<wbr \/>Documents<\/code> special folder keeps those shortcuts working even after you&#8217;ve moved it.<\/p>\n<p>This feature does get in the way sometimes, such as if you want to get the original path without all this &#8220;assistance&#8221;.<\/p>\n<p>We start with a program that prints the path stored in a shortcut.<\/p>\n<pre>#include &lt;windows.h&gt;\r\n#include &lt;winrt\/base.h&gt;\r\n#include &lt;shlobj.h&gt;\r\n\r\nint wmain(int argc, wchar_t** argv) try\r\n{\r\n    winrt::init_apartment(winrt::apartment_type::single_threaded);\r\n\r\n    auto link = winrt::create_instance&lt;IShellLinkW&gt;(CLSID_ShellLink);\r\n    winrt::check_hresult(link.as&lt;IPersistFile&gt;()-&gt;Load(argv[1], STGM_READ));\r\n\r\n    wchar_t buffer[MAX_PATH];\r\n    WIN32_FIND_DATA wfd;\r\n    winrt::check_hresult(link-&gt;GetPath(buffer, MAX_PATH, &amp;wfd, 0));\r\n    printf(\"Path is %ls\\n\", buffer);\r\n} catch (...) {\r\n    printf(\"Error: %ls\\n\" winrt::to_message().c_str());\r\n    return winrt::to_hresult();\r\n}\r\n<\/pre>\n<p>This simple program takes the path to a shortcut file as its command line parameter and prints the target as a path. If you compile this as a 32-bit program and give it the path to a shortcut created from a 64-bit program, then paths that point into <code>C:\\Program Files<\/code> are written as <code>C:\\Program Files (x86)<\/code>.<\/p>\n<p>Let&#8217;s disable that &#8220;helpful&#8221; conversion.<\/p>\n<pre>int wmain(int argc, wchar_t** argv) try\r\n{\r\n    winrt::init_apartment(winrt::apartment_type::single_threaded);\r\n\r\n    auto link = winrt::create_instance&lt;IShellLinkW&gt;(CLSID_ShellLink);\r\n    winrt::check_hresult(link.as&lt;IPersistFile&gt;()-&gt;Load(argv[1], STGM_READ));\r\n\r\n    <span style=\"color: #08f;\">DWORD flags;\r\n    winrt::check_hresult(link.as&lt;IShellLinkDataList&gt;()-&gt;GetFlags(&amp;flags));\r\n    flags |= SLDF_DISABLE_KNOWNFOLDER_RELATIVE_TRACKING;\r\n    winrt::check_hresult(link.as&lt;IShellLinkDataList&gt;()-&gt;SetFlags(flags));\r\n\r\n    winrt::com_ptr&lt;IStream&gt; stm;\r\n    winrt::check_hresult(CreateStreamOnHGlobal(nullptr, TRUE, stm.put()));\r\n    winrt::check_hresult(link.as&lt;IPersistStream&gt;()-&gt;Save(stm.get(), true));\r\n    winrt::check_hresult(stm-&gt;Seek({ 0 }, 0, nullptr));\r\n    winrt::check_hresult(link.as&lt;IPersistStream&gt;()-&gt;Load(stm.get()));<\/span>\r\n\r\n    wchar_t buffer[MAX_PATH];\r\n    WIN32_FIND_DATA wfd;\r\n    winrt::check_hresult(link-&gt;GetPath(buffer, MAX_PATH, &amp;wfd, 0));\r\n    printf(\"Path is %ls\\n\", buffer);\r\n} catch (...) {\r\n    printf(\"Error: %ls\\n\" winrt::to_message().c_str());\r\n    return winrt::to_hresult();\r\n}\r\n<\/pre>\n<p>After loading the shortcut, we query for <code>IShell\u00adLink\u00adData\u00adList<\/code>, which gives us access to the flags that alter the shortcut behavior. In our case, we want to turn on &#8220;disable known-folder-relative tracking&#8221;.<\/p>\n<p>Once we&#8217;ve updated the flags, we have to save and reload the shortcut for it to take effect. We don&#8217;t have to save it to disk, though. We save it to a memory stream, and then load it back from the same stream.<\/p>\n<p>Once reloaded, we can ask for the path, and since known-folder-relative tracking is disabled, you get the original raw path.<\/p>\n<p><b>Bonus chatter<\/b>: If you want to create a shortcut with this flag set in the shortcut itself (rather than merely set in memory like we did here), then use <code>IShell\u00adLink\u00adData\u00adList<\/code> to set the &#8220;disable known-folder-relative tracking&#8221; flag before saving it.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Disable the fancy tracking.<\/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-107807","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Disable the fancy tracking.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107807","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=107807"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107807\/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=107807"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=107807"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=107807"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}