Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / ATL

Windows 7 File-properties Version Tab Shell Extension

4.84/5 (50 votes)
19 Oct 2010Zlib5 min read 195K   4.5K  
Simple parsing of VS_VERSIONINFO version information strings displayed in an XP-style file-properties Version tab

Introduction

Much to the dismay of many people, Windows 7 replaced the original Windows 2000 / Windows XP "Version" file properties tab:

With a new "Details" tab instead:

Unfortunately, it neither shows all of the same information as before nor does it support copying and pasting of any of the displayed information either.

With the handy dandy VersInfoEx Shell Extension however, that's no longer true! The VersInfoEx Shell Extension brings back the missing Version tab functionality to Windows 7!

Background

Using the information provided by Michael Dunn in his excellent article series, "The Complete Idiot's Guide to Writing Shell Extensions" (specifically Part V of the series), I was able to throw together a rather simple Property Page Shell Extension that displays a file's VS_VERSIONINFO file-version resource information.

The code makes use of a hand-crafted C++ class that parses all of the VS_VERSIONINFO file-version resource information -- including all of the version strings for all StringTables which may be present -- and encapsulates them all into a few variables within a simple class for much easier program access.

This class information is then simply displayed to the user by the Shell Extension itself.

Using the Code

The CVersionInfo class does all the grunt work, parsing the VS_VERSIONINFO file-version resource information. I wrote it from scratch using the information provided in the SDK.

There are admittedly several other articles on CodeProject that show how to parse the version information, but all of them are lacking in one or more ways. Most fail to parse ALL of the available version information strings, and their overall parsing logic was in my opinion overly complicated and cumbersome.

The simple parsing logic in the CVersionInfo::Init() function overcomes these two shortcomings by presenting a very short and simple algorithm for parsing all of the version strings:

C++
// Point to the VS_VERSIONINFO block passed to us (key = "VS_VERSION_INFO")

BLOCK* pVersionInfo = (BLOCK*) pVI;

// The root VS_VERSIONINFO block's value data is a VS_FIXEDFILEINFO structure

if (pVersionInfo->wValueLength)
    memcpy( &m_FFInfo, BlockValue( pVersionInfo ), min( sizeof( m_FFInfo ),
        pVersionInfo->wValueLength ) );

// Process all of the root block's child blocks...

BLOCK* pBlock       = ChildBlock( pVersionInfo );
BLOCK* pEndVersInfo = EndBlock( pVersionInfo );

for (; pBlock < pEndVersInfo; pBlock = NextBlock( pBlock ))
{
    if (_wcsicmp( pBlock->szKeyW, L"VarFileInfo" ) == 0)
    {
        // "VarFileInfo" child BLOCKs are "Var" BLOCKS...

        BLOCK* pVar    = (BLOCK*) ChildBlock( pBlock );
        BLOCK* pEndVars = EndBlock( pBlock );

        for (; pVar < pEndVars; pVar = NextBlock( pVar ))
        {
            if (_wcsicmp( pVar->szKeyW, L"Translation" ) == 0)
            {
                DWORD* pLangDword = BlockValue( pVar );
                WORD   nNumDwords = pVar->wValueLength / sizeof(DWORD);

                ...(process language/codepage array)...
            }
        }
    }
    else if (_wcsicmp( pBlock->szKeyW, L"StringFileInfo" ) == 0)
    {
        // "StringFileInfo" child BLOCKs are "StringTable" BLOCKS...

        BLOCK* pStrTab    = (BLOCK*) ChildBlock( pBlock );
        BLOCK* pEndStrTab = EndBlock( pBlock );

        for (; pStrTab < pEndStrTab; pStrTab = NextBlock( pStrTab ))
        {
            // "StringTable" child BLOCKs are "String" BLOCKS...

            BLOCK* pString     = (BLOCK*) ChildBlock( pStrTab );
            BLOCK* pEndStrings = EndBlock( pStrTab );

            for (; pString < pEndStrings; pString = NextBlock( pString ))
            {
                CStringW strNameW  = pString->szKeyW;
                CStringW strValueW = (LPCWSTR) BlockValue( pString );

                ...(process versinfo String)...
            }
        }
    }
}

The simplicity comes from the use of very tiny "helper" functions which properly adjust the passed BLOCK structure pointer. The file version information resource BLOCK structure looks like this:

C++
struct BLOCK                    // (always aligned on 32-bit (DWORD) boundary)
{
    WORD    wLength;            // Length of this block    (doesn't include padding)
    WORD    wValueLength;       // Value length            (if any)
    WORD    wType;              // Value type              (0 = binary, 1 = text)
    WCHAR   szKeyW[];           // Value name (block key)  (always NULL terminated)
  //WORD    padding1[];         // Padding, if any         (ALIGNMENT)
  //xxxxx   Value[];            // Value data, if any      (*ALIGNED*)
  //WORD    padding2[];         // Padding, if any         (ALIGNMENT)
  //xxxxx   Child[];            // Child block(s), if any  (*ALIGNED*)
};

Each block always has a key, but may or may not have a value member, which itself could be one or more child sub-block(s), etc.

All blocks start on a DWORD (32-bit) alignment boundary, as does their value data and children sub-blocks as well (which may be present in addition to the value data).

Neither the block length nor the value length fields include whatever padding there may be between the end of the block and the start of the next block, or the end of the key and the start of the value data.

The "helper" functions themselves are as follows:

C++
// Helper functions for navigating through VERSIONINFO data...

BLOCK*  RoundUp32  ( BLOCK* p, size_t n )
    { return (BLOCK*) (((ptrdiff_t) p + n + 3) & ~3); }

BLOCK*  AlignBlock ( BLOCK* pBlk, BLOCK* p )
    { return RoundUp32( pBlk, ((BYTE*)p - (BYTE*)pBlk) ); }

BLOCK*  BlockValue ( BLOCK* pBlk )
    { return AlignBlock( pBlk, (BLOCK*) ((BYTE*)pBlk + sizeof(BLOCK)
        + (( wcslen( pBlk->szKeyW ) + 1) * sizeof(WCHAR))) ); }

BLOCK*  EndBlock   ( BLOCK* pBlk )
    { return (BLOCK*) ((BYTE*) pBlk + pBlk->wLength); } // (NOTE: must NOT be rounded)

BLOCK*  ChildBlock ( BLOCK* pBlk )
    { return AlignBlock( pBlk, (BLOCK*) ((BYTE*) BlockValue( pBlk )
        + pBlk->wValueLength) ); }

BLOCK*  NextBlock  ( BLOCK* pBlk )
    { return AlignBlock( pBlk, EndBlock( pBlk )); }

Using this information (the above functions) you could, if you wanted to, easily code your own "HasChild()" function as follows:

C++
BOOL HasChild( BLOCK* pBlk ) { return ChildBlock( pBlk ) < EndBlock( pBlk ); }

Points of Interest

The actual Shell Extension code to build and display the Version tab itself was remarkably easy to do, thanks to Michael Dunn's "The Complete Idiot's Guide to Writing Shell Extensions -- Part V".

It involved a few simple SendDlgItemMessage calls in the OnInitDialog callback to set the text in the various edit controls and a few SendMessage calls in the Property Page callback. That's it! Pretty simple really.

The only difficult part was paying attention to the potential "gotcha!" involved with remembering to save your pointers to your dynamically allocated data structures (CVersionInfo class object) so you could be sure to:

  1. retrieve it again during the Property Page callback processing (to show them the String value they clicked on)
  2. delete it when the dialog is eventually dismissed in order to prevent a memory leak

As shown in Michael's article, the trick is to save it in two different places:

  1. PROPSHEETPAGE structure's lParam field during "AddPages"
  2. In the Page dialog's window object itself via SetWindowLongPtr

Both of these important techniques are illustrated in the CVersInfoShlExt::AddPages, OnInitDialog, PropPageDlgProc, and PropPageCallbackProc functions of source member VersInfoExShlExt.cpp.

My choice of using the ATL CSimpleMap class also came in handy when I needed to save some piece of information in each List Box item that identified what resource String the item was for. I could have simply retrieved the list box item's string and then used that as the lookup key to find the corresponding value in my string map, but ATL "simple map" entries are directly accessible via direct indexing which made things simple: I just saved the map's index value! (a simple numeric integer value)

Installation

I do not provide an installer since installing a Shell Extension is so easy:

  1. Copy the DLL to your %SystemRoot%\system32 folder.
  2. Open an Administrator Command Prompt window and enter the command:
    regsvr32 "C:\Windows\system32\VersInfoEx.dll"
  3. Logoff and then Logon again to force the Shell (explorer.exe) to refresh.

Note that you must enter the full path on the regsvr32 command, since that's the actual value that gets written to the registry.

If you ever want to uninstall it, simply "unregister" the shell extension by entering the same command but with the "/u" (unregister) option specified instead. Then simply delete the DLL from the Windows system32 directory.

I have only tested this Shell Extension on Windows 7 x64, but it should work on any 32-bit or 64-bit version of Windows.


Well, that's it I guess. Enjoy having the "version" tab back again on your Windows 7 system!

History

  • Version 1.0.1     Minor Fixes
    1. Fixed unlikely (but possible) minor memory leak
    2. Included "FileDescription" and "LegalCopyright" in items list too
  • Version 1.0.0     First Release

License

This article, along with any associated source code and files, is licensed under The zlib/libpng License