Incident
Dell security bulletin dropped on a Friday: critical BIOS vulnerability affecting OptiPlex and Latitude models, CVSS 8.2, local privilege escalation. Our client had 127 affected Dell machines split across 4 locations, and the cyber insurance policy obligated them to patch within 30 days of a critical bulletin. Manual updates would mean physically visiting every machine — weeks of work and a lot of windshield time.
The challenge
BIOS updates used to be a hands-on nightmare. You download the .exe from Dell's support site, stage it on a USB stick or a share, visit each machine, run it, reboot, pray nothing flashes wrong. For 127 machines across 4 sites, that's weeks and a nonzero brick risk per machine.
Initial assessment:
- affected machines: 127 Dell OptiPlex 7080, 7090, Latitude 5520, 5530
- vulnerability: BIOS privilege escalation, CVSS 8.2
- compliance deadline: 30 days from bulletin release
- constraint: zero disruption to business operations — updates during off-hours only
Dell Command Update is Dell's official tool for managing drivers and BIOS. It has a CLI mode that can scan for updates, download them, and apply them silently. The trick is getting it installed and configured across the fleet without touching each machine.
The solution: Dell command update via RMM
We built a PowerShell wrapper that installs Dell Command Update via winget (so it's always the current version), configures it for silent unattended operation, and runs the scan/apply cycle. Three configuration options did the heavy lifting:
- autoSuspendBitLocker=enable: automatically suspends BitLocker before a BIOS update and resumes after. Without this, post-update reboots hit the BitLocker recovery key prompt and users get locked out.
- userConsent=disable: no popups asking users to approve updates
- reboot=disable: updates stage but don't force a reboot — we control reboot timing separately through the RMM
$ErrorActionPreference = 'Stop'
<#
██╗ ██╗███╗ ███╗███████╗██╗ ██╗ █████╗ ██╗ ██╗██╗ ██╗
██║ ██║████╗ ████║██╔════╝██║ ██║██╔══██╗██║ ██║██║ ██╔╝
██║ ██║██╔████╔██║█████╗ ███████║███████║██║ █╗ ██║█████╔╝
██║ ██║██║╚██╔╝██║██╔══╝ ██╔══██║██╔══██║██║███╗██║██╔═██╗
███████╗██║██║ ╚═╝ ██║███████╗██║ ██║██║ ██║╚███╔███╔╝██║ ██╗
╚══════╝╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚══╝╚══╝ ╚═╝ ╚═╝
================================================================================
SCRIPT : Dell Command Update v2.1.4
AUTHOR : Limehawk.io
DATE : January 2026
USAGE : .\dell_command_update.ps1
================================================================================
FILE : dell_command_update.ps1
DESCRIPTION : Installs Dell Command Update and applies driver/firmware updates
--------------------------------------------------------------------------------
README
--------------------------------------------------------------------------------
PURPOSE
Installs Dell Command Update via winget and runs it to scan and apply
driver/firmware updates silently. Designed for automated Dell system maintenance
in RMM environments.
DATA SOURCES & PRIORITY
1) Hardcoded values (defined within the script body)
2) winget package manager
3) Dell Command Update CLI
REQUIRED INPUTS
- DcuCliPath : Path to Dell Command Update CLI executable
- DcuLogPath : Directory for DCU log files
- WingetId : winget package ID for Dell Command Update
SETTINGS
- Uses winget for package management
- Configures DCU to auto-suspend BitLocker and disable user consent prompts
- Applies updates without rebooting (reboot=disable)
- Logs scan and apply operations to C:\dell\logs
BEHAVIOR
1. Verifies winget is available
2. Installs Dell Command Update if not present
3. Verifies DCU CLI exists
4. Configures DCU for silent operation
5. Scans for available updates and outputs log to console
6. Applies updates without rebooting and outputs log to console
PREREQUISITES
- Windows 10 1809+ or Windows 11 with winget
- Dell system (script will fail on non-Dell hardware)
- Admin privileges for installation and updates
SECURITY NOTES
- No secrets in logs
- Only affects Dell driver/firmware components
EXIT CODES
- 0: Success
- 1: Failure (winget not found, DCU not installed, etc.)
EXAMPLE RUN
[INFO] INPUT VALIDATION
==============================================================
DCU CLI Path : C:\Program Files\Dell\CommandUpdate\dcu-cli.exe
DCU Log Path : C:\dell\logs
Winget ID : Dell.CommandUpdate
[RUN] OPERATION
==============================================================
Checking for winget...
winget found
Checking for Dell Command Update...
Dell Command Update already installed
Configuring Dell Command Update...
Configuration complete
Scanning for updates...
Scan complete
[INFO] SCAN LOG
==============================================================
<Dell Command Update scan results displayed here>
[RUN] APPLY UPDATES
==============================================================
Applying updates...
Updates applied
[INFO] APPLY UPDATES LOG
==============================================================
<Dell Command Update apply results displayed here>
[OK] RESULT
==============================================================
Status : Success
[OK] SCRIPT COMPLETED
==============================================================
--------------------------------------------------------------------------------
CHANGELOG
--------------------------------------------------------------------------------
2026-01-19 v2.1.4 Fixed EXAMPLE RUN section formatting
2026-01-19 v2.1.3 Updated to two-line ASCII console output style
2026-01-14 v2.1.2 Fixed header formatting for framework compliance
2025-12-23 v2.1.1 Updated to Limehawk Script Framework
2025-12-01 v2.1.0 Output scan and apply log contents to console for RMM visibility
2025-12-01 v2.0.0 Switched from Chocolatey to winget for package management
2025-11-29 v1.0.0 Initial Style A implementation
================================================================================
#>
Set-StrictMode -Version Latest
# ==== STATE ====
$errorOccurred = $false
$errorText = ""
# ==== HARDCODED INPUTS ====
$DcuCliPath = "C:\Program Files\Dell\CommandUpdate\dcu-cli.exe"
$DcuLogPath = "C:\dell\logs"
$WingetId = "Dell.CommandUpdate"
# ==== VALIDATION ====
if ([string]::IsNullOrWhiteSpace($DcuCliPath)) {
$errorOccurred = $true
if ($errorText.Length -gt 0) { $errorText += "`n" }
$errorText += "- DcuCliPath is required."
}
if ([string]::IsNullOrWhiteSpace($DcuLogPath)) {
$errorOccurred = $true
if ($errorText.Length -gt 0) { $errorText += "`n" }
$errorText += "- DcuLogPath is required."
}
if ([string]::IsNullOrWhiteSpace($WingetId)) {
$errorOccurred = $true
if ($errorText.Length -gt 0) { $errorText += "`n" }
$errorText += "- WingetId is required."
}
if ($errorOccurred) {
Write-Host ""
Write-Host "[ERROR] VALIDATION FAILED"
Write-Host "=============================================================="
Write-Host $errorText
exit 1
}
# ==== RUNTIME OUTPUT ====
Write-Host ""
Write-Host "[INFO] INPUT VALIDATION"
Write-Host "=============================================================="
Write-Host "DCU CLI Path : $DcuCliPath"
Write-Host "DCU Log Path : $DcuLogPath"
Write-Host "Winget ID : $WingetId"
Write-Host ""
Write-Host "[RUN] OPERATION"
Write-Host "=============================================================="
try {
# Check for winget
Write-Host "Checking for winget..."
$wingetPath = Get-Command "winget" -ErrorAction SilentlyContinue
if (-not $wingetPath) {
throw "winget is not available. Windows 10 1809+ or Windows 11 required."
}
Write-Host "winget found"
# Check if Dell Command Update is already installed
Write-Host "Checking for Dell Command Update..."
$installed = winget list -e --id $WingetId --accept-source-agreements 2>$null | Select-String $WingetId
if (-not $installed) {
Write-Host "Installing $WingetId..."
$installResult = winget install -e --id $WingetId --silent --accept-package-agreements --accept-source-agreements 2>&1
if ($LASTEXITCODE -ne 0) {
throw "Failed to install $WingetId. Exit code: $LASTEXITCODE"
}
Write-Host "Installation complete"
} else {
Write-Host "Dell Command Update already installed"
# Check for updates
Write-Host "Checking for DCU updates..."
$upgradeResult = winget upgrade -e --id $WingetId --silent --accept-package-agreements --accept-source-agreements 2>&1
if ($LASTEXITCODE -eq 0) {
Write-Host "DCU upgraded to latest version"
}
}
# Verify DCU CLI exists
if (-not (Test-Path $DcuCliPath)) {
throw "DCU CLI not found at $DcuCliPath. Installation may have failed."
}
# Ensure log directory exists
if (-not (Test-Path $DcuLogPath)) {
New-Item -Path $DcuLogPath -ItemType Directory -Force | Out-Null
Write-Host "Created log directory: $DcuLogPath"
}
# Configure DCU for silent operation
Write-Host "Configuring Dell Command Update..."
& $DcuCliPath /configure -silent -autoSuspendBitLocker=enable -userConsent=disable
Write-Host "Configuration complete"
# Scan for updates
Write-Host "Scanning for updates..."
& $DcuCliPath /scan -outputLog="$DcuLogPath\scan.log"
Write-Host "Scan complete"
# Output scan log
Write-Host ""
Write-Host "[INFO] SCAN LOG"
Write-Host "=============================================================="
if (Test-Path "$DcuLogPath\scan.log") {
Get-Content "$DcuLogPath\scan.log" | ForEach-Object { Write-Host $_ }
} else {
Write-Host "Scan log not found."
}
# Apply updates without rebooting
Write-Host ""
Write-Host "[RUN] APPLY UPDATES"
Write-Host "=============================================================="
Write-Host "Applying updates..."
& $DcuCliPath /applyUpdates -reboot=disable -outputLog="$DcuLogPath\applyUpdates.log"
Write-Host "Updates applied"
# Output apply log
Write-Host ""
Write-Host "[INFO] APPLY UPDATES LOG"
Write-Host "=============================================================="
if (Test-Path "$DcuLogPath\applyUpdates.log") {
Get-Content "$DcuLogPath\applyUpdates.log" | ForEach-Object { Write-Host $_ }
} else {
Write-Host "Apply updates log not found."
}
} catch {
$errorOccurred = $true
$errorText = $_.Exception.Message
}
if ($errorOccurred) {
Write-Host ""
Write-Host "[ERROR] OPERATION FAILED"
Write-Host "=============================================================="
Write-Host $errorText
Write-Host ""
Write-Host "[ERROR] RESULT"
Write-Host "=============================================================="
Write-Host "Status : Failure"
} else {
Write-Host ""
Write-Host "[OK] RESULT"
Write-Host "=============================================================="
Write-Host "Status : Success"
}
Write-Host ""
if ($errorOccurred) {
Write-Host "[ERROR] FINAL STATUS"
} else {
Write-Host "[OK] FINAL STATUS"
}
Write-Host "=============================================================="
if ($errorOccurred) {
Write-Host "Dell Command Update process failed. See error above."
} else {
Write-Host "Dell Command Update completed successfully."
Write-Host "Reboot may be required for some updates to take effect."
}
Write-Host ""
if ($errorOccurred) {
Write-Host "[ERROR] SCRIPT COMPLETED"
} else {
Write-Host "[OK] SCRIPT COMPLETED"
}
Write-Host "=============================================================="
if ($errorOccurred) {
exit 1
} else {
exit 0
}
Deployment strategy
We didn't blast the script to all 127 machines at once. Firmware is the one area where "deploy to all" is a genuinely bad idea — one bad update or a power loss mid-flash can brick a machine, and bricked machines across four locations is not a Monday anyone wants.
- pilot group (5 machines): one of each model type, users notified, ran during lunch
- wave 1 (30 machines): IT department and volunteers, overnight deployment
- wave 2 (50 machines): non-critical workstations, weekend deployment
- wave 3 (42 machines): remaining systems including exec laptops, scheduled maintenance window
After each wave we verified BIOS versions via RMM inventory scan. Any machine that didn't update got flagged for manual review before we moved on.
Outcome
All 127 machines patched within 2 weeks, well ahead of the 30-day deadline. Zero bricked machines. Zero user complaints. Total hands-on time: about 4 hours for script development, pilot testing, and monitoring. Without automation this would have been 200+ hours of on-site work split across four offices.
machines patched: 127 across 4 locations time to complete: 2 weeks (phased rollout) hours saved: 196+ vs manual updates bricked machines: zero