Comparing version numbers

If you’re using PowerShell, you most probably came across the following issue: Comparing version numbers just doesn’t work if they are stored in strings.

The simple case

Example:

$a = "1.1.19"
$b = "1.1.2"

if ($a -gt $b) {
    Write-Host "$a is greater than $b"
}
else {
    Write-Host "$a is less than $b"
}

Output:

1.1.19 is less than 1.1.2

No! 19 is NOT less than 2, right?
PowerShell just uses character after character to sort, order and compare. Because we’re dealing with strings.

OK, let’s try this using the version type accelerator [version]:

$a = "1.1.19"
$b = "1.1.2"

if ([version]$a -gt [version]$b) {
    Write-Host "$a is greater than $b"
}
else {
    Write-Host "$a is less than $b"
}

Output:

1.1.19 is greater than 1.1.2

Right! Much better.
Now, PowerShell recognizes our numbers correctly and is able to distinguish between 19 and 2.

Semantic versioning

But lately I came across a challenge about detecting the SAP Secure Login Client 3.0.2.2.0. I used my UniversalInstaller PowerShell framework, put in the search value for name and version and this is what happened:

$a = "3.0.2.2.0"
$b = "3.0.2.1.0"

if ([version]$a -gt [version]$b) {
    Write-Host "$a is greater than $b"
}
else {
    Write-Host "$a is less than $b"
}

Output:

Cannot convert value "3.0.2.2.0" to type "System.Version". Error: "Version string portion was too short or too long."
At line:5 char:5
+ if ([version]$a -gt [version]$b) {
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvalidCastParseTargetInvocation

Well, turned out that the [version] accelerator depends on semantic versioning, meaning there’s only Major, Minor, Build and Revision available to describe a version.

Check out your PowerShell version to get an idea how this looks:

$PSVersionTable.PSVersion

Major  Minor  Build  Revision
-----  -----  -----  --------
5      1      15063  1446

But our SAP Client is using a 5th element in its version number. This causes the [version] accelerator to fail.

So, let’s try to reformat our strings to limit them to 4 elements.
This code will split our string $a into part 0,1,2,3 by using a dot as a divider. Part 4 which is the 5th element will be ignored:

'{0}.{1}.{2}.{3}' -f $a.split('.')

Now we enclose this expression with parentheses so we can use it directly within our code:

$a = "3.0.2.2.0"
$b = "3.0.2.3.0"

if ([version]('{0}.{1}.{2}.{3}' -f $a.split('.')) -gt [version]('{0}.{1}.{2}.{3}' -f $b.split('.'))) {
    Write-Host "$a is greater than $b"
}
else {
    Write-Host "$a is less than $b"
}

Here we go. PowerShell is now able to compare those numbers. But ignores everything after the 4th element:

3.0.2.2.0 is less than 3.0.2.3.0

I know, this is some kind of an ugly workaround, ignoring parts of the version. But in 2 years, working with PowerShell and creating more than 100 SCCM packages, this is the first case where the vendor uses more than 4 elements in a version. So, at the time being this is a quick win.
The downside of course is, this code doesn’t work if the version counts less than 4 elements. Therefore I’ll write a small function which takes care of that. Watch out for part 2.

If you’re struggling with the comparison of file versions, check out this askpfe article to learn how to do proper file comparison.

11 thoughts on “Comparing version numbers”

  1. It think this should option should be made available in the CompareTo() method:
    function Compare-Version ($StartVersion,$FinishVersion,[switch]$Strict) {
    ## Compares only the smallest set of version components, unless -Strict
    $ErrorActionPreference = ‘Stop’
    $SVer = [version]$StartVersion
    $FVer = [version]$FinishVersion
    ‘Major’,’Minor’,’Revision’,’Build’ | ForEach-Object {
    If ( $SVer.$_ -gt 0 -or $FVer.$_ -gt 0 -or $Strict ) {
    If ( $VersionFail = $SVer.$_.CompareTo($FVer.$_) ) { Return $VersionFail }
    }
    }
    }

    Reply
  2. I know this is old, but with the symantic versioning, you can get the first 4 using something like this:
    $a = “3.0.2.2.0”
    $b = “3.0.2.3.0”
    if([version]$a.substring(0,$a.lastindexof(‘.’)) -gt [version]$b.substring(0,$b.lastindexof(‘.’))){
    Write-Host “$a is greater than $b”
    }
    else {
    Write-Host “$a is less than $b”
    }

    To me, it just seems less ‘wordy’ than using [version](‘{0}.{1}.{2}.{3}’ -f $a.split(‘.’))

    Reply
    • Hi John
      Another approach as well, thanks.
      But your solution breaks again when you get a version with more than 5 digits. You are just counting the digits until the last delimiter. So, the longer the original version, the longer your substring gets. Basically you just deduct one digit. But [version] only works with 4 digits. You need to cut off after the 4th position.

      Reply
  3. Thank you so much!! I have been fiddling with this comparison between a string and the FileVersion.ProductVersion from an object. All I did was add [Version] in front of both variables in the IF statement and MAGIC.

    If I could, I’d buy you a beer right now. Cheers mate!

    Reply
  4. This isn’t working for me.

    I have $Var.Attribute = 19.2.1

    If ([version]$Var.Attribute -ge 19.2.2) {Write-Host Wrong}

    ^ Returns wrong. Even if I use [version] on both sides of the comparison. PoSh 5.1.17763.771

    Reply
    • Hi Barry
      Thanks for your comment.
      You need to use a literal string or double quotes like this:
      If ([version]$Var -ge ‘19.2.2’) {Write-Host Wrong}
      If ([version]$Var -ge “19.2.2”) {Write-Host Wrong}

      And as you mention it, you don’t even need to use the [version] accelerator on both sides. PowerShell knows what you want and uses [version] on both sides of the comparison.
      This works as well:
      If ($Var -ge [version]’19.2.2′) {Write-Host Wrong}

      But you need the quotes or double quotes.
      Hope this helps.

      Reply
      • Important correction here:
        You must use the [version] accelerator on the first element! Not sure whether this changed over time or I didn’t test properly in 2019. And you really need to make sure that you’re dealing with strings.

        $var = 19.2.1 #does not work, because numbers can only have one dot
        $var = 19.2 #does work, it’s a valid System.Double (https://devblogs.microsoft.com/scripting/understanding-numbers-in-powershell/)
        $var = ‘19.2.1’ # does work, because it’s a string, can have as many dots as you like

        ex1
        $var = ‘19.2.1’
        If ($var -ge ‘19.10.2’) {Write-Host Wrong}
        –> returns ‘wrong’, 2 is not greater than 10 (it thinks 2 is greater than 1)

        ex2
        $var = ‘19.2.1’
        If ($var -ge [version]’19.10.2′) {Write-Host Wrong}
        –> still returns ‘wrong’ because we use the [version] accelerator on the wrong side, PowerShell is still doing a string comparison

        ex3
        $var = ‘19.2.1’
        If ([version]$var -ge ‘19.10.2’) {Write-Host Wrong}
        –> now we’re talking, this is the correct way, it returns nothing because strings are treated as version

        Reply

Leave a Comment