Technology Solutions for Everyday Folks
Photo of Dell firmware update in progress instructing a user not to power down the system and showing a progress bar with basic status information.

Semi-Automatically Offering Dell BIOS/Firmware Updates

Several years ago I implemented a mechanism in our primary [re]build [Configuration Manager/MEMCM/SCCM] task sequence to address upgrades of our fleet's firmware (BIOS). On the whole, the process has worked very well and definitely helped keep things updated. However, this process only upgraded device firmware during a [re]build cycle which works great for our multi-user devices receiving an annual refresh; for high-affinity devices a rebuild happens once every 3-5 years and is usually associated with some form of attrition or replacement.

Updates for high-affinity devices outside of this cycle were infrequent and usually tech-initiated to solve a specific problem. That's not a good long-term posture for keeping firmware up to date—a newer mechanism was necessary.

But...Why Now?

I used to work with some folks who were skeptical of ever applying firmware updates. After all, it wasn't all that long ago when a firmware update came with a moderate risk of fully "bricking" a system if it didn't go perfectly. I never fully bricked a device this way, but had a couple of close calls over the I was familiar with that mentality and hesitation.

However, two decades into the 2000's things are also a lot different: firmware updates are no exception. It's ignorant to not have any plan in place for handling updates. We'd been talking about doing something more deliberate for some time, but the urgency was never really present.

...Until That Time When...

During the pandemic and while we were accommodating a lot of hybrid learning we encountered a problem in which part of the "fix" was applying firmware updates to desktop/laptop systems AND also laptop docks (if present). As firmware updates solved the problem in question, the question had been called and it was time to roll a more deliberate solution.

"We've Never Done This Before"

With renewed urgency and a desire to make it as simple as possible for our techs to deploy the updates (and for me to verify they were now compliant), I settled on a new (to us) process to offer updates to our fleet in a controlled way. It was decided that we didn't want to "force update" these on a maintenance window schedule because we could run into a situation where a reboot could impact an in-use time...which in our environment would directly impact classroom access. Not everything in spaces is tightly scheduled, so it was safest to have a tech visit to complete the process. Other steps to resolve the problem in question also required manual touch, so this was a reasonable balance.

Updates would be invoked via Software Center by an onsite tech to ensure the reboot cycle didn't impact anyone using the space at the time. This was bolstered by the fact the Dock Firmware update utility couldn't be run in "silent" mode (and required the dock be connected, also not a guarantee when unattended). At the same time, we needed to avoid relaying any technical information beyond basic steps to whoever would be clicking the buttons. Having everything deployed via Software Center solved the lion's share of this issue.

My Design

The idea was to selectively scope (add and remove) devices from the available advertisement as necessary. The obvious way to do this was via configuration items (and baselines).

The Configuration Baselines

For longer-term use I settled on two baselines in the process: one for the desktop/laptop firmware, and the other for the dock firmware. This meant the solution could be expanded to a larger scope of the fleet (or the entire fleet) without having to untangle parts or remember what was where and why.

The Configuration Items

In the heat of the moment, the solution was to "hardwire" specific firmware versions in the configuration item detection rule. Not great, but it worked and was easy to implement as a starting point since the original scope was a single device model. This was later changed and is slightly more dynamic and updated on a regular-ish cycle (snippet below clipped for simplicity):

# Model List and Versions
$ModelList = @{
  '5400' = '0001.00xx.00yy'
  '5420' = '0001.00xx.00yy'
$WMIData = Get-WmiObject -Namespace root\dell\sysinv dell_softwareidentity -ErrorAction Stop
$DellData = $WMIData | Select-Object VersionString, ElementName | 
    Where-Object ElementName -like "*BIOS*" | Sort-Object ElementName
if ($DellData.versionstring -lt $ModelList[$ModelNumber]) {
  return $false
} else {
  return $true

This compliance rule is then set up to require evaluation of EQUALS True. When the above snip is evaluated compliant models should return $true in the powershell script.

The logic could be flipped (to return $false in the else statement), which would be a better way to detect compliance. However, I found in testing that I had a better hit rate due to the string comparison happening with the version numbers. The short version is "your mileage may vary" in something like this, so test accordingly.

Caveat #1: Signed Powershell Scripts

In our environment, configuration item Powershell scripts must be code signed. This required a little additional prestage lift to ensure the code signing certificate was properly distributed to and installed on target devices. Once the certificate was distributed, I could sign the scripts with this simple one-liner at the command line:

Set-AuthenticodeSignature -Certificate @(Get-ChildItem -Path cert:\ -Recurse -CodeSigningCert)[0] -TimestampServer "" -FilePath '.\scriptName.ps1'

There are other more automated ways to code sign Powershell scripts if doing such things is more than a "once in a while" activity.

Caveat #2: Detecting Dell Dock Firmware Version

For desktop/laptop, it's pretty straightforward to get a firmware (BIOS) version number with:

Get-WmiObject -Class Win32_BIOS | Select-Object SMBIOSBIOSVersion

However, no such simple and common WMI class is available for the Dell Docks.

The solution was to deploy the Dell System Management utility to devices in scope as a prerequisite. Once installed, access to the dock firmware (if attached) would be queried with:

Get-WmiObject -Namespace root\dell\sysinv dell_softwareidentity -ErrorAction Stop | 
    Select-Object VersionString, ElementName | 
    Where-Object ElementName -like "*WD19*" | 
    Sort-Object ElementName

The Deployment Collection(s)

For each baseline, I created "root" collections based on noncompliance to automatically corral noncompliant devices. I then created separate collections for the deployment of update applications as I normally do and used the noncompliant collection as an include rule for the update collection's membership. This step keeps the scope and deployment separate so the collections can be used for other things as necessary, but connected and automatic for this solution.

The Update Application

A repository of firmware update applications is maintained and distributed separately as a "Package" as it's used for many purposes. It has a file structure similar to:

 | Latitude5400\
    | Long_File_Name_Of_5400_Firmware_Update.exe
 | Latitude5420\
    | Long_File_Name_Of_5420_Firmware_Update.exe
 | .
 | .
 | .

A "Program" is created in this package, for this example it's called Update-Firmware.ps1 and looks like this:

# Model List and Versions
$ModelList = @{
  '5400' = '0001.00xx.00yy'
  '5420' = '0001.00xx.00yy'

# BIOS Password (section redacted)
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secString)
$BIOSpassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)

# Figure out Bitlocker Status
$BitLockerEnabled = $false
try {
  $BitLockerData = Get-BitLockerVolume -MountPoint "C:" -ErrorAction SilentlyContinue | 
      Select-Object VolumeStatus, ProtectionStatus
  if ($BitLockerData.ProtetctionStatus -eq "On") {
    $BitLockerEnabled = $true
} catch {}

# Invoke BIOS Update if necesary
try {
  if (((Get-WmiObject -Class Win32_ComputerSystem).Manufacturer).StartsWith("Dell", $true, $null)) {
    $ModelNumber = (Get-WmiObject Win32_ComputerSystem).Model -replace '\D+(\d+)', '$1'
    if ($ModelList.ContainsKey($ModelNumber)) {
      # Figure out the executable filename and path
      $ComputerModel = Get-WmiObject -Class Win32_ComputerSystem | 
          Select-Object -ExpandProperty Model
      $ModelPath = $ComputerModel.trim() -Replace " ",""
      $BiosFileName = Get-ChildItem "$PSScriptRoot\$ModelPath\*.exe" -Verbose | 
          Select-Object -ExpandProperty Name
      $exe = "$ModelPath\$BiosFileName"
      $cmdArgs = "/p=$BIOSpassword /s /f /r /l=C:\WINDOWS\TEMP\$BiosFileName.log"

      # Grab the current BIOS version
      $WMIData = Get-WmiObject -Namespace root\dell\sysinv dell_softwareidentity -ErrorAction Stop
      $DellData = $WMIData | Select-Object VersionString, ElementName | 
          Where-Object ElementName -like "*BIOS*"

      if ($DellData.VersionString -lt $ModelList[$ModelNumber]) {
        if ($BitLockerEnabled) { Suspend-BitLocker -MountPoint "C:" -RebootCount 1 }
        Start-Process "$PSScriptRoot\$exe" -ArgumentList $cmdArgs -PassThru -Wait
      } else {
        Write-Output "FIRMWARE OK"
    } else {
      Write-Output "ERROR: MODEL NOT IN SCOPE"
  } else {
    Write-Output "ERROR: MAKE NOT IN SCOPE"
catch {
  Write-Output "ERROR: LOAD WMI"

Similar to the compliance rule above, it starts with supporting multiple models in scope followed by a section for handling our BIOS password (most of which has been redacted). We use a process that outputs a SecureString so it is not exposed in code, etc.

A process to determine if we need to deal with BitLocker follows, specifically to determine if BitLocker is enabled so we can temporarily suspend while the firmware update is applied. Much of our fleet (and all of the high-affinity fleet) is required to have BitLocker enabled, but some of our multi-user fleet has been excluded for Reasons.

The remaining bits determine which exe to use and the necessary command-line arguments. This is determined based on the model we get out of WMI. Ideally we'd use the SKU number for brevity, but this version has made it more palatable for folks to broadly understand and maintain.

Ultimately if things are good to go (and an update is deemed necessary), the update application is invoked with the following bits:

$exe = "$ModelPath\$BiosFileName"
$cmdArgs = "/p=$BIOSpassword /s /f /r /l=C:\WINDOWS\TEMP\$BiosFileName.log"
Start-Process "$PSScriptRoot\$exe" -ArgumentList $cmdArgs -PassThru -Wait

The arguments passed along force the update to happen silently, with reboot, and logged to a common/temp location. When invoked by the tech/user "clicks the button" in Software Center it works seamlessly and has proven remarkably effective.

Next Steps and Updates

Ideally we would figure out an amiable way to handle these updates with a forced reboot but this current structure affords us a balance of touch versus force. We can selectively add devices to a required deployment if necessary, though.

The takeaway of this process is that we've now got a proven process available to address such updates. Even if it requires someone to "click the button" the overall tech burden and requisite knowledge barrier is significantly reduced.

A real incremental change that has a positive benefit to the health of the fleet!