
Introduction
The question "How can I update the version information in my file?" usually yields a handful of solutions. Everyone has their favorite. Some are generic, while others are specific to a particular environment. This type of problem is one of the reasons that the "There's more than one way to skin a cat" idiom exists.
Back when I was still using the 16-bit compiler (v1.52c), the solution I put in place required the version information structure in the .rc file to be modified to look something like:
#include "version.h"
VS_VERSION_INFO VERSIONINFO
FILEVERSION _FILEVERSION_
PRODUCTVERSION _PRODUCTVERSION_
...
The version.h file contained a whole slew of #define directives, a pair for each module. I then put together a little utility that ran during the automated build process that would open the version.h file, find the right #define directives, and increment them accordingly. This solution worked well, with little to no user interaction. For some reason, when our development team moved to the 32-bit compiler, version numbers, or at least the auto-incrementing of them, moved to the back burner. It wasn't that they were any less important, but was because we only had a small handful of 32-bit modules and it was more efficient to change the version numbers manually than to re-tool the utility to work with the 32-bit compiler's way of processing the .rc file. Maybe the .rc2 file could have been used. I give VB a thumbs-up for having this functionality in its IDE.
When the question is coupled with "at runtime rather than compile time", the solution becomes a tad more complicated. Such a question came across my viewport the other day.
You've no doubt used the suite of version-related functions to retrieve file version, description, copyright, platform, etc. from a file. Most have even seen the handy class, CModuleVersion, put together by Paul DiLascia back in 1998 to read from both new and old files. Knowing that the VERSIONINFO resource was stored in a file in an easy-to-retrieve location (we can use GetFileVersionInfo() for this or go Matt Pietrek's way (an up-to-date article is here) and navigate the PE file structure manually!), it only makes sense that it should be easy to update. The remainder of this article is my attempt at just that.
Implementation
The first thing we need to do is get a pointer to the start of the version information block. This is done with:
LPCTSTR lpszFilename = _T("c:\\winnt\\system32\\atl.dll");
DWORD dwHandle,
dwSize;
dwSize = GetFileVersionInfoSize(lpszFile, &dwHandle);
if (0 < dwSize)
{
LPBYTE lpBuffer = new BYTE[dwSize];
GetFileVersionInfo(lpszFile, 0, dwSize, lpBuffer);
...
delete [] lpBuffer;
}
At this point, lpBuffer now points to the start of the version information block, a VS_VERSIONINFO structure actually. In my example, that address is 0x007b25a8. From dwSize, I know that the block is 1700 bytes in size. From my tests, it seems that the compiler allocates more room for the version information block than is actually consumed by the data. Maybe, this is for performance reasons, or to ensure that boundaries are aligned following the version information block. It may also be that GetFileVersionInfoSize() is just overly cautious!
This image shows some of the version information block. What's outlined in red is the data that will be updated.

Next, we get to do a little pointer arithmetic. You'll note that the VS_VERSIONINFO structure contains a VS_FIXEDFILEINFO member structure. No big deal except that it follows a variable-length member and some padding bytes. The number of characters in the szKey member of VS_VERSIONINFO rounded to the nearest 4-byte boundary is where the VS_FIXEDFILEINFO member actually starts. So, if the szKey member contained 15 characters (usually "VS_VERSION_INFO"), plus 1 for the NULL character, the number of bytes offset from the beginning of the version information block would be calculated as:
VS_VERSIONINFO *pVerInfo = (VS_VERSIONINFO *) lpBuffer;
LPBYTE pOffsetBytes = (BYTE *) &pVerInfo->szKey[_tcslen(pVerInfo->szKey) + 1];
VS_FIXEDFILEINFO *pFixedInfo =
(VS_FIXEDFILEINFO *) roundpos(pVerInfo, pOffsetBytes, 4);
After rounding to the nearest 4-byte boundary, we get 28. So, pFixedInfo starts 28 bytes after the beginning of the version information block, or address 0x007b25d0. roundpos() is a macro that handles the rounding. In this case, it uses a 4-byte boundary. Consult the source code for the actual implementation.
We're now ready to increment the version-related members of the VS_FIXEDFILEINFO structure. Just for demonstration purposes, I incremented each word by one using:
pFixedInfo->dwFileVersionMS = pFixedInfo->dwFileVersionMS + 0x00010001;
pFixedInfo->dwFileVersionLS = pFixedInfo->dwFileVersionLS + 0x00010001;
pFixedInfo->dwProductVersionMS = pFixedInfo->dwProductVersionMS + 0x00010001;
pFixedInfo->dwProductVersionLS = pFixedInfo->dwProductVersionLS + 0x00010001;
This was so I could tell that everything was working when I viewed the "before" and "after" files (see the pictures at the top of this article). At this point, we have an in-memory snapshot of an updated version information block. It's time to commit it to disk. Using the resource-related functions, we have:
HANDLE hResource = BeginUpdateResource(lpszFile, FALSE);
if (NULL != hResource)
{
UINT uTemp;
if (VerQueryValue(lpBuffer,
_T("\\VarFileInfo\\Translation"),
(LPVOID *) &lpTranslate,
&uTemp) != FALSE)
{
if (UpdateResource(hResource,
RT_VERSION,
MAKEINTRESOURCE(VS_VERSION_INFO),
lpTranslate->wLanguage,
lpBuffer,
dwSize) != FALSE)
{
EndUpdateResource(hResource, FALSE);
}
}
}
Note the comment about using LANG_NEUTRAL/SUBLANG_NEUTRAL as the fourth parameter to UpdateResource(). The section of the version information block that we updated was independent of language and code page, so we could probably get away without having to call VerQueryValue() to get the translation array.
At this point, the file's version information has been updated. However, there remains but one problem. Can you spot it? The corresponding version numbers in the file's .rc file remains unchanged. The next time the file is built, the previous numbers will be used. I guess that's one reason for updating the .rc file rather than the compiled file.
Updating the StringFileInfo section
The above code snippets focused on the VS_FIXEDFILEINFO section of the file. This is somewhat easier to do because the information contained within it is, well, fixed. The information contained in the StringFileInfo section is not necessarily more difficult to change, but it does have the constraint that the new information be the same size or smaller than the old information. In other words, if I wanted to change the string "Copyright 2004" with an updated string of "Copyright 2004-05" the results would not be pretty since the string is three characters longer. Those extra characters would inevitably write on something important.
The first thing we need to do is get a pointer to one of the sub-blocks in the StringFileInfo section. We do this using VerQueryValue() like:
CString strSubBlock;
LPTSTR pValueBuffer;
strSubBlock.Format(_T("\\StringFileInfo\\%04x%04x\\CompanyName"),
lpTranslate->wLanguage,
lpTranslate->wCodePage);
VerQueryValue(lpBuffer, (LPTSTR) ((LPCTSTR) strSubBlock),
(LPVOID *) &pValueBuffer, &uTemp);
At this point, the variable pValueBuffer is pointing to the CompanyName key. Whatever change I make to this value must be the same length or smaller and end with a '\0' character. So you can either right-pad the new string with '\0' characters or zero the whole string out and then update it. For the latter, I used:
ZeroMemory(pValueBuffer, _tcslen(pValueBuffer) * sizeof(TCHAR));
_tcscpy(pValueBuffer, _T("My Company, Inc."));
You can watch this transition if you have the Memory window open in the IDE. At this point, BeginUpdateResource(), UpdateResource(), and EndUpdateResource() can be called as shown above.
Notes
Two of the language-specific strings that relates to this are FileVersion and ProductVersion. They're contained in the Children member of the StringTable structure and are dependent on language and code page. The updating of those is left as an exercise for the reader, or the author if time and interest permits.
References
Thanks goes out to Ted Peck for supplying the two macros I used in this sample.
Enjoy!
| You must Sign In to use this message board. |
|
|
 |
|
 |
Hi, sorry to bother U, but I have a question. why didn't U using following code to get pFixedInfo?
UINT dummy = 0; LPVOID pvdummy = NULL; VerQueryValue(lpBuffer,_T("\\"),&pvdummy,&dummy); pFixedInfo = (VS_FIXEDFILEINFO *)pvdummy;
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
No particular reason other than it would require little to no explanation. I was more interested in showing how that part of the file was put together.
"Old age is like a bank account. You withdraw later in life what you have deposited along the way." - Unknown
"Fireproof doesn't mean the fire will never come. It means when the fire comes that you will be able to withstand it." - Michael Simmons
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
Thanks a lot for your answer I am interested in your resource structure analyze. And I have another question. I have tried your code with little modifaction. I alloc a larger memory to hold lpBuffer, so I could update the string resource companyName for a longer string than the orign one. But it didn't work. It's obviously that the structure is broken. Any good idea? pseudo code:
//setup LPVOID lpReplaceStart = ${pointer returen by VerQueryValue(\\StringFileInfo\\%04x%04x\\CompanyName) }; //alloc memo DWORD nNewLen = ${structure length after repalce company name with a longer string.}; LPBYTE lpNewBuf = new BYTE[nNewLen];
//copy data memcpy(lpNewBuf,${orign buffer between lpBuffer and lpReplaceStart },${length I}); memcpy(lpNewBuf+${length I},${new company name},${length II}); memcpy(lpNewBuf+${length I+ length II},${orign buffer from lpReplaceStart to end},${length III});
//return delete[] lpBuf; lpBuf = lpNewBuf; nBufLen = nNewLen;
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Does my "Whatever change I make to this value must be the same length or smaller and end with a '\0' character. So you can either right-pad the new string with '\0' characters or zero the whole string out and then update it." comment not apply?
"Old age is like a bank account. You withdraw later in life what you have deposited along the way." - Unknown
"Fireproof doesn't mean the fire will never come. It means when the fire comes that you will be able to withstand it." - Michael Simmons
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I want to change company name for a longer string. so I realloc a larger memory to prevent from erasering exist data. I wish I could change the data area like this: [xxxxxxxxx]HP\0[xxxxxxxx] ([xxxxxxxx] stand for other hex data). I would not destory the orign structure. but alloc a larger memory (14 bytes more) like this: [xxxxxxxxx]MICROSOFT\0[xxxxxxxx] But I found that the data structure is broken after done this. So I wonder where else sould I modify to make the code runable?
Thanks,
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
I have searched for information for VS_VERSION_INFO for 64 bit executables but couldn't find any. Looks like all the fields have been frozen in their 32 values. Any idea?
thanks nikos
PS I am talking about static not dynamic information
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
The programe run well,and the return value is ok,But it cann't update the Ocx or Arx file's version info,why?
thank you !
hello
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
malj wrote: But it cann't update the Ocx or Arx file's version info,why?
That's normally the question I would be asking you? Have you stepped through your code to see what, if anything, is failing?
"Love people and use things, not love things and use people." - Unknown
"The brick walls are there for a reason...to stop the people who don't want it badly enough." - Randy Pausch
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
First,I thank you very much!You reply to me so quickly! I have found the reason,The ocx and arx file's langue type is English,but I run it in a chinese envirenment,When I recompile it in chinese langue ,All things are ok! thanks.
hello
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hello !
I need to insert some build info (timestamp) in the PrivateBuild section of the version info. I plan to do this in the post-build step in VC6.
I tried your sample code and while it works great for reading all the info, I somehow can't get it to update the StringFileInfo section. (It updated the numeric version OK from 21.0.1.0 to 22.1.2.1, meaning the call to UpdateResource is working).
This part of the code :
VerQueryValue(lpBuffer, (LPTSTR) ((LPCTSTR) strSubBlock), (LPVOID *) &pValueBuffer, &uTemp);
doesn't seem to make pValueBuffer point to the CompanyName key, as is described in the article. When I look in the memory window of VC6, I can see that it has allowed space for pValueBuffer after the version info.
Bottom line, changing the value of pValueBuffer with _tcscpy doesn't change the lpData structure (I can confirm that by looking at the memory window).
Am I getting something wrong ? Can you help me out ?
Thank you in advance,
Michel
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
 |
cherbuliez wrote: doesn't seem to make pValueBuffer point to the CompanyName key, as is described in the article.
What file(s) does this happen with? Have you tried your code with atl.dll to see if there is something special about that file? What value does VerQueryValue() return?
"A good athlete is the result of a good and worthy opponent." - David Crow
"To have a respect for ourselves guides our morals; to have deference for others governs our manners." - Laurence Sterne
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hello and thanks for your reply. I tried it with my own executable, as well as with atl.dll. I tried it on the CompanyName tag, but nothing changed. VerQueryValue correctly returns "Microsoft Corporation" in the pValueBuffer when I use it on atl.dll.
The lpBuffer is allocated from 0x0032f078. I can see in the Memory Window that the info is there, as in: "...C.o.m.p.a.n.y.N.a.m.e.....M.i.c.r.o.s.o.f.t. .C.o.r.p.o.r.a.t.i.o.n...p.$...F.i.l.e.D.e.s.c.r.i.p.t.". The end of the buffer is padded with 0xCD chars. The pValueBuffer is in the middle of the padded zone, at 0x0032f078, and I can see it change when I ZeroMemory it and then _tcscpy it.
But since the pValueBuffer is in the padded zone of lpBuffer, when I change pValueBuffer, it changes the value locally but not the CompanyName field in the lpBuffer.
The last update on the file changes, indicating that something has been written, but the ComapyName tag stays unchanged (I use that tag for an example. My goal is to set/change the PrivateBuild tag).
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
Ok, so that I can reproduce this, can you try some other Windows file? It may be that I'm making some assumptions with atl.dll that do not exist with other files.
"A good athlete is the result of a good and worthy opponent." - David Crow
"To have a respect for ourselves guides our morals; to have deference for others governs our manners." - Laurence Sterne
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I faced the same problem if I dont use UNICODE properly. If you use this code
wchar_t strSubBlock[200]; wchar_t* pValueBuffer; unsigned int uTemp;
VerQueryValue(lpBuffer, "\\VarFileInfo\\Translation", (LPVOID *) &lpTranslate, &uTemp); swprintf(strSubBlock,L"\\StringFileInfo\\%04x%04x\\CompanyName", lpTranslate->wLanguage, lpTranslate->wCodePage); VerQueryValueW(lpBuffer, strSubBlock, (LPVOID *) &pValueBuffer, &uTemp);
ZeroMemory(pValueBuffer, wcslen(pValueBuffer) * sizeof(TCHAR)); wcscpy(pValueBuffer, L"MyName");
It will work and pValueBuffer will point in the version block.
Regards Bhawna
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hello Bhawna,
Thank you so much for your answer. UNICODE strings do the trick. It works perfectly now. 
Best regards,
Mike
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
Hi!
My Exe uses a lib which has it's own information : version etc. I would like to add another property page with the information of the lib.
Is it possible ? How ?
Thanks Maurice
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
zmau wrote: I would like to add another property page...
To what?
"A good athlete is the result of a good and worthy opponent." - David Crow
"To have a respect for ourselves guides our morals; to have deference for others governs our manners." - Laurence Sterne
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I am creating a library, which other people use (in my company, no big deal). The point is that many times they ask me for a help, So, I ask which version of my lib are you using. So, to solve confusion, I would like my lib to add another propert page with "MY lib Version info block".
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
zmau wrote: ...I would like my lib to add another propert page...
To what? Where do you want this version information to appear?
On a separate note, don't these other developers know how to check the version of a file?
"A good athlete is the result of a good and worthy opponent." - David Crow
"To have a respect for ourselves guides our morals; to have deference for others governs our manners." - Laurence Sterne
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Thanks for the quick answer. I really appriciate it.
1) I want it to appear the same way the version tab appers when you right-click an exe file. 2) of course, they can say. But there are many applications (different and different versions). There are also many version of my lib. I just want to prevent confusion: take a look at the exe and be sure which version of the library is being used. It seems to me that this is far more error proof.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
That I know of, you can't add a tab to the Properties dialog. You can, however, add another option to the context menu that comes up when right-clicking an item.
"A good athlete is the result of a good and worthy opponent." - David Crow
"To have a respect for ourselves guides our morals; to have deference for others governs our manners." - Laurence Sterne
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
zmau wrote: Something like "My properties" ?
You can name it whatever you like.
zmau wrote: Do you know how ?
Yes.
"A good athlete is the result of a good and worthy opponent." - David Crow
"To have a respect for ourselves guides our morals; to have deference for others governs our manners." - Laurence Sterne
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi, I want to copy the FileVersion info from one exe into another exe. The MS documentation includes an example of how to do this in the description of the UpdateResource function. I am able to get the example to work, but only if the target executable already has some sort of resource file compiled into it. If the target executable doesn't have a resource file compiled into it the copying of the source exe's resource information corrupts the target exe. Does anyone know how to do this or if it is possible?
Thanks,
JohnnyH!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|