Click here to Skip to main content
Click here to Skip to main content
Go to top

Windows 7 File-properties "Version" Tab Shell Extension

, 19 Oct 2010
Rate this:
Please Sign up or sign in to vote.
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:

    // 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:

    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:

// 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:

   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

Share

About the Author

"Fish" (David B. Trout)
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

 
GeneralFantastic! Pinmemberkkurapaty8-May-14 8:06 
QuestionAbsolutely love it Pinmemberabbomar662-May-14 5:48 
BugBug for empty fields PinmemberPeter Bruin13-Mar-14 23:38 
GeneralRe: Bug for empty fields Pinmember"Fish" (David B. Trout)14-May-14 14:16 
GeneralRe: Bug for empty fields PinmemberPeter Bruin14-May-14 14:23 
QuestionAwesome PinmemberHamp Turner19-Mar-13 9:53 
QuestionWhy? PinmemberRon Anders30-Jan-13 8:17 
Why does Microsoft do things to the dismay of many people anyway?
Thanks for the article.
QuestionThis also works on Windows 8 PinmemberMember 811720418-Dec-12 13:46 
QuestionNice work PinmemberMarlinspike009-Oct-12 10:25 
BugReceiving Error PinmemberCassaundra6-Sep-12 6:00 
GeneralRe: Receiving Error Pinmember"Fish" (David B. Trout)27-Jan-13 3:06 
Questiondll PinmemberMember 93281531-Aug-12 9:44 
QuestionFinaly I have it back PinmemberMember 821039930-May-12 3:26 
QuestionFANTASTIC! Pinmembersirclive28-May-12 22:56 
GeneralMy vote of 5 Pinmemberanna_mouse16-Feb-12 0:05 
QuestionSummary tab Pinmembergauravkale13-Oct-11 2:31 
AnswerRe: Summary tab Pinmember"Fish" (David B. Trout)15-Dec-11 18:44 
GeneralBuild error RC2104 [resource compiler] Pinmemberroman31317-Mar-11 6:48 
AnswerRe: Build error RC2104 [resource compiler] Pinmember"Fish" (David B. Trout)15-Dec-11 18:38 
GeneralRe: Build error RC2104 [resource compiler] [modified] PinmemberBurBoy9-May-13 11:49 
GeneralMy vote of 5 PinmemberZephyrer3-Mar-11 5:06 
GeneralProblem with the dll Pinmemberridanmos9-Jan-11 22:10 
AnswerRe: Problem with the dll Pinmember"Fish" (David B. Trout)15-Dec-11 18:26 
GeneralEasy and effective. Note on Administrator Command Prompt... Pinmembertomjacbos3-Jan-11 10:37 
GeneralAwesome, you saved me! Pinmembervaldok21-Dec-10 5:48 
GeneralMy vote of 5 Pinmembercamilohe10-Nov-10 3:24 
GeneralMy vote of 5 PinmemberTheAlas8-Nov-10 5:22 
GeneralSecurity tab Pinmembergauravkale8-Nov-10 4:10 
AnswerRe: Security tab Pinmember"Fish" (David B. Trout)15-Dec-11 18:46 
GeneralSweet, thanks Pinmember Randor 29-Oct-10 15:29 
GeneralWell done, well done Pinmembertrotwa22-Oct-10 13:08 
AnswerRe: Well done, well done Pinmember"Fish" (David B. Trout)23-Oct-10 0:11 
GeneralMy vote of 5 PinmemberS.H.Bouwhuis20-Oct-10 23:44 
GeneralMD5 Hash PinmemberBlake Miller20-Oct-10 15:37 
AnswerRe: MD5 Hash Pinmember"Fish" (David B. Trout)20-Oct-10 16:23 
GeneralMy vote of 5 PinmemberPhil.Benson20-Oct-10 1:23 
GeneralMy vote of 5 [modified] PinmemberMartin Richter [MVP C++]19-Oct-10 2:20 
GeneralMy vote of 5 Pinmember"Fish" (David B. Trout)17-Oct-10 17:02 
GeneralExcellent One! PinmemberAjay Vijayvargiya17-Oct-10 7:50 

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

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

| Advertise | Privacy | Mobile
Web02 | 2.8.140921.1 | Last Updated 19 Oct 2010
Article Copyright 2010 by "Fish" (David B. Trout)
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid