Lenovo Thunderbolt Firmware Version Detection

Share on:

When Lenovo announced HT508988 back in late 2019, I found there was no built-in method to get the firmware version on impacted Lenovo models using traditional hardware inventory or WMI. The only solution I found in the forums was to collect the data using the firmware update itself. Through trial and error, I found that although there are dozens of different firmware updates, the detection binaries from just two of them covered my entire fleet. Below, I'll share the steps you can use to gather this information yourself.

First, you'll need to gather up the DETECT folders from the downloaded Firmware updates (after you extract them). You're looking for one that has detect1909ur.exe and one that has detect2008.exe. You'll want the whole folder. If you'd rather not go hunting for them, you can download them from me here.


Important: Please please please validate the digital signature on all of the files inside the Zip and don't randomly trust downloaded binaries from a blog. The Zip itself is not signed. This is only here for those who don't want to go through the hassle of finding them in your firmware update extractions.


Depending on the model laptop, you can now run one or the other of the detection binaries to get your firmware version, as shown below:

Thunderbolt Firmware Detection Binary Execution

While useful for checking one laptop at a time, we'll want to build a better method to run this across our fleet. Cue our PowerShell script. Save this in the same folder that contains the two detect folders.

 1Function Start-ProcessGetStreams {
 2
 3    [CmdLetBinding()]
 4    Param(
 5        [System.IO.FileInfo]$FilePath,
 6        [string[]]$ArgumentList,
 7        [string]$WorkingDirectory = $PSScriptRoot
 8    )
 9
10    $pInfo = New-Object System.Diagnostics.ProcessStartInfo
11    $pInfo.FileName = $FilePath
12    $pInfo.Arguments = $ArgumentList
13    $pInfo.RedirectStandardError = $true
14    $pInfo.RedirectStandardOutput = $true
15    $pinfo.UseShellExecute = $false
16    $pInfo.CreateNoWindow = $true
17    $pInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
18    $pInfo.WorkingDirectory = $WorkingDirectory
19
20    $proc = New-Object System.Diagnostics.Process
21    $proc.StartInfo = $pInfo
22
23    Write-Verbose "Starting $FilePath $($ArgumentList -join " ") in Working Directory $($pInfo.WorkingDirectory)"
24    $proc.Start() | Out-Null
25    
26    Write-Verbose "Waiting for $($FilePath.BaseName) to complete"
27    $proc.WaitForExit()
28
29    $stdOut = $proc.StandardOutput.ReadToEnd()
30    $stdErr = $proc.StandardError.ReadToEnd()
31    $exitCode = $proc.ExitCode
32
33    Write-Verbose "Standard Output: $stdOut"
34    Write-Verbose "Standard Error: $stdErr"
35    Write-Verbose "Exit Code: $exitCode"
36
37    [PSCustomObject]@{
38        "StdOut" = $stdOut
39        "Stderr" = $stdErr
40        "ExitCode" = $exitCode
41    }
42
43}
44
45$systemType = $(Get-CimInstance Win32_ComputerSystem | Select -ExpandProperty Model).Substring(0,4)
46Write-Host "The system type is $systemType"
47
48If($systemType -in ("20NY","20QG","20N3","20N7","20QD")) {
49    $detect = Join-Path $PSScriptRoot "\Detect1909UR\Detect1909UR.exe"
50}
51
52If($systemType -in ("20JN","20L8","20HC","20HE","20K0","20LC","20LB","20JE","20JT","20L6","20LE","20MA","20JD","20HD")) {
53    $detect = Join-Path $PSScriptRoot "\Detect2008\Detect2008.exe"
54}
55
56If(-Not($detect)) {
57    $nvmVersion = $null
58    $status = "No Detector for $systemType"
59} else {
60
61    $detectOutput = Start-ProcessGetStreams -FilePath $detect -WorkingDirectory $(Split-Path $detect) -Verbose
62
63    If($detectOutput.ExitCode -ne 0) {
64        # Sometimes the system will fail to enable the Tbt Controller in the seven seconds that are allotted
65        # A second attempt often fixes this after a short delay. This is more art than science.
66        Start-Sleep -Seconds 15
67        $detectOutput = Start-ProcessGetStreams -FilePath $detect -WorkingDirectory $(Split-Path $detect) -Verbose
68    }
69
70    [regex]$rxNvm = "(?msi)NVM firmware version is (.*?)$"
71    
72    $nvmLookup = $rxNvm.Match($detectOutput.StdOut)
73
74    If($detectOutput.ExitCode -eq 0) {
75        if($nvmLookup.Groups[1].Value) {
76            $nvmVersion = $nvmLookup.Groups[1].Value.Trim()
77            $status = "Ok"
78        } else {
79            $versionid = $null
80            $status = "Could not detect version from StdOut"
81        }
82    } else {
83        $versionid = $null
84        $status = "Detect Returned Exit $($detectOutput.exitcode)"
85    }
86}
87
88$LastScriptRun = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
89
90New-Item -Path "HKLM:\Software\Inventory\Hardware\ThunderboltFirmware" -Force | Out-Null
91
92New-ItemProperty -Path "HKLM:\Software\Inventory\Hardware\ThunderboltFirmware" -Name "NvmVersion" -Value $nvmVersion -Force | Out-Null
93New-ItemProperty -Path "HKLM:\Software\Inventory\Hardware\ThunderboltFirmware" -Name "LastScriptRun" -Value $LastScriptRun -Force | Out-Null
94New-ItemProperty -Path "HKLM:\Software\Inventory\Hardware\ThunderboltFirmware" -Name "Status" -Value $status -Force | Out-Null
95
96Write-Output "NvmVersion = $nvmVersion | Status = $status"

As you can probably tell, this script will look at the system type of the host system and then run the appropriate Detection method for it. Unfortunately, the ones I've listed are the only ones I needed to care about. In order to update this for your models, you'll want to test each of the two binaries to determine which one it uses and then update lines 48 or 52 (in the above sample).

With the data now in the registry, you can use your inventory method of choice to collect the data.



No comments