Click here to Skip to main content
Click here to Skip to main content

Windows 7 File-properties "Version" Tab Shell Extension

By , 19 Oct 2010
 

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

About the Author

"Fish" (David B. Trout)
Software Development Laboratories
United States United States
Member
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).

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionAwesomememberHamp Turner19 Mar '13 - 9:53 
There is not much that I actually use this word for, but this is awesome.
 
Thanks.
QuestionWhy?memberRon 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 8memberMember 811720418 Dec '12 - 13:46 
I tried this out on Windows 8 and it works just fine. Don't forget to run he command window as administrator.
QuestionNice workmemberMarlinspike009 Oct '12 - 10:25 
Thanks Fish
BugReceiving ErrormemberCassaundra6 Sep '12 - 6:00 
Whenever I get to Step 2 of the installation in the Command Prompt, I receive the following error:
 
LoadLibrary("C:\Windows\system32\VersInfoEx.dll") failed. GetLastError returns 0x0000007e.
 
Any idea why this is happening? I tried it on my Windows 7x32 laptop and it worked. I am now trying it on my Desktop which is Windows 7x64.
GeneralRe: Receiving Errormember"Fish" (David B. Trout)27 Jan '13 - 3:06 
Hex 0x0000007e == decimal 126.
 
c:\Program Files\Microsoft SDKs\Windows\v6.0A\Include\WinError.h
 

// MessageId: ERROR_MOD_NOT_FOUND
//
// MessageText:
//
// The specified module could not be found.
//
#define ERROR_MOD_NOT_FOUND 126L
"Fish" (David B. Trout)
fish(at)softdevlabs.com

QuestiondllmemberMember 93281531 Aug '12 - 9:44 
Works fine in Windows 7 and Windows 2008 Server.
RMR

QuestionFinaly I have it backmemberMember 821039930 May '12 - 3:26 
I finally decided to do somtehing about this omission and promptly found your extension. Thanks!
 
There was a comment about the extension not working on a certain system. This might be due to to a wrong entry under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved. Here value name and value data are interchanged.
QuestionFANTASTIC!membersirclive28 May '12 - 22:56 
Thank you SO much for this - it's made my life a great deal easier.
GeneralMy vote of 5memberanna_mouse16 Feb '12 - 0:05 
Excellent. Can once again check the private build number of our dlls again. No thanks to Microsoft.

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

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 19 Oct 2010
Article Copyright 2010 by "Fish" (David B. Trout)
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid