PLEASE NOTE: Some of the details and features in this post have been superseded. Please review our Modern Driver Management and Modern BIOS Management solutions for up to date information.
Original Post:
If you are a Dell hardware house then this script might come in useful for you.
As you are probably aware Dell provide excellent support for SCCM deployments via their Dell Command integration software and up to date driver cab files via their Enterprise Client Deployment site at https://en.community.dell.com/techcenter/enterprise-client/w/wiki/2065.dell-command-deploy-driver-packs-for-enterprise-client-os-deployment.
When I was refreshing my driver and bios update file repository I got thinking wouldn’t it be nice if I could just run a script that would download these update files based on the models of Dell client systems listed in my SCCM device collections?.
I found a script on Dustin Hedges blog (https://deploymentramblings.wordpress.com/2014/04/17/downloading-dell-driver-cab-files-automagically-with-the-driver-pack-catalog/) but I wanted to automate this further.
So here is my resulting effort. The below scripts requires you to specify your driver file share and your SCCM site server name as a variable, it then does the following;
- Queries SCCM for a full list of Dell enterprise client products (Optiplex & Latitude)
- Downloads BIOS updates for each model
- Downloads the driver CAB for each model
- Extract the driver CAB
- Import the drivers in the extracted CAB folder
- Create a Category based on the machine model
- Create a Driver Package based on the machine model and filename
- Imports the associated drivers into the newly created Driver Package
- Creates a BIOS Update Package based on machine model
- Creates a BIOS update deployment PowerShell script for each model using the latest BIOS update and silent switches
Progress bars have also been added for both the system model and driver import stage.
The downloads are stored within sub-folders within the share you specified, e.g;
\\MySCCMServer\Drivers\Dell Optiplex 7040\BIOS
\\MySCCMServer\Drivers\Dell Optiplex 7040\Driver Cabs\
Automatically created SCCM Driver Packages:
SCCM Driver Package Contents;
Multi-Threaded Script
To run the script use the following syntax;
.\DellDownloads.ps1 -SiteServer YOURSITESERVER -RepositoryPath “\\YOURSERVER\DRIVERREPO\” -PackagePath “\\YOURSERVER\DRIVERPACKPATH”
In the latest release BIOS packaging is included, it also generates a PowerShell script for use at deployment time which is contained within the BIOS folder of the model and uses the latest BIOS exe with silent switches for a silent upgrade.
Task Sequence BIOS Update ScriptNote: If you are using a BIOS setup password (which you should be), you will need to specify this within the script (unless you want me to update the script to look for this run running it from the shell).
Modify the following line – $BIOSSwitches = ” -noreboot -nopause /p=%YOURBIOSPASSWORD “
UPDATE LOG
08/11/2016
The script has been updated with the following functionality;
- Creates BIOS packages for each model downloaded
- Creates a deployment PowerShell script containing the latest BIOS exe name and switches for a silent / no reboot update of the BIOS
- If the script is re-run it will automatically update the BIOS exe to use in the deployment PS script and update the distribution points.
28/10/2016
As a response to feedback, I have added a $MaxConcurrent jobs variable into the multi-threaded script that lets you specify the max number of jobs in order to control CPU utilization.
26/10/2016
I have added in an additional script below which is multi-threaded. This should help reduce the overall time to download, extract and create the driver packages in large environments with a wide range of models.
16/10/2016
Additional functionality has been added to now automate the process of extracting the CAB, creating computer categories, import the drivers into SCCM and create a driver pack for each of the models / driver packs downloaded.
Script Download Link – https://gallery.technet.microsoft.com/scriptcenter/SCCM-Dell-Client-Bios-ee577b04?redir=0
<# .NOTES =========================================================================== Created with: SAPIEN Technologies, Inc., PowerShell Studio 2016 v5.2.128 Created on: 16/10/2016 13:00 Created by: Maurice Daly Filename: DellDownloads.ps1 =========================================================================== .DESCRIPTION This script allows you to automate the process of keeping your Dell driver and BIOS update sources up to date. The script reads the Dell SCCM driver pack site for models you have specified and then downloads the corresponding latest driver packs and BIOS updates. Version 1.0 Retreive Dell models and download BIOS and Driver Packs Version 2.0 Added driver CAB file extract, create new driver pack, category creation and import driver functions. Version 2.1 Added multi-threading Version 2.2 Added Max Concurrent jobs setting for limiting CPU utilisation Version 2.3 Replaced Invoke-WebRequest download with BITS enabled downloads for improved performance Version 2.4 Updated code and separated functions. Added required variables via commandline Version 3.0 Creates BIOS Packages for each model and writes update powershell file for deployment with SCCM. Notes You can skip the driver package creation process by changing the $DriverPackageCreation variable to $False. The system architecture can also be changed by modifying the $Architecture variable and using x64 or x86 To re-enable error messaging for troubleshooting purpose comment out the Error and Warning Preference values below Use : This script is provided as it and I accept no responsibility for any issues arising from its use. Twitter : @modaly_it Blog : https://modalyitblog.com/ #> [CmdletBinding(SupportsShouldProcess = $true)] param (
[parameter(Mandatory = $true, HelpMessage = “Site server where the SMS Provider is installed”, Position = 1)]
[ValidateNotNullOrEmpty()] [ValidateScript({ Test-Connection -ComputerName $_ -Count 1 -Quiet })] [string]$SiteServer,
[parameter(Mandatory = $true, HelpMessage = “UNC path for downloading and extracting drivers”)]
[ValidateNotNullOrEmpty()] [ValidateScript({ Test-Path $_ })] [string]$RepositoryPath,
[parameter(Mandatory = $true, HelpMessage = “UNC path of your driver package repository”)]
[ValidateNotNullOrEmpty()] [ValidateScript({ Test-Path $_ })] [string]$PackagePath ) $ErrorActionPreference = ‘SilentlyContinue’ $WarningPreference = ‘SilentlyContinue’ # Define Maximum Number Of Simultaneously Running Jobs $MaxConcurrentJobs = 5 $ErrorActionPreference = ‘SilentlyContinue’ $WarningPreference = ‘SilentlyContinue’ # Import SCCM PowerShell Module $ModuleName = (get-item $env:SMS_ADMIN_UI_PATH).parent.FullName + “\ConfigurationManager.psd1” Import-Module $ModuleName # Query SCCM Site Code function QuerySiteCode ($SiteServer) { Write-Debug “Determining SiteCode for Site Server: ‘$($SiteServer)'” $SiteCodeObjects = Get-WmiObject -Namespace “root\SMS” -Class SMS_ProviderLocation -ComputerName $SiteServer -ErrorAction Stop foreach ($SiteCodeObject in $SiteCodeObjects) { if ($SiteCodeObject.ProviderForLocalSite -eq $true) { $SiteCode = $SiteCodeObject.SiteCode Write-Debug “SiteCode: $($SiteCode)” } } Return [string]$SiteCode } function QueryModels ($SiteCode) { # ArrayList to store the Dell models in $DellProducts = New-Object -TypeName System.Collections.ArrayList # Enumerate through all models $Models = Get-WmiObject -Namespace “root\SMS\site_$($SiteCode)” -Class SMS_G_System_COMPUTER_SYSTEM | Select-Object -Property Model | Where-Object { ($_.Model -like “*Optiplex*”) -or ($_.Model -like “*Latitude*”) } # Add model to ArrayList if not present if ($Models -ne $null) { foreach ($Model in $Models) { if ($Model.Model -notin $DellProducts) { $DellProducts.Add($Model.Model) | Out-Null } } } Return $DellProducts } function StartDownloadAndPackage ($PackagePath, $RepositoryPath, $SiteCode, $DellProducts) { $RunDownloadJob = { Param ($Model, $SiteCode, $PackagePath, $RepositoryPath) # =================== DEFINE VARIABLES ===================== # Import SCCM PowerShell Module $ModuleName = (get-item $env:SMS_ADMIN_UI_PATH).parent.FullName + “\ConfigurationManager.psd1” Import-Module $ModuleName # Directory used for driver downloads $DriverRepositoryRoot = ($RepositoryPath.Trimend(“\”) + “\Dell\”) Write-Host “Driver package path set to $DriverRepositoryRoot” # Directory used by SCCM for driver package $DriverPackageRoot = $PackagePath Write-Host “Driver package path set to $DriverPackageRoot” # Define Operating System $OperatingSystem = “Windows 10” $Architecture = “x64” # Define Dell Download Sources $DellDownloadList = “https://downloads.dell.com/published/Pages/index.html” $DellDownloadBase = “https://downloads.dell.com” $DellSCCMDriverList = “https://en.community.dell.com/techcenter/enterprise-client/w/wiki/2065.dell-command-deploy-driver-packs-for-enterprise-client-os-deployment” $DellSCCMBase = “https://en.community.dell.com” # Import Driver Packs? $DriverPackCreation = $true # =================== INITIATE DOWNLOADS =================== # ============= BIOS Upgrade Download ================== Write-Host “Getting download URL for Dell client model: $Model” $ModelLink = (Invoke-WebRequest -Uri $DellDownloadList).Links | Where-Object { $_.outerText -eq $Model } $ModelURL = (Split-Path $DellDownloadList -Parent) + “/” + ($ModelLink.href) # Correct slash direction issues $ModelURL = $ModelURL.Replace(“\”, “/”) $BIOSDownload = (Invoke-WebRequest -Uri $ModelURL -UseBasicParsing).Links | Where-Object { ($_.outerHTML -like “*BIOS*”) -and ($_.outerHTML -like “*WINDOWS*”) } | select -First 1 $BIOSFile = $BIOSDownload.href | Split-Path -Leaf # Check for destination directory, create if required and download the BIOS upgrade file if ((Test-Path -Path ($DriverRepositoryRoot + $Model + “\BIOS”)) -eq $true) { if ((Test-Path -Path ($DriverRepositoryRoot + $Model + “\BIOS\” + $BIOSFile)) -eq $false) { Write-Host -ForegroundColor Green “Downloading $($BIOSFile) BIOS update file” # Invoke-WebRequest ($DellDownloadBase + $BIOSDownload.href) -OutFile ($DriverRepositoryRoot + $Model + “\BIOS\” + $BIOSFile) -UseBasicParsing Start-BitsTransfer ($DellDownloadBase + $BIOSDownload.href) -Destination ($DriverRepositoryRoot + $Model + “\BIOS\” + $BIOSFile) } else { Write-Host -ForegroundColor Yellow “Skipping $BIOSFile… File already downloaded…” } } else { Write-Host -ForegroundColor Green “Creating $Model download folder” New-Item -Type dir -Path ($DriverRepositoryRoot + $Model + “\BIOS”) Write-Host -ForegroundColor Green “Downloading $($BIOSFile) BIOS update file” # Invoke-WebRequest ($DellDownloadBase + $BIOSDownload.href) -OutFile ($DriverRepositoryRoot + $Model + “\BIOS\” + $BIOSFile) -UseBasicParsing Start-BitsTransfer ($DellDownloadBase + $BIOSDownload.href) -Destination ($DriverRepositoryRoot + $Model + “\BIOS\” + $BIOSFile) } # ============= SCCM Driver Cab Download ================== Write-Host “Getting SCCM driver pack link for model: $Model” $ModelLink = (Invoke-WebRequest -Uri $DellSCCMDriverList -UseBasicParsing).Links | Where-Object { ($_.outerHTML -like “*$Model*”) -and ($_.outerHTML -like “*$OperatingSystem*”) } | select -First 1 $ModelURL = $DellSCCMBase + ($ModelLink.href) # Correct slash direction issues $ModelURL = $ModelURL.Replace(“\”, “/”) $SCCMDriverDownload = (Invoke-WebRequest -Uri $ModelURL -UseBasicParsing).Links | Where-Object { $_.href -like “*.cab” } $SCCMDriverCab = $SCCMDriverDownload.href | Split-Path -Leaf # Check for destination directory, create if required and download the driver cab if ((Test-Path -Path ($DriverRepositoryRoot + $Model + “\Driver Cab\”)) -eq $true) { if ((Test-Path -Path ($DriverRepositoryRoot + $Model + “\Driver Cab\” + $SCCMDriverCab)) -eq $false) { Write-Host -ForegroundColor Green “Downloading $($SCCMDriverCab) driver cab file” # Invoke-WebRequest ($SCCMDriverDownload.href) -OutFile ($DriverRepositoryRoot + $Model + “\Driver Cab\” + $SCCMDriverCab) -UseBasicParsing Start-BitsTransfer -Source ($SCCMDriverDownload.href) -Destination ($DriverRepositoryRoot + $Model + “\Driver Cab\” + $SCCMDriverCab) $SkipDriver = $false } else { Write-Host -ForegroundColor Yellow “Skipping $SCCMDriverCab… File already downloaded…” $SkipDriver = $true } } else { Write-Host -ForegroundColor Green “Creating $Model download folder” New-Item -Type dir -Path ($DriverRepositoryRoot + $Model + “\Driver Cab”) Write-Host -ForegroundColor Green “Downloading $($SCCMDriverCab) driver cab file” #Invoke-WebRequest ($SCCMDriverDownload.href) -OutFile ($DriverRepositoryRoot + $Model + “\Driver Cab\” + $SCCMDriverCab) Start-BitsTransfer -Source ($SCCMDriverDownload.href) -Destination ($DriverRepositoryRoot + $Model + “\Driver Cab\” + $SCCMDriverCab) } # =================== CREATE BIOS UPDATE PACKAGE =========================== $BIOSUpdatePackage = (“Dell” + ” ” + $Model + ” ” + “BIOS UPDATE”) $BIOSUpdateRoot = ($DriverRepositoryRoot + $Model + “\BIOS\”) Set-Location -Path ($SiteCode + “:”) if ((Get-CMPackage -name $BIOSUpdatePackage) -eq $null) { Write-Host -ForegroundColor Green “Creating BIOS Package” New-CMPackage -Name “$BIOSUpdatePackage” -Path $BIOSUpdateRoot -Description “Dell $Model BIOS Updates” -Manufacturer “Dell” -Language English } Set-Location -Path $env:SystemDrive $BIOSUpdateScript = ($BIOSUpdateRoot + “BIOSUpdate.ps1”) $CurrentBIOSFile = Get-ChildItem -Path $BIOSUpdateRoot -Filter *.exe -Recurse | Sort-Object $_.LastWriteTime | select -First 1 if ((Test-Path -Path $BIOSUpdateScript) -eq $False) { # Create BIOSUpdate.ps1 Deployment Script New-Item -Path ($BIOSUpdateRoot + “BIOSUpdate.ps1″) -ItemType File $BIOSSwitches = ” -noreboot -nopause ” Add-Content -Path $BIOSUpdateScript (‘$CurrentBIOSFile=’ + ‘”‘ + $($CurrentBIOSFile.name) + ‘”‘) Add-Content -Path $BIOSUpdateScript (‘$BIOSSwitches=’ + ‘”‘ + $($BIOSSwitches) + ‘”‘) Add-Content -Path $BIOSUpdateScript (‘Start-Process $CurrentBIOSFile -ArgumentList $BIOSSwitches’) } else { # Check if older BIOS update exists and update BIOSUpdate deployment script $BIOSFileCount = (Get-ChildItem -Path $BIOSUpdateRoot -Filter *.exe -Recurse).count if ($BIOSFileCount -gt 1) { $OldBIOSFiles = Get-ChildItem -Path $BIOSUpdateRoot -Filter *.exe -Recurse | Where-Object { $_.Name -ne $CurrentBIOSFile.name } foreach ($OldBIOS in $OldBIOSFiles) { (Get-Content -Path $BIOSUpdateScript) -replace $OldBIOS.name, $CurrentBIOSFile.name | Set-Content -Path $BIOSUpdateScript } } } # Refresh Distribution Points Get-CMPackage -name $BIOSUpdatePackage | Update-CMDistributionPoint } # =================== CREATE DRIVER PACKAGE AND IMPORT DRIVERS =================== $DriverSourceCab = ($DriverRepositoryRoot + $Model + “\Driver Cab\” + $SCCMDriverCab) $DriverExtractDest = ($DriverRepositoryRoot + $Model + “\Extracted Drivers”) $DriverPackageDir = ($DriverSourceCab | Split-Path -Leaf) $DriverPackageDir = $DriverPackageDir.Substring(0, $DriverPackageDir.length – 4) $DriverCabDest = $DriverPackageRoot + “\Dell\” + $DriverPackageDir if ($DriverPackCreation -eq $true) { if ((Test-Path -Path $DriverExtractDest) -eq $false) { New-Item -Type dir -Path $DriverExtractDest } else { Get-ChildItem -Path $DriverExtractDest -Recurse | Remove-Item -Recurse -Force } New-Item -Type dir -Path $DriverCabDest Set-Location -Path ($SiteCode + “:”) $CMDDriverPackage = “Dell ” + $Model + ” ” + “(” + $DriverPackageDir + “)” + ” ” + $Architecture if (Get-CMDriverPackage -Name $CMDDriverPackage) { Write-Host -ForegroundColor Yellow “Skipping.. Driver package already exists..” } else { Write-Host -ForegroundColor Green “Creating driver package” Set-Location -Path $env:SystemDrive Expand “$DriverSourceCab” -F:* “$DriverExtractDest” $DriverINFFiles = Get-ChildItem -Path $DriverExtractDest -Recurse -Filter “*.inf” | Where-Object { $_.FullName -like “*$Architecture*” } Set-Location -Path ($SiteCode + “:”) # Get-Location | Out-File -FilePath C:\Location2.txt New-CMDriverPackage -Name $CMDDriverPackage -path ($DriverPackageRoot + “\Dell\” + $DriverPackageDir + “\” + $OperatingSystem + “\” + $Architecture) if (Get-CMCategory -CategoryType DriverCategories -name (“Dell ” + $Model)) { Write-Host -ForegroundColor Yellow “Category already exists” $DriverCategory = Get-CMCategory -CategoryType DriverCategories -name (“Dell ” + $Model) } else { Write-Host -ForegroundColor Green “Creating category” $DriverCategory = New-CMCategory -CategoryType DriverCategories -name (“Dell ” + $Model) } $DriverPackage = Get-CMDriverPackage -Name $CMDDriverPackage foreach ($DriverINF in $DriverINFFiles) { $DriverInfo = Import-CMDriver -UncFileLocation ($DriverINF.FullName) -ImportDuplicateDriverOption AppendCategory -EnableAndAllowInstall $True -AdministrativeCategory $DriverCategory | Select-Object * Add-CMDriverToDriverPackage -DriverID $DriverInfo.CI_ID -DriverPackageName $CMDDriverPackage } } Set-Location -Path $env:SystemDrive } $TotalModelCount = $DellProducts.Count $RemainingModels = $TotalModelCount foreach ($Model in $DellProducts) { write-progress -activity “Initiate Driver Download &amp;amp; Driver Package Jobs” -status “Progress:” -percentcomplete (($TotalModelCount – $RemainingModels)/$TotalModelCount * 100) $RemainingModels– $Check = $false while ($Check -eq $false) { if ((Get-Job -State ‘Running’).Count -lt $MaxConcurrentJobs) { Start-Job -ScriptBlock $RunDownloadJob -ArgumentList $Model, $SiteCode, $PackagePath, $RepositoryPath -Name ($Model + ” Download”) $Check = $true } } } Get-Job | Wait-Job | Receive-Job Get-Job | Remove-Job } # Get SCCM Site Code $SiteCode = QuerySiteCode ($SiteServer) Write-Debug $PackagePath Write-Debug $RepositoryPath if ($SiteCode -ne $null) { # Query Dell Products in SCCM using QueryModels function $DellProducts = QueryModels ($SiteCode) # Output the members of the ArrayList if ($DellProducts.Count -ge 1) { foreach ($ModelItem in $DellProducts) { $PSObject = [PSCustomObject]@{ “Dell Models Found” = $ModelItem } Write-Output $PSObject Write-Debug $PSObject } } # Start download, extract, import and package process Write-Host -ForegroundColor Green “Starting download, extract, import and driver package build process..” StartDownloadAndPackage ($PackagePath) ($RepositoryPath) ($SiteCode) ($DellProducts) }
[…] SCCM Dell Client Bios & Driver Autodownload PowerShell Script […]
Hi Devin,
An updated script is now on the site and the Microsoft Script Center (https://gallery.technet.microsoft.com/scriptcenter/SCCM-Dell-Client-Bios-ee577b04) that should sort out that issue for you.
Maurice
The link you sent me from https://gallery.technet.microsoft.com/scriptcenter/SCCM-Dell-Client-Bios-ee577b04 has run successfully. Thanks for the help!
Good to hear Devin, glad it helped you out 🙂
Just a quick update for everyone following this: Maurice has been a huge help today and helped me via Teamviewer. I started the script in PowerShell ISE instead of the normal PowerShell console. That was the problem, now everything is working. 🙂
Hi Kevin,
Thanks for the feedback by email. I have added in the requested Max Concurrent jobs variable so you can now limit your CPU utilization during the download / package phase.
It’s not working, I just copied it again. I also get this in line 49:
+ Begin
+ ~~~~~
+ CategoryInfo : ObjectNotFound: (Begin:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
I’ve just sent you an email..
Hi Maurice,
yes I’m running the script on the site server. I’m using the multi-threaded script.
Thanks!
Best regards
Kevin
Hi Kevin,
OK so stepping through this one step at a time, if you run lines 38-97 do you get any returned values for the $DellProducts or $Models arrays?.
Maurice
Actually Kevin it looks like that there might have been an issue pasting my script into WordPress. I have just pasted in the script again and run it with no issue. Can you either copy it off of the site or I can email you a copy.
Hi there, first of all thanks for this script. I can’t get it to work, here is what happens. I edit all the variables (SCCM Server, Path to the ConfigurationManager.psd1 because our SCCM is setup on the D: drive and source share path.)
When I run the script, it gives me the following output and nothing else happens:
# Determine SiteCode from WMI
try
{
Write-Verbose “Determining SiteCode for Site Server: ‘$($SiteServer)'”
$SiteCodeObjects = Get-WmiObject -Namespace “rootSMS” -Class SMS_ProviderLocation -ComputerName $SiteServer -ErrorAction Stop
foreach ($SiteCodeObject in $SiteCodeObjects)
{
if ($SiteCodeObject.ProviderForLocalSite -eq $true)
{
$SiteCode = $SiteCodeObject.SiteCode
Write-Debug “SiteCode: $($SiteCode)”
}
}
}
catch [Exception] {
Throw “Unable to determine SiteCode”
}
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
——- —— —– —– —– —— — ———–
439 36 146676 132868 1597 86,28 4328 AFD2DMonitor
323 14 8896 14932 68 7,80 5644 AgPkiMon
2345 31 28252 32480 547 1,52 1256 AIUpdateSVC
….
….
# ArrayList to store the Dell models in
$DellProducts = New-Object -TypeName System.Collections.ArrayList
# Enumerate through all models
$Models = Get-WmiObject -Namespace “rootSMSsite_$($SiteCode)” -Class SMS_G_System_COMPUTER_SYSTEM | Select-Object -Property Model | Where-Object { ($_.Model -like
“*Optiplex*”) -or ($_.Model -like “*Latitude*”) }
# Add model to ArrayList if not present
if ($Models -ne $null)
{
foreach ($Model in $Models)
{
if ($Model.Model -notin $DellProducts)
{
$DellProducts.Add($Model.Model) | Out-Null
}
}
}
# Output the members of the ArrayList
if ($DellProducts.Count -ge 1)
{
foreach ($ModelItem in $DellProducts)
{
$PSObject = [PSCustomObject]@{
Model = $ModelItem
}
Write-Output $PSObject
}
}
PS C:Windowssystem32>
I also tried to set the site code manually because apparently the script can’t find it correctly but it doesn’t help either.
Best regards
Kevin
Hi Kevin,
Just checking first of all that you are running the script on your Site Server?. Can you also let me know if you are running the single or multi-threaded script.
Regards
Maurice