Click here to Skip to main content
15,884,836 members
Articles / Desktop Programming / Win32
Article

Workaround for IShellLink::GetPath()

Rate me:
Please Sign up or sign in to vote.
1.05/5 (15 votes)
1 Mar 2008CPOL 46K   365   7   8
Workaround for IShellLink::GetPath(), which sometimes fails and crashes the application.

Introduction

IShellLink::GetPath() may fail and crash your application. This is a workaround to help you out of this.

Using the code

This is the straight forward implementation:
//*****************************************************************************
// File ..................: ReadLnkFile.cpp
// Description ...........: Decoder for .lnk files on WINDOWS
// Author ................: Peter Thoemmes (http://www.notes-about-cpp.com/)
//-----------------------------------------------------------------------------
// #                  !!! Disclaimer !!!                                      #
// # This source code is provided "AS-IS" basis, without any warranties or    #
// # representations express, implied or statutory; including, without        #
// # limitation, warranties of quality, performance, non-infringement,        #
// # merchantability or fitness for a particular purpose. Peter Thoemmes      #
// # does not warrant that this source code will meet your needs or be free   #
// # from errors.                                                             #
//-----------------------------------------------------------------------------
// Many thanks to Jesse Hager (jessehager@iname.com) for reverse engineering
// the shortcut file format and publishing the document 'The Windows Shortcut
// File Format'.
//-----------------------------------------------------------------------------
// Copyright (c) 2008 Peter Thoemmes, Weinbergstr. 3a, D-54441 Ockfen/Germany
//*****************************************************************************

#include <string>
#include <vector>
using namespace std;

//-----------------------------------------------------------------------------
// Get target file (full local path) from a link file (.lnk):
//-----------------------------------------------------------------------------

string ReadLnkFile(const string& strFullLinkFileName)
{
    //-------------------------------------------------------------------------
    // How to read the target's path from a .lnk-file:
    //-------------------------------------------------------------------------
    // Problem:
    //
    //    The COM interface to shell32.dll IShellLink::GetPath() fails!
    //
    // Solution:
    //
    //   We need to parse the file manually. The path can be found like shown
    //   here, if the shell item id list is present. In case it is not present
    //   we have to assume A = -6, but not to parse/decode byte 76 and 77.
    //
    //  +---------------------+-----------------------------------------------+
    //  | Index               | Description                                   |
    //  +---------------------+-----------------------------------------------+
    //  |                   0 | 'L' (magic value)                             |
    //  +---------------------+-----------------------------------------------+
    //  |                 ... | ...                                           |
    //  +---------------------+-----------------------------------------------+
    //  |                  76 | A_0                                           |
    //  +---------------------+-----------------------------------------------+
    //  |                  77 | A_1 (16 bit) [w/o shell item id list: A = -6] |
    //  +---------------------+-----------------------------------------------+
    //  |                 ... | ...                                           |
    //  +---------------------+-----------------------------------------------+
    //  |         78 + 16 + A | B_0                                           |
    //  +---------------------+-----------------------------------------------+
    //  |     78 + 16 + A + 1 | B_1                                           |
    //  +---------------------+-----------------------------------------------+
    //  |     78 + 16 + A + 2 | B_2                                           |
    //  +---------------------+-----------------------------------------------+
    //  |     78 + 16 + A + 3 | B_3 (32 bit)                                  |
    //  +---------------------+-----------------------------------------------+
    //  |                 ... | ...                                           |
    //  +---------------------+-----------------------------------------------+
    //  |          78 + A + B | PATH_STR_0                                    |
    //  +---------------------+-----------------------------------------------+
    //  |      78 + A + B + 1 | PATH_STR_1                                    |
    //  +---------------------+-----------------------------------------------+
    //  |      78 + A + B + 2 | PATH_STR_2                                    |
    //  +---------------------+-----------------------------------------------+
    //  |                 ... | ...                                           |
    //  +---------------------+-----------------------------------------------+
    //  |                 ... | 0x00                                          |
    //  +---------------------+-----------------------------------------------+
    //-------------------------------------------------------------------------

    //-------------------------------------------------------------------------
    // Get the .lnk-file content:
    //-------------------------------------------------------------------------

    FILE* pFile = fopen(strFullLinkFileName.c_str(),"rb");
    if(!pFile)
        return string("");
    vector<unsigned char> vectBuffer;
    unsigned char byte = '?';
    while((byte = (unsigned char) fgetc(pFile)) != (unsigned char) EOF)
        vectBuffer.push_back(byte);
    fclose(pFile);

    //-------------------------------------------------------------------------
    // Check the magic value (first byte) and the GUID (16 byte from 5th byte):
    //-------------------------------------------------------------------------
    // The GUID is telling the version of the .lnk-file format. We expect the
    // following GUID (HEX): 01 14 02 00 00 00 00 00 C0 00 00 00 00 00 00 46.
    //-------------------------------------------------------------------------

    if(vectBuffer.size() < 20)
        return string("");
    if(vectBuffer[0] != (unsigned char) 'L') //test the magic value
        return string("");
    if((vectBuffer[4] != 0x01) || //test the GUID
       (vectBuffer[5] != 0x14) ||
       (vectBuffer[6] != 0x02) ||
       (vectBuffer[7] != 0x00) ||
       (vectBuffer[8] != 0x00) ||
       (vectBuffer[9] != 0x00) ||
       (vectBuffer[10] != 0x00) ||
       (vectBuffer[11] != 0x00) ||
       (vectBuffer[12] != 0xC0) ||
       (vectBuffer[13] != 0x00) ||
       (vectBuffer[14] != 0x00) ||
       (vectBuffer[15] != 0x00) ||
       (vectBuffer[16] != 0x00) ||
       (vectBuffer[17] != 0x00) ||
       (vectBuffer[18] != 0x00) ||
       (vectBuffer[19] != 0x46))
    {
        return string("");
    }

    //-------------------------------------------------------------------------
    // Get the flags (4 byte from 21st byte):
    //-------------------------------------------------------------------------
    // Check if it points to a file or directory!
    //-------------------------------------------------------------------------
    // Flags (4 byte little endian):
    //        Bit 0 -> has shell item id list
    //        Bit 1 -> points to file or directory
    //        Bit 2 -> has description
    //        Bit 3 -> has relative path
    //        Bit 4 -> has working directory
    //        Bit 5 -> has commandline arguments
    //        Bit 6 -> has custom icon
    //-------------------------------------------------------------------------

    unsigned int i = 20;
    if(vectBuffer.size() < (i + 4))
        return string("");
    unsigned int dwFlags = (unsigned int) vectBuffer[i]; //little endian format
    dwFlags |= (((unsigned int) vectBuffer[++i]) << 8);
    dwFlags |= (((unsigned int) vectBuffer[++i]) << 16);
    dwFlags |= (((unsigned int) vectBuffer[++i]) << 24);

    bool bHasShellItemIdList = (dwFlags & 0x00000001) ? true : false;
    bool bPointsToFileOrDir = (dwFlags & 0x00000002) ? true : false;
    
    if(!bPointsToFileOrDir)
        return string("");

    //-------------------------------------------------------------------------
    // Shell item id list (starts at 76 with 2 byte length -> so we can skip):
    //-------------------------------------------------------------------------

    int32 A = -6;
    if(bHasShellItemIdList)
    {
        i = 76;
        if(vectBuffer.size() < (i + 2))
            return string("");
        A = (unsigned char) vectBuffer[i]; //little endian format
        A |= (((unsigned char) vectBuffer[++i]) << 8);
    }

    //-------------------------------------------------------------------------
    // File location info:
    //-------------------------------------------------------------------------
    // Follows the shell item id list and starts with 4 byte structure length,
    // followed by 4 byte offset for skipping.
    //-------------------------------------------------------------------------

    i = 78 + 4 + A;
    if(vectBuffer.size() < (i + 4))
        return string("");
    unsigned int B = (unsigned int) vectBuffer[i]; //little endian format
    B |= (((unsigned int) vectBuffer[++i]) << 8);
    B |= (((unsigned int) vectBuffer[++i]) << 16);
    B |= (((unsigned int) vectBuffer[++i]) << 24);

    //-------------------------------------------------------------------------
    // Local volume table:
    //-------------------------------------------------------------------------
    // Follows the file location info and starts with 4 byte table length for
    // skipping the actual table and moving to the local path string.
    //-------------------------------------------------------------------------

    i = 78 + A + B;
    if(vectBuffer.size() < (i + 4))
        return string("");
    unsigned int C = (unsigned int) vectBuffer[i]; //little endian format
    C |= (((unsigned int) vectBuffer[++i]) << 8);
    C |= (((unsigned int) vectBuffer[++i]) << 16);
    C |= (((unsigned int) vectBuffer[++i]) << 24);

    //-------------------------------------------------------------------------
    // Local path string (ending with 0x00):
    //-------------------------------------------------------------------------

    i = 78 + A + B + C;
    if(vectBuffer.size() < (i + 1))
        return string("");

    string strLinkedTarget = "";
    for(;i < vectBuffer.size();++i)
    {
        strLinkedTarget.append(1,(char) vectBuffer[i]);
        if(!vectBuffer[i])
            break;
    }

    //Return if empty:
    if(strLinkedTarget.empty())
        return string("");

    //-------------------------------------------------------------------------
    // Convert the target path into the long format (format without ~):
    //-------------------------------------------------------------------------
    // GetLongPathNameA() fails it the target file doesn't exist!
    //-------------------------------------------------------------------------

    char* szLinkedTargetLongFormat = new char[MAX_PATH + 1];
    if(!szLinkedTargetLongFormat)
        return string("");
    if(!::GetLongPathNameA(
                strLinkedTarget.c_str(),
                szLinkedTargetLongFormat,
                MAX_PATH))
    {
        return strLinkedTarget; //file doesn't exist
    }
    string strLinkedTargetLongFormat(szLinkedTargetLongFormat);
    delete[] szLinkedTargetLongFormat;

    return strLinkedTargetLongFormat;
}

Simply call the function ReadLnkFile() to get your target file from the .lnk file:

string strFullTargetPath = ReadLnkFile("C:\\MyShortcut.lnk");

Points of Interest

Many thanks to Jesse Hager (jessehager@iname.com) for reverse engineering the shortcut file format and publishing the document 'The Windows Shortcut File Format'. Without this I would still stuck with IShellLink::GetPath() crashing my application.

History

2008-02-29, Peter Thoemmes: first release.

2008-03-01, Peter Thoemmes: description updated.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) SES ENGINEERING Luxembourg
Luxembourg Luxembourg
I am married to Claudia and we have got two children, our daughter Anna-Lena and our son Markus.
---------
Hobbies...
Motorbiking, TaeKwonDo, Jogging, Bicycling, ...
---------
Career...
1981-1983: Apprenticeship - Nachrichtengerätemechaniker
1983-1985: Apprenticeship - Informationselekroniker
1985-1989: German Air Force - Funkelektroniker/Fachabitur
1989-1992: College of Engineering - Dipl.Ing.(FH)
1992-1995: University Saarbrücken/Germany - Dipl.Ing.(UNI)
1995-1999: Real-Time-OS developer for embedded systems
1999-2000: Senior Software Engineer - BPR tools
2001- now: Senior Software Engineer - SAT Earthstation M&C
---------
Private research & publications...
2003 ....: Book 'Notizen zu C++'
2004 ....: Article 'SmartPointers' in dotnetpro
2005 ....: Article 'Portable Strings' in dotnetpro
2006 ....: Article 'Portable Console' in dotnetpro
2007 ....: Article 'DVB-S2, HDTV, H.264 & BDA' in dotnetpro


Comments and Discussions

 
GeneralMy vote of 5 Pin
transoft15-Jun-11 10:26
transoft15-Jun-11 10:26 
Solved my problem!
AnswerCrash in rpcrt4.dll Pin
Vasiliy Zverev6-Oct-09 23:19
Vasiliy Zverev6-Oct-09 23:19 
GeneralRe: Crash in rpcrt4.dll Pin
HelgeKlein25-Aug-11 10:03
HelgeKlein25-Aug-11 10:03 
GeneralMy vote of 2 Pin
ilmcuts20-Dec-08 5:35
ilmcuts20-Dec-08 5:35 
GeneralCan't resolve "Microsoft Office Word 2007.lnk" format Pin
jingshanhou3-Dec-08 15:39
jingshanhou3-Dec-08 15:39 
GeneralRe: Can't resolve "Microsoft Office Word 2007.lnk" format Pin
ProgmanEx16-Apr-13 21:04
ProgmanEx16-Apr-13 21:04 
Generallol Pin
Jonathan Potter29-Feb-08 11:47
Jonathan Potter29-Feb-08 11:47 
QuestionNo explanation? WTF? Pin
Jim Crafton29-Feb-08 4:27
Jim Crafton29-Feb-08 4:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.