ConfigMgr: Dynamic BIOS Updates via OSD

 Overview

In an earlier post, I discussed stumbling upon outdated BIOS versions for systems already deployed in the field. In this follow-up, I'll delve into the implementation of automated updates through the build process within a dynamic environment, eliminating the need to manually download each BIOS file.

Note: This approach is for bare metal installs.

The Details

Before starting, ensure you've downloaded the Dell System Firmware Update Utility, crucial for BIOS updates. Once downloaded, navigate to your Configuration Manager Console and create a package, including relevant metadata. Key step: in the source folder, ensure Flash64W.exe and the PowerShell script are placed.


After distributing your content to your distribution point(s), open your task sequence and establish a new group. As this method applies exclusively to Dell Systems, incorporate conditions accordingly. Implement a WQL query to the group: SELECT * FROM Win32_ComputerSystem WHERE Manufacturer LIKE "%Dell%"

Under the newly created group, include a "Run PowerShell Script" action and specify your package and PowerShell script.



Lastly, include a "Restart Computer" action with the condition: Task Sequence Variable BIOSinstalled equals "True". This step is important as it allows skipping an unnecessary action if the system already has the latest BIOS version installed. Your final group layout should resemble the following in your task sequence.


And there you have it—a streamlined method to automatically update your BIOS version for systems during the build process, eliminating the need to manage installation files manually. This approach enhances efficiency and ensures that your systems are always up to date without extra effort.

Kudos
  • This post would not be complete without giving a shoutout to Tyler Siniff for the collaboration and spearheading this effort.

Links

PowerShell Code

##*=============================================
##* START VARIABLE LISTINGS
##*=============================================

$Date = Get-Date -Format 'MMMdd_HHMMttss'

# Application Details - Modify Script Details
[string]$appVendor = 'Company'
[string]$appName = 'DellBIOSUpdatesDynamic' ## Update Me
[string]$appNameWithSpaces = "Dell BIOS Update - Dynamic" ## Update Me
[string]$appVersion = '1.0.0.0' ## Update Me
[string]$appTitle = "$appVendor $appName $appVersion"
[string]$appArch = 'x64x86' ## Update Me (If Needed)
[string]$appLang = 'EN'
[string]$appRevision = '01'
[string]$appScriptVersion = '1.0.0.0' ## Update Me
[string]$appScriptDate = '04/12/2024' ## Update Me
[string]$appScriptAuthor = 'Nathan C. Hoy and Tyler Siniff' ## Update Me
[string]$runtime = '5'

#region DONOTMODIfY

#declare env variable
$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment
$tsenv.Value("BIOSinstalled") = $false

# Log File Info 
$LogPath = "$PSScriptRoot\Logs"

$LogName = "$appName" + "_" + "$appScriptVersion" + "_" + "PS.log"
$LogFile = Join-Path -Path $LogPath -ChildPath $LogName

$FolderCheckLeaf = Test-Path -Path $LogPath -PathType Leaf
$FolderCheckContainer = Test-Path -Path $LogPath -PathType Container

# Specs and Details
$PackageName = (Get-Process -Id $PID).ProcessName
$SerialNumber = (Get-WmiObject win32_bios).serialnumber
$Manufacturer = (Get-WmiObject win32_bios).Manufacturer
$BIOsVerison = (Get-WmiObject win32_bios).SMBIOSBIOSVersion
$Model = (Get-WmiObject win32_computersystem).Model
$Name = (Get-WmiObject win32_computersystem).Name
$GB = Get-WmiObject win32_LogicalDisk | Measure-Object -Sum Size
$GB2 = Get-WmiObject win32_LogicalDisk | Measure-Object -Sum freespace
$Disk = "{0:N2}" -f ($GB.Sum / 1GB) + " GB"
$FreeSpace = "{0:N2}" -f ($gb2.sum / 1GB) + " GB"
[psobject]$envOS = Get-WmiObject -Class 'Win32_OperatingSystem' -ErrorAction 'SilentlyContinue'
[string]$envOSName = $envOS.Caption.Trim()
[string]$envOSServicePack = $envOS.CSDVersion
[version]$envOSVersion = $envOS.Version
[string]$envOSVersionMajor = $envOSVersion.Major
[string]$envOSVersionMinor = $envOSVersion.Minor
[string]$envOSVersionBuild = $envOSVersion.Build
[string]$envOSVersionRevision = ,((Get-ItemProperty -Path 'HKLM:SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name 'BuildLabEx' -ErrorAction 'SilentlyContinue').BuildLabEx -split '\.') | ForEach-Object { $_[1] }
If ($envOSVersionRevision -notmatch '^[\d\.]+$') { $envOSVersionRevision = '' }
If ($envOSVersionRevision) { [string]$envOSVersion = "$($envOSVersion.ToString()).$envOSVersionRevision" }
Else { "$($envOSVersion.ToString())" }

#endregion

# Custom Items
$FlagSingle = $false
$FlagMultiple = $false
$DownloadPath = "$PSScriptRoot\Repo"
$FlashExe = "$PSScriptRoot\Flash64W.exe"
$BIOSUpdate = "$DownloadPath\biosupdate.exe"
$BIOSUpdateLog = "$LogPath\biosupdatelog.txt"

#*=============================================
##* END VARIABLE LISTINGS
##*=============================================

##*=============================================
##* START FUNCTION LISTINGS
##*=============================================

#region Log Functions

Function Start-Log
{
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$LogPath,
        [Parameter(Mandatory = $true, Position = 1)]
        [string]$LogName,
        [Parameter(Mandatory = $true, Position = 2)]
        [string]$ScriptVersion
    )
    
    Process
    {
        #Create file and start logging
        If (!(Test-Path -Path $LogFile))
        {
            New-Item -Path $LogFile -ItemType File
        }
        Add-Content -Path $LogFile -Value "***************************************************************************************************"
        Add-Content -Path $LogFile -Value "Started processing at [$([DateTime]::Now)]."
        Add-Content -Path $LogFile -Value "***************************************************************************************************"
        Add-Content -Path $LogFile -Value ""
        Add-Content -Path $LogFile -Value "Running script version [$ScriptVersion]."
        Add-Content -Path $LogFile -Value ""
        Add-Content -Path $LogFile -Value "***************************************************************************************************"
        Add-Content -Path $LogFile -Value "                                                               Package Information"
        Add-Content -Path $LogFile -Value "***************************************************************************************************"
        Add-Content -Path $LogFile -Value ""
        Add-Content -Path $LogFile -Value "Package Name = [$appNameWithSpaces]"
        Add-Content -Path $LogFile -Value "App Version = [$appVersion]"
        Add-Content -Path $LogFile -Value "Script Version = [$appScriptVersion]"
        Add-Content -Path $LogFile -Value ""
        Add-Content -Path $LogFile -Value "***************************************************************************************************"
        Add-Content -Path $LogFile -Value "                                                               System Information"
        Add-Content -Path $LogFile -Value "***************************************************************************************************"
        Add-Content -Path $LogFile -Value ""
        Add-Content -Path $LogFile -Value "Name = [$Name]"
        Add-Content -Path $LogFile -Value "OS Name= [$envOSName]"
        Add-Content -Path $LogFile -Value "OS Version= [$envOSVersion]"
        Add-Content -Path $LogFile -Value "Serial Number= [$SerialNumber]"
        Add-Content -Path $LogFile -Value "BIOS Version = [$BIOsVerison]"
        Add-Content -Path $LogFile -Value "Manufacturer = [$Manufacturer]"
        Add-Content -Path $LogFile -Value "Model = [$Model]"
        Add-Content -Path $LogFile -Value "Total Disk Size = [$Disk]"
        Add-Content -Path $LogFile -Value "Free Disk Space = [$FreeSpace]"
        Add-Content -Path $LogFile -Value ""
        Add-Content -Path $LogFile -Value "***************************************************************************************************"
    }
}

Function Write-PoShLog
{
    [CmdletBinding()]
    [OutputType([object[]])]
    Param (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        $Message
    )
    
    Process
    {
        $Message = "$Message  [$([DateTime]::Now)]"
        $LogPath = $LogFile
        
        #Write Content to Log
        Add-Content  $LogPath -Value $Message
    }
}

Function Stop-Log
{
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$LogPath,
        [Parameter(Mandatory = $false, Position = 1)]
        [switch]$NoExit
        
    )
    Process
    {
        Add-Content -Path $LogPath -Value ""
        Add-Content -Path $LogPath -Value "***************************************************************************************************"
        Add-Content -Path $LogPath -Value "Finished processing at [$([DateTime]::Now)]."
        Add-Content -Path $LogPath -Value "***************************************************************************************************"
        
        #Exit calling script If NoExit has not been specIfied or is set to False
        If (!($NoExit) -or ($NoExit -eq $False))
        {
            Exit
        }
    }
}

#endregion

Function Get-DellCatalogPC
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false, Position = 0)]
        [ValidateSet('Application', 'BIOS', 'Driver', 'Firmware')]
        [string]$Component,
        [Parameter(Mandatory = $false, Position = 1)]
        [switch]$Compatible
    )
    $VerbosePreference = "Continue"
    
    #=======================================================================
    # Compatibility
    #=======================================================================
    
    $SystemSKU = $((Get-WmiObject -Class Win32_ComputerSystem).SystemSKUNumber).Trim()
    $BIOSVersion = $((Get-WmiObject -Class Win32_BIOS).SMBIOSBIOSVersion).Trim()
    
    #=======================================================================
    # Variables
    #=======================================================================
    $DellDownloadsUrl = "http://downloads.dell.com/"
    $CatalogPcUrl = "http://downloads.dell.com/catalog/CatalogPC.cab"
    
    $RepoFolderCheckLeaf = Test-Path -Path $DownloadPath -PathType Leaf
    $RepoFolderCheckContainer = Test-Path -Path $DownloadPath -PathType Container
    
    
    $OfflineCatalogPcFullName = Join-Path $DownloadPath "Get-DellCatalogPC.xml"
    $CatalogPcCabName = [string]($CatalogPcUrl | Split-Path -Leaf)
    $CatalogPcCabFullName = Join-Path $DownloadPath $CatalogPcCabName
    $CatalogPcXmlName = "CatalogPC.xml"
    $CatalogPCXmlFullName = Join-Path $DownloadPath $CatalogPcXmlName
    
    #=======================================================================
    # Offline Catalog
    #=======================================================================
    
    If (($RepoFolderCheckLeaf -eq $true) -or ($RepoFolderCheckContainer -eq $false))
    {
        Remove-Item -Path $DownloadPath -Force -ErrorAction SilentlyContinue | Out-Null
        New-Item -ItemType Directory -Path $DownloadPath -Force
        
        $RepoFolderCheckLeaf = Test-Path -Path $LogPath -PathType Leaf
        $RepoFolderCheckContainer = Test-Path -Path $LogPath -PathType Container
    }
    
    If ($RepoFolderCheckContainer -eq $true)
    {
        If (Test-Path $OfflineCatalogPcFullName)
        {
            $ExistingFile = Get-Item $OfflineCatalogPcFullName
            
            If (((Get-Date) - $ExistingFile.CreationTime).TotalDays -gt 14)
            {
                Remove-Item -Path $OfflineCatalogPcFullName -Force -ErrorAction SilentlyContinue
            }
        }
        
        If (Test-Path $OfflineCatalogPcFullName)
        {
            $DellCatalogPc = Import-Clixml -Path $OfflineCatalogPcFullName
        }
        Else
        {
            If (Test-Path $CatalogPcCabFullName)
            {
                $ExistingFile = Get-Item $CatalogPcCabFullName
                
                If (((Get-Date) - $ExistingFile.CreationTime).TotalDays -gt 1)
                {
                    Remove-Item -Path $CatalogPcCabFullName -Force -ErrorAction SilentlyContinue
                }
            }
            
            If (-NOT (Test-Path $CatalogPcCabFullName))
            {
                (New-Object System.Net.WebClient).DownloadFile($CatalogPcUrl, "$CatalogPcCabFullName")
            }
            
            If (-NOT (Test-Path $CatalogPcCabFullName))
            {
                Write-PoShLog "[ERROR] Could not download the Dell CatalogPC.cab"
                Break
            }
            
            Expand "$CatalogPcCabFullName" "$CatalogPCXmlFullName" | Out-Null
            
            If (-NOT (Test-Path $CatalogPCXmlFullName))
            {
                Add-Content -Path $LogFile "Could Not Expand the Dell CatalogPC.xml"
                
                Break
            }
            
            [xml]$XMLCatalogPcUrl = Get-Content "$CatalogPCXmlFullName" -ErrorAction Stop
            
            $DellCatalogPc = $XMLCatalogPcUrl.ManIfest.SoftwareComponent
            
            $DellCatalogPc = $DellCatalogPc | `
            Select-Object @{ Label = "Component"; Expression = { ($_.ComponentType.Display.'#cdata-section'.Trim()) }; },
                          @{ Label = "ReleaseDate"; Expression = { [datetime] ($_.dateTime) }; },
                          @{ Label = "Name"; Expression = { ($_.Name.Display.'#cdata-section'.Trim()) }; },
                          #@{Label="Description";Expression={($_.Description.Display.'#cdata-section'.Trim())};},
                          @{ Label = "DellVersion"; Expression = { $_.dellVersion }; },
                          @{ Label = "Url"; Expression = { -join ($DellDownloadsUrl, $_.path) }; },
                          @{ Label = "VendorVersion"; Expression = { $_.vendorVersion }; },
                          @{ Label = "Criticality"; Expression = { ($_.Criticality.Display.'#cdata-section'.Trim()) }; },
                          @{ Label = "FileName"; Expression = { (split-path -leaf $_.path) }; },
                          @{ Label = "SizeMB"; Expression = { '{0:f2}' -f ($_.size/1MB) }; },
                          @{ Label = "PackageID"; Expression = { $_.packageID }; },
                          @{ Label = "PackageType"; Expression = { $_.packageType }; },
                          @{ Label = "ReleaseID"; Expression = { $_.ReleaseID }; },
                          @{ Label = "Category"; Expression = { ($_.Category.Display.'#cdata-section'.Trim()) }; },
                          @{ Label = "SupportedDevices"; Expression = { ($_.SupportedDevices.Device.Display.'#cdata-section'.Trim()) }; },
                          @{ Label = "SupportedBrand"; Expression = { ($_.SupportedSystems.Brand.Display.'#cdata-section'.Trim()) }; },
                          @{ Label = "SupportedModel"; Expression = { ($_.SupportedSystems.Brand.Model.Display.'#cdata-section'.Trim()) }; },
                          @{ Label = "SupportedSystemID"; Expression = { ($_.SupportedSystems.Brand.Model.systemID) }; },
                          @{ Label = "SupportedOperatingSystems"; Expression = { ($_.SupportedOperatingSystems.OperatingSystem.Display.'#cdata-section'.Trim()) }; },
                          @{ Label = "SupportedArchitecture"; Expression = { ($_.SupportedOperatingSystems.OperatingSystem.osArch) }; },
                          @{ Label = "HashMD5"; Expression = { $_.HashMD5 }; }
            
            $DellCatalogPc = $DellCatalogPc | Sort-Object ReleaseDate -Descending
            $DellCatalogPc | Export-Clixml -Path $OfflineCatalogPcFullName
        }
        #=======================================================================
        # Filter Compatible
        #=======================================================================
        
        If ($Compatible)
        {
            $DellCatalogPc = $DellCatalogPc | Where-Object { $_.SupportedSystemID -contains $SystemSKU }
        }
        
        #=======================================================================
        # Filter Component
        #=======================================================================
        If ($Component)
        {
            $DellCatalogPc = $DellCatalogPc | Where-Object { $_.Component -eq $Component }
        }
        
        $DellCatalogPc
    }
}

##*=============================================
##* END FUNCTION LISTINGS
##*=============================================
##*=============================================
##* START SCRIPT BODY
##*=============================================

# Log Path

If (($FolderCheckLeaf -eq $true) -or ($FolderCheckContainer -eq $false))
{
    Remove-Item -Path $LogPath -Force -ErrorAction SilentlyContinue | Out-Null
    New-Item -ItemType Directory -Path $LogPath -Force | Out-Null;
    
    $FolderCheckLeaf = Test-Path -Path $LogPath -PathType Leaf
    $FolderCheckContainer = Test-Path -Path $LogPath -PathType Container
}

# Repo Path

If (($RepoCheckLeaf -eq $true) -or ($RepoCheckContainer -eq $false))
{
    Remove-Item -Path $RepoPath -Force -ErrorAction SilentlyContinue | Out-Null
    New-Item -ItemType Directory -Path $RepoPath -Force | Out-Null;
    
    $RepoCheckLeaf = Test-Path -Path $RepoPath -PathType Leaf
    $RepoCheckContainer = Test-Path -Path $RepoPath -PathType Container
}

If ($FolderCheckContainer -eq $true)
{
    Start-Log -LogPath $LogPath -LogName $LogName -ScriptVersion $appScriptVersion | Out-Null
    
    If (Test-Path -Path $LogPath)
    {
        #=======================================================================
        # Gather BIOS Information
        #=======================================================================
        
        $BIOSresults = Get-DellCatalogPC -Compatible -Component BIOS
        
        #region log results
        If ($BIOSresults.Component.Count -eq 1)
        {
            $FlagSingle = $true
            
            Add-Content -Path $LogFile "***************************************************************************************************"
            Add-Content -Path $LogFile "                                                               BIOS Details | 1 of 1"
            Add-Content -Path $LogFile "***************************************************************************************************"
            
            ForEach ($item in $BIOSresults.PSObject.Get_Properties())
            {
                $Name = $($item).Name
                $Value = $($item).Value
                Add-Content -Path $LogFile "$Name = [$Value]"
            }
            
            Add-Content -Path $LogFile "***************************************************************************************************"
        }
        ElseIf ($BIOSresults.Component.Count -gt 1)
        {
            $FlagMultiple = $true
            
            Add-Content -Path $LogFile "***************************************************************************************************"
            
            ForEach ($item in $BIOSresults)
            {
                $MultipleCount++
                Add-Content -Path $LogFile "                                                               BIOS Details | $MultipleCount of $($BIOSresults.Component.Count)"
                Add-Content -Path $LogFile "***************************************************************************************************"
                
                ForEach ($subitem in $item.PSObject.Get_Properties())
                {
                    $Name = $($subitem).Name
                    $Value = $($subitem).Value
                    Add-Content -Path $LogFile "$Name = [$Value]"
                }
                
                Add-Content -Path $LogFile "***************************************************************************************************"
            }
        }
        
        #endregion
        
        #region Download/initiate BIOS update
        
        #=======================================================================
        # Single Update
        #=======================================================================
        
        If ($FlagSingle -eq $true)
        {
            If ($BIOSresults.VendorVersion)
            {
                If ([version]$BIOsVerison -ge [version]$($BIOSresults.VendorVersion))
                {
                    $BIOsCurrent = "Yes"
                }
                Else
                {
                    $BIOsCurrent = "No"
                }
                
                #download update
                
                If ($biosCurrent -eq "no")
                {
                    Add-Content -Path $LogFile "BIOS Current? | [No]"
                    Try
                    {
                        Invoke-WebRequest -Uri $($BIOSresults.Url) -OutFile $BIOSUpdate
                        Add-Content -Path $LogFile "Downloadubg Bios Update | [Complete]"
                    }
                    Catch
                    {
                        Add-Content -Path $LogFile "Downloading Bios Update | [Failed]"
                        Stop-Log -LogPath $LogFile
                        [Environment]::Exit
                    }
                }
                Else
                {
                    Add-Content -Path $LogFile "BIOS Current | [Yes]"
                    Add-Content -Path $LogFile "No BIOS update needed, stopping script"
                    Stop-Log -LogPath $LogFile
                    [Environment]::Exit
                }
                
                #check that the file exists
                
                $biosUpdateExists = Test-Path -Path $BIOSUpdate -ErrorAction SilentlyContinue
                
                If ($biosUpdateExists -eq $true)
                {
                    Try
                    {
                        Start-Process -FilePath $FlashExe -ArgumentList "/b=$BIOSUpdate", "/s", "/l=$BIOSUpdateLog"
                        
                        If ($?)
                        {
                            Add-Content -Path $LogFile "Running Bios Update | [Complete]"
                            #change env variable to true to tell TS to reboot
                            $tsenv.Value("BIOSinstalled") = $true
                        }
                        Else
                        {
                            Add-Content -Path $LogFile "Running Bios Update | [Failed]"
                            Stop-Log -LogPath $LogFile
                            [Environment]::Exit
                        }
                    }
                    Catch
                    {
                        Add-Content -Path $LogFile "Starting Bios Update | [Failed]"
                        Stop-Log -LogPath $LogFile
                        [Environment]::Exit
                    }
                }
                Else
                {
                    Add-Content -Path $LogFile "Bios Update Exists Check | [Failed]"
                    Stop-Log -LogPath $LogFile
                    [Environment]::Exit
                }
            }
        }
        
        #=======================================================================
        # Multiple - Search for Latest
        #=======================================================================
        
        ElseIf ($FlagMultiple -eq $true)
        {
            $Sort = $BIOSresults | Sort-Object -Property ReleaseDate -Descending
            $MostRecent = $Sort[0]
            
            If ([version]$BIOsVerison -ge [version]$($MostRecent.VendorVersion))
            {
                $BIOsCurrent = "Yes"
            }
            Else
            {
                $BIOsCurrent = "No"
            }
            
            #Download Update
            
            If ($biosCurrent -eq "no")
            {
                Add-Content -Path $LogFile "BIOS Current? | [No]"
                Try
                {
                    Invoke-WebRequest -Uri $($MostRecent.Url) -OutFile $BIOSUpdate
                    Add-Content -Path $LogFile "Downloading Bios Update | [Complete]"
                }
                Catch
                {
                    Add-Content -Path $LogFile "Downloadubg Bios Update | [Failed]"
                    Stop-Log -LogPath $LogFile
                    [Environment]::Exit
                }
            }
            Else
            {
                Add-Content -Path $LogFile "BIOS Current | [Yes]"
                Add-Content -Path $LogFile "No BIOS update needed, stopping script"
                
                Stop-Log -LogPath $LogFile
                [Environment]::Exit
            }
            #check that the file exists
            
            $biosUpdateExists = Test-Path -Path $BIOSUpdate -ErrorAction SilentlyContinue
            
            If ($biosUpdateExists -eq $true)
            {
                Try
                {
                    Start-Process -FilePath $FlashExe -ArgumentList "/b=$BIOSUpdate", "/s", "/l=$BIOSUpdateLog"
                    
                    If ($?)
                    {
                        Add-Content -Path $LogFile "Running Bios Update | [Complete]"
                        
                        #change env variable to true to tell TS to reboot
                        $tsenv.Value("BIOSinstalled") = $true
                    }
                    Else
                    {
                        Add-Content -Path $LogFile "Running Bios Update | [Failed]"
                        
                        Stop-Log -LogPath $LogFile
                        [Environment]::Exit
                    }
                }
                Catch
                {
                    Add-Content -Path $LogFile "Starting Bios Update | [Failed]"
                }
            }
            Else
            {
                Add-Content -Path $LogFile "Bios Update Exists Check | [Failed]"
                Stop-Log -LogPath $LogFile
                
                [Environment]::Exit
            }
        }
        #endregion  
    }
    Else
    {
        Add-Content -Path $LogFile "Failed to Start Log"
        Stop-Log -LogPath $LogFile
        
        [Environment]::Exit
    }
    
    Stop-Log -LogPath $LogFile
    [Environment]::Exit
}

##*=============================================
##* END INSTALLATION
##*=============================================
##*=============================================
##* END SCRIPT BODY
##*=============================================

Post a Comment

0 Comments