Dell BIOS Download and Update CM Package

This Script connects to your ConfigMgr then compares the list of BIOS Updates to what Dell has available. Note, there are some other great tools like the Driver Automation Tool, but those didn’t fit my needs and I wanted to fully automate this with scheduled tasks.  We also had issues using the tool in our network due to our proxy servers, so I wrote my own.

This tool will download the Files, and Update the Package Info (Does NOT create the CM Packages)

Build your list of Dell Models, Let it know the Package Number, then the script will do the rest.  I also have this script send me an email, you’ll need to update it to fit your needs.

Your List of Dell Models & the associated Package ID:
image

From the image below, you can see that it will fill out the Version, and Description.
I have it use the Language Field for the KB ID / Release ID information. (Note, I do the same thing for Dell Driver Packs… soon to be blogged, it’s like 80% the same code)

image

My plan is to use this on weekly scheduled task, that will run, download the updated BIOS, update the source content, update the package information, trigger distribution update, then sent alert (email) to let me know what’s up.

This code would also allow you to make some small adjustments to allow you to download the BIOS needed for the machine you’re running it from, then apply it.  That Blog Post is here: https://garytown.com/dell-bios-update-winpe-model-independent-from-internet but I need to go back and fix it, as Dell has changed the CAB file downloads since I wrote that.

How we use it.. We automatically download and Update PILOT packages, we then test in a PILOT Task Sequence.   Once we’ve tested to our content, we promote the version to Production.  Future Blog Post.

Applying the BIOS / Drivers are also completely dynamic and automated using Task Sequence Variables, you can get a sneak peak of that on this POST: https://garytown.com/waas-post-1-precache-compat-scan-ts.  Look for Dynamic Drivers section.  We do the same process for BIOS.  Perhaps going through that process is a future post to go into depth.

Running Script:
image

Script downloads the SCUP Cab File 8MB, then extracts it 168MB, then mounts it to $XML
image

image

Then starts comparing what you have in CM to what Dell has available, if it finds one, it uses BITS to download it.  I’m using BITS because we have some crappy Proxy Servers that tend to drop my downloads at random times, BITS allows me to keep retrying and often picks up where it left off.  This isn’t a big deal for BIOS downloads, but it is for the Driver Cab downloads.

Folder Structure: Creates Archive Folder to keep BIOS Updates FOREVER.. and Current Folder which is the most updated version, and what the CM Package Points to.  PowerShell is just where I keep the scripts.
image
image

Email – I’m using GMAIL in my lab as my relay service, if you wish to do the same, you’ll need to make a change to make it less secure.  I’d recommend a completely separate email account with crazy difficult password.  More info: https://support.google.com/accounts/answer/6010255?hl=en

When Updates Needed:
image

When No Updates Needed:
image

Running in UpgradeCMPackageOnly Mode:
This mode will skip updating the source files, and only update information in CM.
This is useful if you want to update the the Description fields, etc.  NOTE, it will update fields to the LATEST Version from Dell, even if you didn’t update the source.
image

Script:

<# 
Downloads BIOS Updates for Specific Models. - https://garytown.com/dell-bios-download-and-update-cm-package
Downloads the Dell SCUP Catalog Cab File, Extracts XML, Loads XML, finds BIOS downloads for corrisponding Models, downloads them if update is available (compared to the CM Package), then updates the CM Package
$RunMethod Options
    Report, will show current status of BIOS Updates
    Download, will Download BIOS Updates & Update CM Package
    DownloadOnly, will only Download the Updates, not updating the CM Package
    UpdateCMPacakageOnly, Be careful of this one, it will assume all of the BIOS are downloaded and in the correct places, then update CM Pacakge with this information.
        This is useful if you decide you want to change a field in the Package, like Manufacture from Dell to Dell Inc, it will grab the latest info from DELL, assume it's downloaded and in the correct place, then update the CM Packages with the updated info.

To add / Remove Dell Models, Adjust the Dell Model Table

#> 
[CmdletBinding()]
    Param (
		    [Parameter(Mandatory=$true,Position=1,HelpMessage="Application")]
            [ValidateNotNullOrEmpty()]
            [ValidateSet("Report", "Download", "DownloadOnly", "UpdateCMPackageOnly")]
		    $RunMethod = "Report"
 	    )


#Settings for Google - Rest of Info @ Bottom
$SMTPServer = "smtp.gmail.com"
$SMTPPort = "587"
$Username = "youremail@gmail.com"
$Password = "yourgmailpassword"

#Script Vars
$LogFile = "$PSScriptRoot\DellBIOSDownload.log"
$SiteCode = "ps2"
$EmailArray = @()
$scriptName = $MyInvocation.MyCommand.Name
$CabPath = "$PSScriptRoot\DriverBIOSCatalog.cab"
$FileServerName = "SRC"

#region: CMTraceLog Function formats logging in CMTrace style
        function CMTraceLog {
         [CmdletBinding()]
    Param (
		    [Parameter(Mandatory=$false)]
		    $Message,
 
		    [Parameter(Mandatory=$false)]
		    $ErrorMessage,
 
		    [Parameter(Mandatory=$false)]
		    $Component = "Dell BIOS Downloader",
 
		    [Parameter(Mandatory=$false)]
		    [int]$Type,
		
		    [Parameter(Mandatory=$true)]
		    $LogFile
	    )
    <#
    Type: 1 = Normal, 2 = Warning (yellow), 3 = Error (red)
    #>
	    $Time = Get-Date -Format "HH:mm:ss.ffffff"
	    $Date = Get-Date -Format "MM-dd-yyyy"
 
	    if ($ErrorMessage -ne $null) {$Type = 3}
	    if ($Component -eq $null) {$Component = " "}
	    if ($Type -eq $null) {$Type = 1}
 
	    $LogMessage = "<![LOG[$Message $ErrorMessage" + "]LOG]!><time=`"$Time`" date=`"$Date`" component=`"$Component`" context=`"`" type=`"$Type`" thread=`"`" file=`"`">"
	    $LogMessage | Out-File -Append -Encoding UTF8 -FilePath $LogFile
    }

function Get-FolderSize {
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true,ValueFromPipeline=$true)]
$Path,
[ValidateSet("KB","MB","GB")]
$Units = "MB"
)
  if ( (Test-Path $Path) -and (Get-Item $Path).PSIsContainer ) {
    $Measure = Get-ChildItem $Path -Recurse -Force -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum
    $Sum = $Measure.Sum / "1$Units"
    [PSCustomObject]@{
      "Path" = $Path
      "Size($Units)" = $Sum
    }
  }
}



$DellModelTable = @(

        @{ Model = "Latitude 5290"; PackageID = "PS200055"}
        @{ Model = "Latitude 5490"; PackageID = "PS200056"}
        @{ Model = "Latitude 5580"; PackageID = "PS200057"}
        @{ Model = "Latitude 5590"; PackageID = "PS200058"}
        @{ Model = "Latitude 7275"; PackageID = "PS200059"}
        @{ Model = "Latitude 7280"; PackageID = "PS20005A"}
        @{ Model = "Latitude 7480"; PackageID = "PS20005B"}
        @{ Model = "Latitude E5570"; PackageID = "PS20005C"}
        @{ Model = "Latitude E7250"; PackageID = "PS20005D"}
        @{ Model = "Latitude E7270"; PackageID = "PS20005E"}
        @{ Model = "Latitude E7450"; PackageID = "PS20005F"}
        @{ Model = "Latitude E7470"; PackageID = "PS200060"}
        @{ Model = "OptiPlex 3040"; PackageID = "PS200061"}
        @{ Model = "Optiplex 5050"; PackageID = "PS200062"}
        @{ Model = "OptiPlex 5060"; PackageID = "PS200063"}
        @{ Model = "Optiplex 7040"; PackageID = "PS200064"}
        @{ Model = "Optiplex 7050"; PackageID = "PS200065"}
        @{ Model = "Venue 11 Pro 7140"; PackageID = "PS200054"}
        )

Set-Location -Path "C:"



CMTraceLog -Message "Starting Script: $scriptName" -Type 1 -LogFile $LogFile
Write-Output "Starting Script: $scriptName"

#Check if Running with Access to Source Server (File Server)
try {Test-Connection -ComputerName $FileServerName -Quiet -Count 1 
    $DownloadBIOSRootArchive = "\\$FileServerName\src$\OSD\Firmware\DellArchive"
    $DownloadBIOSRootCurrent = "\\$FileServerName\src$\OSD\Firmware\DellCurrent"
    $DownloadToServer = $true
    Write-Output "Connected to Server $FileServerName"
    }
Catch 
    {
    Write-Output "Not Connected to File Server, Exiting"
    $DownloadToServer = $false
    }

#If you use a Proxy... configure this secion... if not... make sure you don't have something with 192.168.1.15
if ((Test-NetConnection proxy.garytown.com -Port 8080).NameResolutionSucceeded -eq $true) 
    {
    $UseProxy = $true
    $ProxyServer = "http://garytown.com:8080" 
    $BitsProxyList = @("192.168.82.11:8080, 127.212.111.2:8080, 192.123.133.222:8080")
    CMTraceLog -Message "Found Proxy Server, using for Downloads" -Type 1 -LogFile $LogFile
    Write-Output "Found Proxy Server, using for Downloads"
    }
Else 
    {
    $UseProxy = $False
    $ProxyServer = $null
    $BitsProxyList = $null
    CMTraceLog -Message "No Proxy Server Found, continuing without" -Type 1 -LogFile $LogFile
    Write-Output "No Proxy Server Found, continuing without"
    }

if ($DownloadToServer -eq $true)
    {
    # Generic Script Info for Email
    $EmailArray += "<font color=Black>Script was run in Mode: $RunMethod</font><br>"
    $EmailArray += "<font color=Black>Connecting to File Share Server: $FileServerName</font><br>"

    # Download Dell Cab Catalog File
    Write-Output "Starting Download of Dell BIOS Catalog"
    CMTraceLog -Message "Starting Download of Dell BIOS Catalog" -Type 1 -LogFile $LogFile
    Invoke-WebRequest -Uri "ftp://ftp.dell.com/catalog/DellSDPCatalogPC.cab" -OutFile $CabPath -UseBasicParsing -Verbose -Proxy $ProxyServer
    [int32]$n=1
    While(!(Test-Path $CabPath) -and $n -lt '3')
        {
        Invoke-WebRequest -Uri "ftp://ftp.dell.com/catalog/DellSDPCatalogPC.cab" -OutFile $CabPath -UseBasicParsing -Verbose -Proxy $ProxyServer
        $n++
        }



    # Extract XML from Cab File
    Write-Output "Starting Expand of Dell Catalog"
    CMTraceLog -Message "Starting Expand of Dell Catalog" -Type 1 -LogFile $LogFile
    If(Test-Path "$PSScriptRoot\DellSDPCatalogPC.xml"){Remove-Item -Path "$PSScriptRoot\DellSDPCatalogPC.xml" -Force -Verbose}
    Start-Sleep -Seconds 1
    $Expand = expand $CabPath -F:DellSDPCatalogPC.xml $PSScriptRoot
    #expand $CabPath -F* $PSScriptRoot
    Write-Output "Successfully Extracted DriverPackCatalog.xml to $PSScriptRoot"
    Start-Sleep -Seconds 1 #Wait for extract to finish

    Write-Output "Loading XML, this can take awhile... a few minutes even"
    [xml]$XML = Get-Content "$PSScriptRoot\DellSDPCatalogPC.xml" -Verbose

    if  (Test-Path 'C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin'){Import-Module 'C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1'}

    # Create Array of Downloads
    $Downloads = $xml.SystemsManagementCatalog.SoftwareDistributionPackage
    ForEach ($DellModel in $DellModelTable)
        {
        CMTraceLog -Message  "---Starting to Process Model: $($DellModel.Model)---" -Type 2 -LogFile $LogFile
        #Create Download Link For Model
        #This is where you might need to change some filters to fit your need, like I needed to exclude (-notmatch) 2 in 1 devices.
        $DellModelNumber = $DellModel.Model.Split( )[-1]
        $Target = $Downloads | Where-Object -FilterScript {$PSitem.LocalizedProperties.Title -match $DellModelNumber}
        $Target = $Target | Where-Object -FilterScript {$PSItem.InstallableItem.OriginFile.FileName -notmatch "2-IN-1"}
        $Target = $Target | Where-Object -FilterScript {$PSItem.InstallableItem.OriginFile.FileName -notmatch "2In1"}
        $Target = $Target | Where-Object -FilterScript {$PSItem.InstallableItem.OriginFile.FileName -notmatch "AIO"}
        $Target = $Target | Where-Object -FilterScript {$PSItem.InstallableItem.OriginFile.FileName -notmatch "9020M"}
        $Target = $Target | Where-Object -FilterScript {$PSItem.InstallableItem.OriginFile.FileName -notmatch "Video"}
        $Target = $Target | Where-Object -FilterScript {$PSItem.InstallableItem.OriginFile.FileName -notmatch "DRVR"}
        $Target = $Target | Where-Object -FilterScript {$PSItem.InstallableItem.OriginFile.FileName -notmatch "Wyse"}
        if ($Target.count -gt "1"){$Target = $Target[0]}
        $TargetVersion = $target.LocalizedProperties.Title.Split(",")[-1]
        $Target = $Target | Where-Object -FilterScript {$PSitem.LocalizedProperties.Title -match $TargetVersion}
        $TargetPath = "$($DownloadBIOSRootArchive)\$($DellModel.Model)\$($TargetVersion)"
        $TargetKBID = $Target.UpdateSpecificData.KBArticleID
        $TargetLink = $Target.InstallableItem.OriginFile.OriginUri
        $TargetFileName = $Target.InstallableItem.OriginFile.FileName
        $TargetFilePathName = "$TargetPath\$TargetFileName"
        $TargetDate = Get-Date $Target.Properties.CreationDate -Format 'yyyy-MM-dd'
        if (-NOT(Test-Path $TargetPath))
            {
            CMTraceLog -Message  "Creating Directory $TargetPath" -Type 1 -LogFile $LogFile
            Write-Host "Creating Directory $TargetPath"
            New-Item -Path $TargetPath -ItemType Directory
            }

        #Get Current Driver CMPackage Version from CM
        if ($DownloadToServer -eq $true)
            {
            Set-Location -Path "$($SiteCode):"
            $PackageInfo = Get-CMPackage -Id $DellModel.PackageID -Fast
            $PackageInfoVersion = $PackageInfo.Version
            Set-Location -Path "C:"
            }
        Else
            {
            $PackageInfoVersion = $null
            }
        #Do the Download
        
        #if (Test-Path -Path "$($TargetPath)\$($TargetFileName)") {$TestDownload = Get-childitem -Path "$($TargetPath)\$($TargetFileName)" | Select-Object -ExpandProperty Name -ErrorAction SilentlyContinue}
        if ($PackageInfoVersion -eq $TargetVersion)
            {
            Write-host "CM Package $($PackageInfo.Name) already Current: $PackageInfoVersion Dell: $TargetVersion" -ForegroundColor Green
            CMTraceLog -Message "CM Package $($PackageInfo.Name) already Current: $PackageInfoVersion Dell: $TargetVersion" -Type 1 -LogFile $LogFile
            $EmailArray += "<font color=Green>CM Package $($PackageInfo.Name) already Current: $PackageInfoVersion Dell: $TargetVersion</font><br>"
            }
        Else
            {
            Write-host "Updated BIOS for $($DellModel.Model) available: $TargetFileName" -ForegroundColor Yellow
            CMTraceLog -Message  "Updated BIOS for $($DellModel.Model) available: $TargetFileName" -Type 1 -LogFile $LogFile
            $EmailArray += "<font color=#8B0000>Updated BIOS for $($DellModel.Model) available: $TargetFileName</font><br>"
            Write-host "Need to Update CM Package $($PackageInfo.Name) from $($PackageInfoVersion) to $TargetVersion" -ForegroundColor Yellow
            CMTraceLog -Message "Need to Update CM Package $($PackageInfo.Name) from $($PackageInfoVersion) to $TargetVersion" -Type 1 -LogFile $LogFile

            if ($RunMethod -eq "Download" -or $RunMethod -eq "DownloadOnly")   
                {
                #BITS Download of Driver Pack with Retry built in (Do loop)
                CMTraceLog -Message  "Starting Download of $($DellModel.Model) BIOS File: $TargetFileName" -Type 1 -LogFile $LogFile
                Write-Output "Starting Download of $($DellModel.Model) BIOS File: $TargetFileName" 
                $BitStartTime = Get-Date
                Import-Module BitsTransfer
                $DownloadAttempts = 0
                if ($UseProxy -eq $true) 
                    {Start-BitsTransfer -Source $TargetLink -Destination $TargetFilePathName -ProxyUsage Override -ProxyList $BitsProxyList -DisplayName $TargetFileName -Asynchronous}
                else 
                    {Start-BitsTransfer -Source $TargetLink -Destination $TargetFilePathName -DisplayName $TargetFileName -Asynchronous}
                do
                    {
                    $DownloadAttempts++
                    Get-BitsTransfer -Name $TargetFileName | Resume-BitsTransfer
                    }
                while
                    ((test-path "$TargetFilePathName") -ne $true)

                #Invoke-WebRequest -Uri $TargetLink -OutFile $TargetFilePathName -UseBasicParsing -Verbose -Proxy $ProxyServer -ErrorAction Stop
                $DownloadTime = [math]::Round($((Get-Date).Subtract($BitStartTime).TotalSeconds))
                CMTraceLog -Message  "Download Complete: $TargetFilePathName" -Type 1 -LogFile $LogFile
                Write-Output "Download Complete: $TargetFilePathName"
                CMTraceLog -Message  "Took $DownloadTime Seconds to Download $TargetFileName with $DownloadAttempts Attempt(s)" -Type 1 -LogFile $LogFile
                Write-Output "Took $DownloadTime Seconds to Download $TargetFileName with $DownloadAttempts Attempt(s)"
                if (Test-Path "$($DownloadBIOSRootCurrent)\$($DellModel.Model)"){Remove-Item -Path "$($DownloadBIOSRootCurrent)\$($DellModel.Model)" -Recurse -Force }
                New-Item "$($DownloadBIOSRootCurrent)\$($DellModel.Model)" -ItemType Directory
                Copy-Item $TargetFilePathName -Destination "$($DownloadBIOSRootCurrent)\$($DellModel.Model)"
                }
                    #Update Package to Point to new Source & Trigger Distribution
            }
        if ($RunMethod -eq "Download" -or $RunMethod -eq "UpdateCMPackageOnly")
            {
            write-output "Updating Package Info in ConfigMgr $($PackageInfo.Name) ID: $($DellModel.PackageID)"
            Import-Module 'C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1'
            Set-Location -Path "$($SiteCode):"         
            Set-CMPackage -Id $DellModel.PackageID -path "$($DownloadBIOSRootCurrent)\$($DellModel.Model)"
            Set-CMPackage -Id $DellModel.PackageID -Version $TargetVersion
            #Set-CMPackage -Id $DellModel.PackageID -DriverPkgVersion $Target.dellVersion
            #Set-CMPackage -Id $DellModel.PackageID -DriverModel $DellModel.Model
            #Set-CMPackage -Id $DellModel.PackageID -DriverManufacturer "Dell"
            Set-CMPackage -Id $DellModel.PackageID -Description "Version $TargetVersion Released $TargetDate.  Archive Folder = $($DownloadBIOSRootArchive)\$($DellModel.Model)"
            Set-CMPackage -Id $DellModel.PackageID -Language $TargetKBID
            Set-CMPackage -ID $DellModel.PackageID -Manufacturer "Dell"
            $PackageInfo = Get-CMPackage -Id $DellModel.PackageID -Fast
            $EmailArray += "<font color=#8B0000>Updated Package $($PackageInfo.Name), ID $($DellModel.PackageID) Information: </font><br>"
            $EmailArray += "<font color=#8B0000>&emsp;Version to $($PackageInfoVersion) </font><br>"
            $EmailArray += "<font color=#8B0000>&emsp;Language to: $TargetKBID</font><br>"
            $EmailArray += "<font color=#8B0000>&emsp;Desciption to: Version $TargetVersion Released $TargetDate.  Archive Folder = $($DownloadBIOSRootArchive)\$($DellModel.Model)</font><br>"
            Update-CMDistributionPoint -PackageId $DellModel.PackageID
            Set-Location -Path "C:"
            CMTraceLog -Message "Updated Package $($PackageInfo.Name), ID $($DellModel.PackageID) to $($PackageInfoVersion) which was released $($TargetDate)"  -Type 1 -LogFile $LogFile
            }   
        }
    }

#Settings for Email
#Customize this Part for your Subject & Body.  Keep it short if you plan to use SMS texts.
$to = "Your@Email.COM"
$subject = "Dell BIOS Updates"
$body = "$EmailArray" 
$message = New-Object System.Net.Mail.MailMessage
$message.subject = $subject
$message.body = $body
$message.IsBodyHTML = $true
$message.to.add($to)
$message.from = $username
        
$smtp = New-Object System.Net.Mail.SmtpClient($SMTPServer, $SMTPPort);
$smtp.EnableSSL = $true
$smtp.Credentials = New-Object System.Net.NetworkCredential($Username, $Password);
$smtp.send($message)
write-host "Mail Sent"


CMTraceLog -Message "Finished Script: $scriptName" -Type 1 -LogFile $LogFile
Write-Output "Finished Script: $scriptName"



Posted on GARYTOWN.COM

2 thoughts on “Dell BIOS Download and Update CM Package”

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.