IS4
IS4

Reputation: 13207

Get BHID_LinkTargetItem from a .lnk file if IsShortcut was deleted from the registry

I have a IShellItem object created from a .lnk file, and I want to obtain the file it points to, either as another IShellItem or as a PIDL. Because the target may not be an actual file (merely an item in the virtual file system), I would like to refrain from using any paths at all.

The closest method I found is IShellItem::BindToHandler, which accepts BHID_LinkTargetItem as the argument, and obtains the target as an IShellItem. That's perfect, but there is a slight problem - it doesn't work if I delete the IsShortcut registry key in HKCL (in lnkfile etc.). Calling IShellItem::GetAttributes doesn't return SFGAO_LINK in that case, and the method fails.

It's no surprise that system functions stop working when tampering with the registry, but it is a common thing to remove this specific key, to hide the arrows from the shortcut icons. I know there is probably a better way to do it, but I don't want my code to fail if the user has done this.

What I have currently resolved to, is constructing an IPersistFile at the location and casting it to IShellLink. Now I just call IShellLink::GetIDList and I am happy. If the shell item is not a real file, I try to obtain the link as specified in the second paragraph.

However, the fact that I have to obtain the path to the link file is what worries me a bit. Are there some cases where this could fail for a valid link object? Can it be done without having to work with paths?

Upvotes: 2

Views: 390

Answers (1)

zett42
zett42

Reputation: 27776

You can obtain an IShellLink directly from an IShellItem by calling IShellItem::BindToHandler() and passing BHID_SFUIObject for the rbhid parameter. Then call IShellLink::GetIDList() to obtain the link target as you already do in your current solution.

In my test under Windows 10 it worked independently from the existence of the IsShortcut registry entry.

Example:

#include <ShlObj.h>     // Shell API
#include <atlcomcli.h>  // CComPtr
#include <atlbase.h>    // CComHeapPtr
#include <iostream> 
#include <system_error>

// Throw a std::system_error if the HRESULT indicates failure.
template< typename T >
void ThrowIfFailed( HRESULT hr, T&& msg )
{
    if( FAILED( hr ) )
        throw std::system_error{ hr, std::system_category(), std::forward<T>( msg ) };
}

// RAII wrapper to initialize/uninitialize COM
struct CComInit
{
    HRESULT hr = ::CoInitialize( nullptr );
    CComInit() { ThrowIfFailed( hr, "CoInitialize failed" ); }
    ~CComInit() { ::CoUninitialize(); }
};

int main()
{
    try
    {
        CComInit init;

        // Create a shell item from a file for testing purposes.
        CComPtr<IShellItem> pItem;
        ThrowIfFailed(
            SHCreateItemFromParsingName( L"C:\\test.lnk", nullptr, IID_PPV_ARGS( &pItem ) ),
            "Could not create shell item from path" );

        // Obtain a link object from the shell item.
        CComPtr<IShellLink> pLink;
        ThrowIfFailed(
            pItem->BindToHandler( nullptr, BHID_SFUIObject, IID_PPV_ARGS( &pLink ) ),
            "Could not obtain IShellLink" );

        // Get the link target as PIDL.
        CComHeapPtr<ITEMIDLIST> pidlTarget;
        ThrowIfFailed(
            pLink->GetIDList( &pidlTarget ),
            "Could not get link target as PIDL" );

        // Create a shell item from the link target PIDL...
        CComPtr<IShellItem> pItemTarget;
        ThrowIfFailed(
            SHCreateShellItem( nullptr, nullptr, pidlTarget, &pItemTarget ),
            "Could not create shell item for link target" );

        //... to obtain the display name (we may use SIGDN_FILESYSPATH to obtain a
        // path only if we know  the target is a filesystem item).
        CComHeapPtr<wchar_t> pDisplayName;
        ThrowIfFailed(
            pItemTarget->GetDisplayName( SIGDN_DESKTOPABSOLUTEEDITING, &pDisplayName ),
            "Could not get display name of link target" );

        std::wcout << L"Link target (display name): " << pDisplayName.m_pData << '\n';

        return 0;
    }
    catch( std::system_error const& e )
    {
        std::cout << "ERROR: " << e.what() << ", error code: " << e.code() << "\n";

        return 1;
    }
}

Notes:

  • The code uses ATL COM smart pointers for exception-safe, clean code. CComPtr is used to manage COM objects (its destructor automatically calls the Release() method when the object goes out of scope). CComHeapPtr is used to manage "raw memory" allocated by the shell (its destructor automatically calls ::CoTaskMemFree()).

Upvotes: 1

Related Questions