Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / ATL

Windows 7 File-properties "Version" Tab Shell Extension

Rate me:
Please Sign up or sign in to vote.
4.84/5 (50 votes)
19 Oct 2010Zlib5 min read 185.2K   4.5K   64   52
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


Written By
Software Development Laboratories
United States United States
Old timer with over 30+ years experience in the industry.

Started out programming in assembler on IBM mainframes working my way up from novice computer operator to manager of programming department responsible for multi-million-record database updating software for a major credit reporting company, eventually becoming part of a small team responsible for designing/developing a new microprocessor operating system.

Made the transition from IBM mainframes to Windows personal computers in the early '90s when the internet was still young. Started programming on Windows with Visual C++ version 1.0 on Windows 3.1. Currently using VS2005 but plan to upgrade to VS2008 "soon".

Contracted for a short while at Microsoft providing Premier Technical Support for the "Desktop Team 3" (VC++/MFC).

Currently part of the "Hercules" Open Source core development team, allowing me to stay active in both the mainframe area (which is most definitely NOT dead but still VERY much alive and well thankyouverymuch) as well as in the Windows programming area too (which seems to be a good fit for someone with my background).

I'm currently self-employed providing per-hour contract support for Hercules (http://www.hercules-390.org).

Comments and Discussions

 
PraiseVery useful Pin
hispeedsurfers28-Sep-22 23:03
hispeedsurfers28-Sep-22 23:03 
Questionvery good Pin
olivier gg26-Jul-19 23:02
olivier gg26-Jul-19 23:02 
PraiseWorks on Windows 10 too Pin
Member 977741013-Jan-18 0:55
Member 977741013-Jan-18 0:55 
BugRegisters in win 7, but nothing changes Pin
Member 1274573612-Oct-16 4:10
Member 1274573612-Oct-16 4:10 
GeneralRe: Registers in win 7, but nothing changes Pin
Member 1107818811-Apr-17 3:36
Member 1107818811-Apr-17 3:36 
PraiseBeautiful Pin
MPAQ8-May-16 9:19
MPAQ8-May-16 9:19 
Questionversion (summary sheet with editable fields) - xp like file-inspection on win 7 (x64) - FOLDER TAGS Pin
Noob7667586755-Learning SQL + C#, Very Grate24-Apr-16 3:00
Noob7667586755-Learning SQL + C#, Very Grate24-Apr-16 3:00 
QuestionAnomaly Pin
Member 1233103016-Feb-16 3:48
Member 1233103016-Feb-16 3:48 
AnswerRe: Anomaly Pin
olivier gg26-Jul-19 23:52
olivier gg26-Jul-19 23:52 
GeneralMy vote of 5 Pin
Chris Richardson30-Nov-14 22:24
Chris Richardson30-Nov-14 22:24 
GeneralFantastic! Pin
kkurapaty8-May-14 8:06
kkurapaty8-May-14 8:06 
QuestionAbsolutely love it Pin
abbomar662-May-14 5:48
abbomar662-May-14 5:48 
BugBug for empty fields Pin
Peter Bruin13-Mar-14 23:38
Peter Bruin13-Mar-14 23:38 
GeneralRe: Bug for empty fields Pin
"Fish" (David B. Trout)14-May-14 14:16
"Fish" (David B. Trout)14-May-14 14:16 
GeneralRe: Bug for empty fields Pin
Peter Bruin14-May-14 14:23
Peter Bruin14-May-14 14:23 
QuestionAwesome Pin
Hamp Turner19-Mar-13 9:53
Hamp Turner19-Mar-13 9:53 
QuestionWhy? Pin
Ron Anders30-Jan-13 8:17
Ron Anders30-Jan-13 8:17 
QuestionThis also works on Windows 8 Pin
Member 811720418-Dec-12 13:46
Member 811720418-Dec-12 13:46 
AnswerRe: This also works on Windows 8 Pin
Member 114896931-Mar-15 13:47
Member 114896931-Mar-15 13:47 
QuestionNice work Pin
Marlinspike009-Oct-12 10:25
Marlinspike009-Oct-12 10:25 
BugReceiving Error Pin
Cassaundra6-Sep-12 6:00
Cassaundra6-Sep-12 6:00 
GeneralRe: Receiving Error Pin
"Fish" (David B. Trout)27-Jan-13 3:06
"Fish" (David B. Trout)27-Jan-13 3:06 
Questiondll Pin
Member 93281531-Aug-12 9:44
Member 93281531-Aug-12 9:44 
QuestionFinaly I have it back Pin
Member 821039930-May-12 3:26
Member 821039930-May-12 3:26 
QuestionFANTASTIC! Pin
sirclive28-May-12 22:56
sirclive28-May-12 22:56 

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.