File Versions in Windows (with PowerShell)

In an everlasting remediation with Cisco AnyConnect on Windows where 4.5.xxxxx doesn't upgrade properly by just installing a newer version of the product. Cisco has a workaround with 4.6.x but your 4.5 installation is still fucked because a) it's uninstalled in Apps & Feature (Add/Remove if you're old school) but it complains you have a newer version installed if you attempt to install a newer version, or b) it is complaining about "The File 'ManifestTool.exe' is not marked for installation." when attempting to uninstall/reinstall AnyConnect.

So enough of that rant, why did I want to document the versioning? Because Cisco AnyConnect relies a lot of its modules to be the same version of the Core install. I had a need to do some version checking with SCCM and in PowerShell scripting to make sure I can remediate the installation. The problem is, not every developer writes their versions the same way. Most versions are generally written in this format 1.0.123.3456 (see semver.org for some usage examples); however Cisco AnyConnect uses 4, 6, 03049 when queried. Cisco is not the only offender here though; the open source R project version query returns this: 3.5.3.26217 (2019-03-11). If you gave PowerShell a comparison test between 1.4.323.456 and 2.5.323.128, PowerShell can automatically translate them into a version object and compare the 2 versions properly. PowerShell will detect those versions as Major Version, Minor Version, Build Number, Revision Number respectively. For example, you can see your PowerShell's Version table using this command: $PSVersionTable.PSVersion

If you are writing a script, creating a detection method, or a global condition in SCCM, you will have to convert those version numbers into a readable format for PowerShell. If you are a seasoned programmer, you probably know how to do this already. I'm not a seasoned programmer, research + asking questions on PowerShell Slack had given me a lot of insight.

Here's some things to consider when working with version objects:

Convert your number-dot-number string to a version object, remember to quote your string!
$OurVersion=[version]"2.11.1"

Grab the version number using ProductVersion (This command for AnyConnect returns commas); note that I'm using a wild card in the path in case it gets ran on a 32-bit system.

$AnyConnectPath="C:\Program Files*\Cisco\Cisco AnyConnect Secure Mobility Client\vpnui.exe"
$FileVersion=(Get-ChildItem $AnyConnectPath).VersionInfo.ProductVersion

Let's get rid of the commas and convert the string into a version object

$GetFileVersion=(Get-ChildItem $AnyConnectPath).VersionInfo.ProductVersion -replace ', ','.'
$ConvertedACVersion=[version]$GetFileVersion
 

There is another place that product returns a version without commas though (for AnyConnect). You can get it with Get-Command; it is used in older PowerShell versions for other things but works here for AnyConnect.

(Get-Command $AnyConnectPath).Version

So what do we do with the R project and the extra date? Just trim it off.

$AppPath="C:\Program Files\R\R-*\bin\R.exe"
$InstalledVersions=(Get-ChildItem $AppPath).VersionInfo.FileVersion[0]

If there are multiple versions of R installed we split the lines before trimming.

$InstalledVersions=(Get-ChildItem $AppPath).VersionInfo.FileVersion
foreach($line in $InstalledVersions) {
  $s = $line.split()
  $ver = $s[0]
}

In conclusion, we have a few ways to get versions. Here are 2 examples I've used to compare versions.

First one here is a global condition in SCCM. You can turn this into a detection method by adding Write-Host "Installed" along with your $true; the string for Write-Host does not matter.

$AnyConnectPath="C:\Program Files*\Cisco\Cisco AnyConnect Secure Mobility Client\vpnui.exe"
$MinVersion=[version]"4.6.03049"
If (Test-Path $AnyConnectPath) {
    $ACVersion=[version](Get-ChildItem "C:\Program Files*\Cisco\Cisco AnyConnect Secure Mobility Client\vpnui.exe").VersionInfo.ProductVersion
    $MaxVersionInstalled=($ACVersion | Measure-Object -Maximum).maximum
    If ($MaxVersionInstalled -ge $MinVersion) { $true }
    Else { $false }
}
Else { $false }

Here's a snippet of a script also trying to detect the version of AnyConnect. If it's a certain version or newer, run another installer.

$AnyConnectPath="C:\Program Files*\Cisco\Cisco AnyConnect Secure Mobility Client\vpnui.exe"
$MinVersion=[version]"4.6.03049"
If (Test-Path $AnyConnectPath) {
    $GetACVersion=(Get-ChildItem "C:\Program Files*\Cisco\Cisco AnyConnect Secure Mobility Client\vpnui.exe").VersionInfo.ProductVersion -replace ', ','.'
    $ConvertedACVersion=[version]$GetACVersion
    $MaxVersionInstalled=($ConvertedACVersion | Measure-Object -Maximum).maximum
    If ($MaxVersionInstalled -ge $MinVersion) {
    	Start-Process "msiexec.exe" -ArgumentList '/i "installer.msi" /q /lvx* "C:\Windows\Temp\MyProgram-install.log"' -Wait
    }