Recently, the Install Dell Command Update and Flash BIOS in WinPE solution I published stopped working when we purchased the Dell E7280. The Dell Command | Update was installing a driver in the WinPE environment that would stick in a continuous installation. Before, it was an all or nothing thing on using the DCU-CLI.exe within WinPE. It could not accept any parameters. I finally figured that problem out. I injected MSI.dll into the %WINDIR%\system32 folder and full functionality of the executable was enabled.
I decided to rework the entire script at that point. The script now operates in both WinPE and the Windows environment. You may ask why you would want to execute this in WinPE. The reason is that if there is a BIOS update needed and you also configure BIOS settings before the OS is laid down, you will want this to be performed first as some BIOS settings cannot be changed after the OS is laid down.
The script detects if it is running in WinPE and will only execute a BIOS update at that point. The way the script works is that it executes the DCU-CLI.exe and uses the /report parameter to only generate a report on what to install. Here is a sample report:
The script then reads the XML report file as shown above to find what to install. In WinPE, it automatically knows to only run the BIOS update. In Windows, it will install all updates if no parameter is defined. You can see in the script that I have defined BIOS, Drivers, and Applications meaning if you select any of these, that is all that will be installed. If you don't select any, all will be installed.
Inside the XML file, there is the <file> field that gives the download address for downloading the driver. After talking with Warren Byle from Dell, I verified that types of updates to include in this script. Warren also verified the release code is unique for every new version of a driver. This gave me the idea to create a local driver repository so they can be downloaded and then executed locally. This saves a lot on time and bandwidth, especially when talking about hundreds of megabytes of data downloads.
The script downloads every driver listed in the XML file to the specified network share outlined in the WindowsRepository parameter. It will first scan the repository folders for one named after the release code and verify the contents matches the file download. If so, it skips downloading.
The next step is installing the drivers. I found that /S works on all Dell driver installations. The only part I had to figure out were the return codes, which are 0, 2, and 3010 for a successful installation.
Another thing I changed was the process of setting this up in MDT. Here are the steps I put in the task sequence processes for running this within WinPE.
I chose to use T: for my mapped drive since I know that driver letter is not used for anything else here. The Map T: Drive task is as shown.
The next step was copying the Dell Client | Update files to WinPE.
These files I copy over were grabbed from the Dell Command | Update directory after it had been installed on a PC.
The next thing I do is to copy the MSI.DLL file to the WinPE system32 directory. I grabbed this file initially from the system32 directory on my Win10 machine.
The next step is unmapping the T: drive. This is necessary because we are going to map that drive letter to the next UNC path.
The next step is to map to the repository location, where the PowerShell script also resides.
We're now going to copy over the PowerShell script. You may wonder why I chose here. That is because this script is intended to be executed both in WinPE and Windows. This directory will be used in Windows, so why not keep the script in the same place instead of having to make a copy of it?
The next step is executing the script. Here is the command line I use: powershell.exe -executionpolicy bypass -file %SystemDrive%\DCU\DellBIOSDriverUpdate.ps1 -WindowsRepository \\<FileShare Repository> -BIOSPassword <Password> -WinPERepository "t:"
The next step is deleting the old XML file. I have the task sequences copy over the XML file to the repository directory in the event I want to look at it. This task sequence deletes the file if it exists.
The next step is to copy the XML to the repository.
Finally, we unmap the T: drive again.
This is all that is to use this script in the WinPE environment.
Next, is using it in the windows environment. This one is much easier. The first thing, don't enter it as an application. If you do, it can only be executed one time in the task sequence. Enter it as a Run Command line task sequence as shown below.
You do need to use the full UNC path and filename for the command line and enter the UNC path under start in.
You maybe wondering what the repository looks like. Here is a pic of the repository that contains the directories labeled after each release version containing the update. The XML files contain the report of all needed drivers derived from the dcu-cli.exe. This is also where I keep this script.
This is a video showing the process of the script in action. I exposed the steps to display on the screen so you could see what it is doing here.
Here is a video of it operating during the build after the OS was laid down.
You can download this script from my GitHub site located here.
I decided to rework the entire script at that point. The script now operates in both WinPE and the Windows environment. You may ask why you would want to execute this in WinPE. The reason is that if there is a BIOS update needed and you also configure BIOS settings before the OS is laid down, you will want this to be performed first as some BIOS settings cannot be changed after the OS is laid down.
The script detects if it is running in WinPE and will only execute a BIOS update at that point. The way the script works is that it executes the DCU-CLI.exe and uses the /report parameter to only generate a report on what to install. Here is a sample report:
The script then reads the XML report file as shown above to find what to install. In WinPE, it automatically knows to only run the BIOS update. In Windows, it will install all updates if no parameter is defined. You can see in the script that I have defined BIOS, Drivers, and Applications meaning if you select any of these, that is all that will be installed. If you don't select any, all will be installed.
Inside the XML file, there is the <file> field that gives the download address for downloading the driver. After talking with Warren Byle from Dell, I verified that types of updates to include in this script. Warren also verified the release code is unique for every new version of a driver. This gave me the idea to create a local driver repository so they can be downloaded and then executed locally. This saves a lot on time and bandwidth, especially when talking about hundreds of megabytes of data downloads.
The script downloads every driver listed in the XML file to the specified network share outlined in the WindowsRepository parameter. It will first scan the repository folders for one named after the release code and verify the contents matches the file download. If so, it skips downloading.
The next step is installing the drivers. I found that /S works on all Dell driver installations. The only part I had to figure out were the return codes, which are 0, 2, and 3010 for a successful installation.
Another thing I changed was the process of setting this up in MDT. Here are the steps I put in the task sequence processes for running this within WinPE.
I chose to use T: for my mapped drive since I know that driver letter is not used for anything else here. The Map T: Drive task is as shown.
The next step was copying the Dell Client | Update files to WinPE.
These files I copy over were grabbed from the Dell Command | Update directory after it had been installed on a PC.
The next thing I do is to copy the MSI.DLL file to the WinPE system32 directory. I grabbed this file initially from the system32 directory on my Win10 machine.
The next step is unmapping the T: drive. This is necessary because we are going to map that drive letter to the next UNC path.
The next step is to map to the repository location, where the PowerShell script also resides.
We're now going to copy over the PowerShell script. You may wonder why I chose here. That is because this script is intended to be executed both in WinPE and Windows. This directory will be used in Windows, so why not keep the script in the same place instead of having to make a copy of it?
The next step is executing the script. Here is the command line I use: powershell.exe -executionpolicy bypass -file %SystemDrive%\DCU\DellBIOSDriverUpdate.ps1 -WindowsRepository \\<FileShare Repository> -BIOSPassword <Password> -WinPERepository "t:"
The next step is deleting the old XML file. I have the task sequences copy over the XML file to the repository directory in the event I want to look at it. This task sequence deletes the file if it exists.
The next step is to copy the XML to the repository.
Finally, we unmap the T: drive again.
This is all that is to use this script in the WinPE environment.
Next, is using it in the windows environment. This one is much easier. The first thing, don't enter it as an application. If you do, it can only be executed one time in the task sequence. Enter it as a Run Command line task sequence as shown below.
You do need to use the full UNC path and filename for the command line and enter the UNC path under start in.
This is a video showing the process of the script in action. I exposed the steps to display on the screen so you could see what it is doing here.
Here is a video of it operating during the build after the OS was laid down.
You can download this script from my GitHub site located here.
<#
.SYNOPSIS
Update the BIOS and Drivers
.DESCRIPTION
This script will update the BIOS, Applications, and Drivers. It can detect if it is running within the WinPE or Windows environments. If it is running within WinPE, it will only update the BIOS, otherwise it will run all updates.
.PARAMETER WindowsRepository
UNC path to the updates Windows Repository that is accessible if the operating system is present
.PARAMETER BIOSPassword
Password for the BIOS
.PARAMETER BIOS
Perform BIOS updates only
.PARAMETER Drivers
Perform drivers updates only
.PARAMETER Applications
Perform applications updates only
.PARAMETER WinPERepository
Path to the updates Windows Repository that is accessible if running within WinPE
.EXAMPLE
Running in Windows only and applying all updates
powershell.exe -file DellBIOSDriverUpdate.ps1 -WindowsRepository "\\UNCPath2Repository"
Running in WinPE Only
powershell.exe -file DellBIOSDriverUpdate.ps1 -WinPERepository "t:"
Running in both Windows and WinPE
powershell.exe -file DellBIOSDriverUpdate.ps1 -WindowsRepository "\\UNCPath2Repository" -WinPERepository "t:"
.NOTES
===========================================================================
Created with: SAPIEN Technologies, Inc., PowerShell Studio 2017 v5.4.140
Created on: 6/21/2017 3:16 PM
Created by: Mick Pletcher
Filename: DellBIOSDriverUpdate.ps1
===========================================================================
#>
param
(
[string]$WindowsRepository,
[string]$BIOSPassword,
[switch]$BIOS,
[switch]$Drivers,
[switch]$Applications,
[string]$WinPERepository
)
function Get-Architecture {
<#
.SYNOPSIS
Get-Architecture
.DESCRIPTION
Returns 32-bit or 64-bit
.EXAMPLE
Get-Architecture
.NOTES
Additional information about the function.
#>
[CmdletBinding()][OutputType([string])]
param ()
$OSArchitecture = (Get-WmiObject -Class Win32_OperatingSystem | Select-Object OSArchitecture).OSArchitecture
Return $OSArchitecture
}
function Get-WindowsUpdateReport {
<#
.SYNOPSIS
Get list of updates to install
.DESCRIPTION
Execute the dcu-cli.exe to get a list of updates to install.
.EXAMPLE
PS C:\> Get-WindowsUpdateReport
.NOTES
Additional information about the function.
#>
[CmdletBinding()][OutputType([xml])]
param ()
#Test if this is running in the WinPE environment
If ((test-path -Path 'REGISTRY::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\WinPE\') -eq $true) {
$Executable = Get-ChildItem -Path "x:\DCU" -Filter dcu-cli.exe
$ReportFile = "x:\DCU\DriverReport.xml"
} else {
$Architecture = Get-Architecture
If ($Architecture -eq "32-Bit") {
$Executable = Get-ChildItem -Path $env:ProgramFiles"\Dell\CommandUpdate" -Filter dcu-cli.exe
} else {
$Executable = Get-ChildItem -Path ${env:ProgramFiles(x86)}"\Dell\CommandUpdate" -Filter dcu-cli.exe
}
#Name and location of the report file
If ($WindowsRepository[$WindowsRepository.Length - 1] -ne "\") {
$ReportFile = $WindowsRepository + "\" + "DriverReport.xml"
} else {
$ReportFile = $WindowsRepository + "DriverReport.xml"
}
}
#Delete XML report file if it exists
If ((Test-Path -Path $ReportFile) -eq $true) {
Remove-Item -Path $ReportFile -Force -ErrorAction SilentlyContinue
}
#Define location where to write the report
$Switches = "/report" + [char]32 + $ReportFile
#Get dcu-cli.exe report
$ErrCode = (Start-Process -FilePath $Executable.FullName -ArgumentList $Switches -Wait -Passthru).ExitCode
#Retrieve list of drivers if XML file exists
If ((Test-Path -Path $ReportFile) -eq $true) {
#Get the contents of the XML file
[xml]$DriverList = Get-Content -Path $ReportFile
Return $DriverList
} else {
Return $null
}
}
function Get-WinPEUpdateReport {
<#
.SYNOPSIS
Get Dell Client Update Report
.DESCRIPTION
Execute the Dell Client | Update to generate the XML file listing available updates
.EXAMPLE
PS C:\> Get-WinPEUpdateReport
.NOTES
Additional information about the function.
#>
[CmdletBinding()]
param ()
#Define XML Report File
$ReportFile = $env:SystemDrive + "\DCU\DriversReport.xml"
#Delete XML Report file
If ((Test-Path $ReportFile) -eq $true) {
Remove-Item -Path $ReportFile -Force | Out-Null
}
#Define Dell Client | Update commandline executable
$Executable = $env:SystemDrive + "\DCU\dcu-cli.exe"
#Define switches for Dell Client | Update
$Switches = "/report" + [char]32 + $ReportFile
#Execute Dell Client | Update
$ErrCode = (Start-Process -FilePath $Executable -ArgumentList $Switches -Wait -Passthru).ExitCode
#Retrieve list of drivers if XML file exists
If ((Test-Path -Path $ReportFile) -eq $true) {
#Get the contents of the XML file
[xml]$DriverList = Get-Content -Path $ReportFile
Return $DriverList
} else {
Return $null
}
}
function Update-Repository {
<#
.SYNOPSIS
Update the repository
.DESCRIPTION
This function reads the list of items to be installed and checks the repository to make sure the item is present. If it is not, the item is downloaded to the repository.
.PARAMETER Updates
List of Updates to be installed
.EXAMPLE
PS C:\> Update-Repository
.NOTES
Additional information about the function.
#>
[CmdletBinding()]
param
(
[ValidateNotNullOrEmpty()]$Updates
)
#Set the variable to the to the repository
If ((test-path -Path 'REGISTRY::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\WinPE\') -eq $true) {
If ($WinPERepository[$WinPERepository.Length - 1] -ne "\") {
$Repository = $WinPERepository + "\"
} else {
$Repository = $WinPERepository
}
} elseif ($WindowsRepository[$WindowsRepository.Length - 1] -ne "\") {
$Repository = $WindowsRepository + "\"
} else {
$Repository = $WindowsRepository
}
foreach ($Update in $Updates.Updates.Update) {
#Define the storage location of this driver
$UpdateRepository = $Repository + $Update.Release
#Get the URI to download the file from
$DownloadURI = $Update.file
$DownloadFileName = $UpdateRepository + "\" + ($DownloadURI.split("/")[-1])
#Create the new directory if it does not exist
If ((Test-Path $UpdateRepository) -eq $false) {
New-Item -Path $UpdateRepository -ItemType Directory -Force | Out-Null
}
#Download file if it does not exist
If ((Test-Path $DownloadFileName) -eq $false) {
Invoke-WebRequest -Uri $DownloadURI -OutFile $DownloadFileName
}
}
}
function Update-Applicatons {
<#
.SYNOPSIS
Update Dell Applications
.DESCRIPTION
This function only updates Dell Applications
.PARAMETER Updates
List of updates to install
.EXAMPLE
PS C:\> Update-Applicatons
.NOTES
Additional information about the function.
#>
[CmdletBinding()]
param
(
[ValidateNotNullOrEmpty()]$Updates
)
if ($WindowsRepository[$WindowsRepository.Length - 1] -ne "\") {
$Repository = $WindowsRepository + "\"
} else {
$Repository = $WindowsRepository
}
foreach ($Update in $Updates.Updates.Update) {
#Check if update is a application update
If ($Update.type -eq "Application") {
#Get application update file
$UpdateFile = $Repository + $Update.Release + "\" + (($Update.file).split("/")[-1])
#Verify application update file exists
If ((Test-Path $UpdateFile) -eq $true) {
$Output = "Installing " + $Update.name + "....."
Write-Host $Output -NoNewline
# /s to suppress user interface
$Switches = "/s"
$ErrCode = (Start-Process -FilePath $UpdateFile -ArgumentList $Switches -WindowStyle Minimized -Wait -Passthru).ExitCode
If (($ErrCode -eq 0) -or ($ErrCode -eq 3010)) {
Write-Host "Success" -ForegroundColor Yellow
} else {
Write-Host "Failed" -ForegroundColor Red
}
}
}
}
}
function Update-BIOS {
<#
.SYNOPSIS
Update the BIOS
.DESCRIPTION
This function will update the BIOS on the system
.PARAMETER Updates
List of updates to install
.PARAMETER Update
XML info of the BIOS update
.EXAMPLE
PS C:\> Update-BIOS
.NOTES
Additional information about the function.
#>
[CmdletBinding()]
param
(
[ValidateNotNullOrEmpty()]$Updates
)
#Set the variable to the to the repository
If ((test-path -Path 'REGISTRY::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\WinPE\') -eq $true) {
If ($WinPERepository[$WinPERepository.Length - 1] -ne "\") {
$Repository = $WinPERepository + "\"
} else {
$Repository = $WinPERepository
}
} elseif ($WindowsRepository[$WindowsRepository.Length - 1] -ne "\") {
$Repository = $WindowsRepository + "\"
} else {
$Repository = $WindowsRepository
}
foreach ($Update in $Updates.Updates.Update) {
#Check if update is a BIOS update
If ($Update.type -eq "BIOS") {
#Get BIOS update file
$UpdateFile = $Repository + $Update.Release + "\" + (($Update.file).split("/")[-1])
#Verify BIOS update file exists
If ((Test-Path $UpdateFile) -eq $true) {
$Output = "Installing " + $Update.name + "....."
Write-Host $Output -NoNewline
# /s to suppress user interface
$Switches = "/s /p=" + $BIOSPassword
$ErrCode = (Start-Process -FilePath $UpdateFile -ArgumentList $Switches -WindowStyle Minimized -Wait -Passthru).ExitCode
If (($ErrCode -eq 0) -or ($ErrCode -eq 2) -or ($ErrCode -eq 3010)) {
Write-Host "Success" -ForegroundColor Yellow
} else {
Write-Host "Failed" -ForegroundColor Red
}
}
}
}
}
function Update-Drivers {
<#
.SYNOPSIS
Update Dell Drivers
.DESCRIPTION
This function only updates Dell drivers
.PARAMETER Updates
List of updates to install
.EXAMPLE
PS C:\> Update-Drivers
.NOTES
Additional information about the function.
#>
[CmdletBinding()]
param
(
[ValidateNotNullOrEmpty()]$Updates
)
if ($WindowsRepository[$WindowsRepository.Length - 1] -ne "\") {
$Repository = $WindowsRepository + "\"
} else {
$Repository = $WindowsRepository
}
foreach ($Update in $Updates.Updates.Update) {
#Check if update is a application update
If ($Update.type -eq "Driver") {
#Get driver update file
$UpdateFile = $Repository + $Update.Release + "\" + (($Update.file).split("/")[-1])
$UpdateFile = Get-ChildItem -Path $UpdateFile
#Verify driver update file exists
If ((Test-Path $UpdateFile) -eq $true) {
$Output = "Installing " + $Update.name + "....."
Write-Host $Output -NoNewline
# /s to suppress user interface
$Switches = "/s"
$ErrCode = (Start-Process -FilePath $UpdateFile.Fullname -ArgumentList $Switches -WindowStyle Minimized -Passthru).ExitCode
$Start = Get-Date
Do {
$Process = (Get-Process | Where-Object { $_.ProcessName -eq $UpdateFile.BaseName }).ProcessName
$Duration = (Get-Date - $Start).TotalMinutes
} While (($Process -eq $UpdateFile.BaseName) -and ($Duration -lt 10))
If (($ErrCode -eq 0) -or ($ErrCode -eq 2) -or ($ErrCode -eq 3010)) {
Write-Host "Success" -ForegroundColor Yellow
} else {
Write-Host "Failed with error code $ErrCode" -ForegroundColor Red
}
}
}
}
}
Clear-Host
#Check if running in WinPE environment and get Windows Updates Report
If ((test-path -Path 'REGISTRY::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\WinPE\') -eq $true) {
$Updates = Get-WinPEUpdateReport
} Else {
#Get list of drivers
$Updates = Get-WindowsUpdateReport
}
$Updates.Updates.Update.Name
#Process drivers if there is a list
If ($Updates -ne $null) {
Update-Repository -Updates $Updates
}
#Check if running in WinPE environment
If ((test-path -Path 'REGISTRY::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\WinPE\') -eq $true) {
#Perform BIOS Update
Update-BIOS -Updates $Updates
} Else {
#Install Applications (APP)
If (($Applications.IsPresent) -or ((!($Applications.IsPresent)) -and (!($BIOS.IsPresent)) -and (!($Drivers.IsPresent)))) {
Update-Applicatons -Updates $Updates
}
#Install BIOS (BIOS)
If (($BIOS.IsPresent) -or ((!($Applications.IsPresent)) -and (!($BIOS.IsPresent)) -and (!($Drivers.IsPresent)))) {
Update-BIOS -Updates $Updates
}
#Install Bundle (SBDL)
#Install Drivers (DRVR)
If (($Drivers.IsPresent) -or ((!($Applications.IsPresent)) -and (!($BIOS.IsPresent)) -and (!($Drivers.IsPresent)))) {
Update-Drivers -Updates $Updates
}
#Install Firmware (FRMW)
#Install ISV Driver (ISVDRVR)
}
I found in your blog something very good, I need to implode this in the company where I work, I live in Brazil, could you give me more information about
ReplyDeleteDell Automatic BIOS, Application, and Driver Updates in Build