Posts Tagged ‘Versions’

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.

I used to think that getting a file version in PowerShell was a fairly simple process:


PS> (Get-Item c:\windows\system32\mshtml.dll).VersionInfo.FileVersion
11.00.9600.16410 (winblue_gdr.130923-1706)

There are two issues with that method, though:
1. That property can contain text along with the version
2. It turns out that in Windows Vista and higher, that property might not be coming from the file you think it is.

Issue #1 just means that the property is only good if you’re visually checking the version, or if no comparisons with other versions are necessary.

Issue #2 is the big one for me. What appears to be going on is that the FileVersion string is considered a non-fixed part of the version information, and it is pulled from the MUI resource file (if one exists). This file contains the language specific settings, and it looks like it only gets updated when the major or minor file version of associated file changes. If the private file version changes, it doesn’t look like the MUI file is changed.

That means that the FileVersion property has the potential to give you information that isn’t completely up to date. Critical patches from MS usually just update vulnerable code, so there’s no need to change language-specific things in the MUI file.

So, how can you get accurate file version information? Well, you can combine other properties from the VersionInfo object to create an accurate file version:


PS> $VersionInfo = (Get-Item C:\Windows\System32\mshtml.dll).VersionInfo
PS> $FileVersion = ("{0}.{1}.{2}.{3}" -f $VersionInfo.FileMajorPart, 
    $VersionInfo.FileMinorPart, 
    $VersionInfo.FileBuildPart, 
    $VersionInfo.FilePrivatePart)
PS> $FileVersion
11.0.9600.16476

That’s still pretty simple, but it takes a lot more typing. Because of that extra typing, though, your version is now in a known format, i.e., ‘x.x.x.x’. And, if you cast it to a [version] type, you can compare versions as if they were numbers:


PS> [version] $FileVersion -gt "11.0.9600.16410"
True

There is one thing to note here:
Version comparisons between different formats, e.g., 1.2.3 and 1.2.3.0, might not return what you’re expecting. See the remarks section here. Here are some examples of what I’m talking about:


# Gotcha #1
PS> [version] "1.2.3" -eq [version] "1.2.3.0"
False

# Gotcha #2
PS> [version] "1.2.3" -lt [version] "1.2.3.0"
True

# Example of different formats working as you would expect
PS> [version] "1.2.3" -gt [version] "1.2.2.15"
True

# Another example of different formats working
PS> [version] "1.3" -gt [version] "1.2.2.15"
True

As long as you keep your version formats the same, though, the comparison operators will work exactly as you would expect them to. I recommend keeping them in the ‘x.x.x.x’ format.

Something else about the comparisons: In the examples, casting the second string to a [version] isn’t actually necessary. When a comparison operation occurs in PowerShell, the type of the first object is used for the comparisons. That means that the second string in the examples is automatically converted to a [version], even if you don’t tell PowerShell to do it. If you run this command, you’ll get an error:


PS> [version] "1.2.3.4" -gt "potato"
Could not compare "1.2.3.4" to "potato". Error: "Cannot convert value "potato" to type "System.Version". Error: "Version string portion was too short or too long.""
At line:1 char:1
+ [version] "1.2.3.4" -gt "potato"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : ComparisonFailure

I just put a string there, but PowerShell tried to make it a [version] object. It doesn’t hurt to always explicitly use the type (you could even argue that it’s a best practice), but it’s not necessary if you’re trying to cut down on the characters you have to type. Just make sure that the first object is actually a [version] type.

Like I said before, the biggest drawback to combining the four File*Part properties is how much typing it requires. So, I decided to make PowerShell always get the file version for me, and automatically add it to a file object:

# PowerShell must be at least v3 to do this:
Update-TypeData -TypeName System.IO.FileInfo -MemberType ScriptProperty -MemberName PSFileVersion -Value {
    if ($this.VersionInfo.FileVersion) {
        [version] ("{0}.{1}.{2}.{3}" -f $this.VersionInfo.FileMajorPart, 
            $this.VersionInfo.FileMinorPart, 
            $this.VersionInfo.FileBuildPart, 
            $this.VersionInfo.FilePrivatePart)
    }    
}

That method requires version 3.0. If you have 2.0, you should be able to get it to work, though. The difference is that you have to save an XML file, and then use the Update-TypeData cmdlet to read that file. Here’s the XML file:

<Types>
    <Type>
        <Name>System.IO.FileInfo</Name>
        <Members>
            <ScriptProperty>
                <Name>PSFileVersion</Name>
                <GetScriptBlock>
                    if ($this.VersionInfo.FileVersion) {
                        [version] ("{0}.{1}.{2}.{3}" -f $this.VersionInfo.FileMajorPart, 
                            $this.VersionInfo.FileMinorPart, 
                            $this.VersionInfo.FileBuildPart, 
                            $this.VersionInfo.FilePrivatePart)
                    }    
                </GetScriptBlock>
            </ScriptProperty>
        </Members>
    </Type>
</Types>

And here’s the command:

# Replace $PathToXmlFile with proper path:
Update-TypeData -AppendPath $PathToXmlFile

This works on my machine when forcing PowerShell to run version 2, but I haven’t tested it on a machine that truly has PowerShell v2 installed.

Now, when you use Get-Item, Get-ChildItem, or anything that returns a [System.IO.FileInfo] object, the object will have a PSFileVersion property. Here are some examples of this functionality:


# Use Get-ChildItem to display file names and versions:
PS> dir C:\Windows\system32 -Filter msh*.dll | ? psfileversion | select name, psfileversion

Name          PSFileVersion  
----          -------------  
mshtml.dll    11.0.9600.16476
MshtmlDac.dll 11.0.9600.16384
mshtmled.dll  11.0.9600.16384
mshtmler.dll  11.0.9600.16384


# Same as before, but this shows the accurate System.Version version and the inaccurate VersionInfo.FileVersion:
PS> dir C:\Windows\system32 -Filter msh*.dll | ? psfileversion | select name, psfileversion, @{N="FileVersionString";E={$_.VersionInfo.FileVersion}}

Name          PSFileVersion   FileVersionString                         
----          -------------   -----------------                         
mshtml.dll    11.0.9600.16476 11.00.9600.16410 (winblue_gdr.130923-1706)
MshtmlDac.dll 11.0.9600.16384 11.00.9600.16384 (winblue_rtm.130821-1623)
mshtmled.dll  11.0.9600.16384 11.00.9600.16384 (winblue_rtm.130821-1623)
mshtmler.dll  11.0.9600.16384 11.00.9600.16384 (winblue_rtm.130821-1623)


# Comparisons:
PS> (Get-Item C:\Windows\System32\mshtml.dll).PSFileVersion -gt "11.0.9600.16410"
True
PS> (Get-Item C:\Windows\System32\mshtml.dll).PSFileVersion -lt "11.0.9600.16410"
False


# List all of the files with versions under system32:
dir C:\Windows\System32 | ? psfileversion | select name, psfileversion | Out-GridView

You can find this on the Script Repository here.