In my previous post, I briefly mentioned that the VersionInfo.FileVersion property of a file object might not be accurate. I didn’t really give any examples to back that up, though. I’d like to show you how you can see this issue for yourself. First, run this so you can follow along in the examples:
$Culture = (Get-Culture).Name $Files = dir C:\Windows\system32 -File | select Name, @{ N="FileVersionString"; E={ $String = $_.VersionInfo.FileVersion if ($String -match "(\d+\s*(\.|,)\s*){1,3}\d+") { $String = $Matches[0] -replace ",", "." $String = $String -replace "\s*", "" $String = ([version] $String).ToString() } $String }}, @{ N="FileVersion";E={ "{0}.{1}.{2}.{3}" -f $_.VersionInfo.FileMajorPart, $_.VersionInfo.FileMinorPart, $_.VersionInfo.FileBuildPart, $_.VersionInfo.FilePrivatePart} }, @{ N="MUIExists";E={ Test-Path ("{0}\{1}\{2}.mui" -f $_.PSParentPath, $Culture, $_.Name) }}
This is a simple set of commands that gets all of the files from the system32 directory. For each file, it tries to get the file version information. The property named ‘FileVersionString’ takes the VersionInfo.FileVersion and does the following to it:
1. It removes any text as long as the string starts with something that resembles a version (spaces are allowed between the sections, and a comma or a decimal point can be used to separate the parts)
2. It replaces any commas with decimal points
3. It removes any spaces that might be left
4. It temporarily casts it to a [version] type, then back to a string. The idea here is to get rid of extra leading zeros, e.g., 9.00.7600.45000 becomes 9.0.7600.45000
The ‘FileVersion’ property is a string computed the way that I think is the best way to get a file version: combining the FileMajorPart, FileMinorPart, FileBuildPart, and FilePrivatePart numbers. The ‘MUIExists’ property is a Boolean property that tells you whether or not the file in question has a MUI resource file associated with it. The logic for this is very simple, and there may be some errors. Notice that the first line is getting the culture of the system (‘en-US’ on my computer).
Now lets look at the results:
# Show how many files were in folder:
PS> $Files.Count
3345
# Show how many files in folder have a VersionInfo.FileVersion string:
PS> ($Files | where { $_.FileVersionString }).Count
2944
# Get the files that have version strings that don't match the version numbers:
PS> $FilesWithNonMatchingVersions = $Files |
where { $_.FileVersionString -and $_.FileVersionString -ne $_.FileVersion }
# Show how many files have version information mismatches:
PS> $FilesWithNonMatchingVersions.Count
176
# Show how many files that have conflicting version information that have MUI
# resources, and how many don't:
PS> $FilesWithNonMatchingVersions | group MUIExists -NoElement
Count Name
----- ----
162 True
14 False
There are 3345 files in the system32 directory on the machine I ran that on. Out of those, 2944 have FileVersion strings. Out of those, 176 have a mismatch between the VersionInfo.FileVersion property and the file version that was computed by combining the four version parts.
Out of those 176, 162 have a MUI resource file, and 14 don’t. The 14 that don’t are files where the version strings simply don’t match. The version that Explorer would show for those files is the version computed using the four version parts.
176 files in the system32 directory are showing the VersionInfo.FileVersion property from a different file than you think they are (it’s actually coming from the file’s MUI resource). That’s about 5.5% of the files that have a version in the system32 directory (on a Windows 7 system that I ran this on, it was 478 out of 2480, or just over 19%).
If you run these commands, you’ll get an example of a single random file (NOTE: this will copy a file into your temp directory):
$RandomFileName = $FilesWithNonMatchingVersions | ? { $_.MUIExists } | Get-Random | select -exp Name $SourceFile = "c:\windows\system32\$RandomFileName" $MuiFile = "c:\windows\system32\$Culture\$RandomFileName.mui" $DestFile = "$env:temp\$RandomFileName" Copy-Item $SourceFile $DestFile Get-Item $SourceFile, $MuiFile, $DestFile | select -exp VersionInfo | fl FileName, FileVersion, FileM*Part, FileBuildPart, FilePrivatePart
And here are the results on the machine I used:
FileName : C:\windows\system32\WMPhoto.dll
FileVersion : 6.3.9600.16384 (winblue_rtm.130821-1623)
FileMajorPart : 6
FileMinorPart : 3
FileBuildPart : 9600
FilePrivatePart : 16474
FileName : C:\windows\system32\en-US\WMPhoto.dll.mui
FileVersion : 6.3.9600.16384 (winblue_rtm.130821-1623)
FileMajorPart : 6
FileMinorPart : 3
FileBuildPart : 9600
FilePrivatePart : 16384
FileName : C:\Users\me\AppData\Local\Temp\WMPhoto.dll
FileVersion : 6.3.9600.16474 (winblue_gdr.131122-1506)
FileMajorPart : 6
FileMinorPart : 3
FileBuildPart : 9600
FilePrivatePart : 16474
Notice how on the first object the FileVersion doesn’t match the version if you combine the four numeric parts? Also, did you notice how the third object is showing matching versions? The binary file actually contains the proper information, but the API call that gets the file information is checking to see if there is MUI file, and if there is, it uses the FileVersion from the MUI file instead of the file you asked for. The file information from the first and third objects are from the exact same file, just in different directories. When the file is in the system32 directory, it has a MUI file associated with it. When it’s moved to the temp folder, it doesn’t. The four numeric file version parts are actually pulled from the real file whether or not there is a MUI file associated with it.
So, if you want accurate file versions (as accurate as Explorer will show you), I suggest using the four file version parts from the VersionInfo object: FileMajorPart, FileMinorPart, FileBuildPart, FilePrivatePart.
An example of doing that can be found here.
This is the most valuable post I’ve read in a week.