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 string
s:
BLOCK* pVersionInfo = (BLOCK*) pVI;
if (pVersionInfo->wValueLength)
memcpy( &m_FFInfo, BlockValue( pVersionInfo ), min( sizeof( m_FFInfo ),
pVersionInfo->wValueLength ) );
BLOCK* pBlock = ChildBlock( pVersionInfo );
BLOCK* pEndVersInfo = EndBlock( pVersionInfo );
for (; pBlock < pEndVersInfo; pBlock = NextBlock( pBlock ))
{
if (_wcsicmp( pBlock->szKeyW, L"VarFileInfo" ) == 0)
{
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)
{
BLOCK* pStrTab = (BLOCK*) ChildBlock( pBlock );
BLOCK* pEndStrTab = EndBlock( pBlock );
for (; pStrTab < pEndStrTab; pStrTab = NextBlock( pStrTab ))
{
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 {
WORD wLength; WORD wValueLength; WORD wType; WCHAR szKeyW[]; };
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:
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); }
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:
- retrieve it again during the Property Page callback processing (to show them the
String
value they clicked on) - 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:
PROPSHEETPAGE
structure's lParam
field during "AddPages
" - 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:
- Copy the DLL to your %SystemRoot%\system32 folder.
- Open an Administrator Command Prompt window and enter the command:
regsvr32 "C:\Windows\system32\VersInfoEx.dll"
- 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
- Fixed unlikely (but possible) minor memory leak
- Included "
FileDescription
" and "LegalCopyright
" in items list too
- Version 1.0.0 First Release