Deploy Lenovo ThinkCentre, ThinkPad, and ThinkStation BIOS by Custom WinPE

In this blog post, we are updating a previous post that only covered ThinkPad.  We have included updated scripting and information to cover ThinkCentre and ThinkStation as well.

Upgrading BIOS versions and getting consistency in an environment can provide unique challenges across an organization.  This blog post will lay out a possible process for putting together a custom WinPE boot WIM file to deploy a BIOS upgrades to current ThinkCentre, ThinkPad, and ThinkStation products.

Overview of the solution:

  1. Setup the server side folder structure
    • Folders
    • Logs
    • BIOS Folders
      • BIOS update files
    • Powershell script
  2. Configure the WinPE.wim file
    • Add necessary optional components
    • Add necessary drivers to the boot image
    • Edit the StartNet.cmd file in the WinPE.wim file.

Setup the server side folder structure

Beginning with the server side folder structure, create a new share (or use an existing share with appropriate permissions) on a server.  In that share, create a folder called BIOSVersions.  Inside BIOSVersions, create a folder called Logs and then create a folder with the first 4 digits of the SMBIOSBIOSVersion of each model computer with a BIOS to upgrade.




The folders seen above are for the X1 Carbon 4th (N1FE) and X1 Carbon 3rd (N14E).  The contents of each folder are made up of the source files from the Windows installable version of the BIOS from https:\\support.lenovo.com.  Once each BIOS has been downloaded and extracted, copy it to the coordinating folder.  Be sure to include all files and sub-folders from the extracted location.

Once the folder structure has been created and populated, create a blank Powershell file called BIOSUpdater.ps1 and copy the following lines of code to the file and save it.



#Define Variables
$exeName = ""
$srcDir = "T:\BIOSVersions\"
$localBiosFolder = "X:\bios"
$csproduct = Get-WmiObject -Class Win32_ComputerSystemProduct -Namespace root\cimv2
$deviceID = $csproduct.Name + '-' + $csproduct.IdentifyingNumber
$type = $csproduct.Version.Split()[0]
$model = $csproduct.Version.Split(' ', 2)[1]
$osArc = (Get-WmiObject -Class Win32_OperatingSystem -Namespace root\cimv2).OSArchitecture
$BiosCode = (Get-WmiObject -Class Win32_BIOS -Namespace root\cimv2).SMBIOSBIOSVersion.Substring(0,4)
$log = $srcDir + 'logs\' + $deviceID + '.log'

#Locate and Copy BIOS Folder
Write-Host "Locating BIOS folder"
$folder = Get-ChildItem -Path $srcDir | ? {$_.Name -eq $BiosCode}
if($folder -eq $null)
{
    "Could not find folder for $deviceID. Looking for $BiosCode" | out-file $log
    Write-Host "Could not locate BIOS folder"
    Write-Host "Examine log file on the share for more information"
    return
}
Write-Host "Located BIOS folder"

Write-Host "Begin BIOS Files Copy"
Copy-Item ($srcDir + $folder + '\*') $localBiosFolder -Recurse
Write-Host "Completed BIOS Files Copy"

#Execute BIOS Flash and Reboot
Write-Host "Executing BIOS Flash"
If($type -eq "ThinkPad")
{
    $exeName = if($osArc -eq "32-bit") {"WINUPTP.EXE"} else {"WINUPTP64.EXE"}
    $arg1 = "-s"
       
    & .\$exeName $arg1 | Out-Null
    
    If (Test-Path $localBiosFolder\winuptp.log)
    {
        Copy-Item $localBiosFolder\winuptp.log $log       # Copy the log file for this device to the network share for auditing purposes.
    } 
    Else 
    {
        "Could not find log file for $deviceID" | out-file $log
        Write-Host "Could not locate Winuptp.log file"
    }
    Write-Host "Completing BIOS Flash..."
    Write-Host "..."
    Write-Host "Rebooting"    
    & "wpeutil" reboot
}
ElseIf($type -eq "ThinkCentre" -or ($type -eq "ThinkStation" -and $model -like "P3*"))
{
    & ".\Flash.cmd" /ign /sccm /quiet | Out-File $log # Check the readme.txt to ensure that each WFlash2.exe supports the /SCCM (Reboot Suppress) switch.
    Write-Host "Completing BIOS Flash..."
    Write-Host "..."
    Write-Host "Rebooting"
    & "wpeutil" shutdown      # This needs to be a shutdown command, as the ThinkCentre BIOS documentation states 
                              # that the computer needs to reach an S5 power state to complete the update of the 
                              # BIOS.  Computer will actually reboot.
}
ElseIf($type -eq "ThinkStation")
{
    $exeName = if($osArc -eq "32-bit") {"AFUWIN.EXE"} else {"AFUWINx64.EXE"}
    $ROMName = Get-ChildItem -Path $localBiosFolder -Include "*.ROM" -Name
    & .\$exeName $ROMName /P /B /N /R /SP /RTB /FIT | Out-File $log
    Write-Host "Completing BIOS Flash..."
    Write-Host "..."
    Write-Host "Rebooting"
    & "wpeutil" reboot
}
Else
{
    "Unable to determine type of machine for $deviceID" | out-file $log
    Write-Host "Unable to determine type of machine for $deviceID"
}

BIOSUpdater.ps1


Configure the WinPE.wim file

The next item is to configure the WinPE.wim file with all of the pertinent optional components, drivers, and script updates.

In this example, we have the Windows 10 ADK installed to the default directory (C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment).  

Copy the winpe.wim file from amd64\en-us folder to a spot to work with it.  I am using D:\TestBIOS\WinPE.wim as the area that is copied to.

Load the the wim file into command window using the DISM Command to open the image.


Dism /Mount-Image /ImageFile:D:\TestBios\WinPE.wim /Index:1 /MountDir:D:\MountIt

Next we need to configure Optional Components.  Add the following Optional Components in this specific order, as order does matter for this installation:


1. HTA:


Dism /Image:D:\MountIt /Add-Package /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-HTA.cab"
Dism /Image:D:\MountIt /Add-Package /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-HTA_en-us.cab"

2. Scripting:


Dism /Image:D:\MountIt /Add-Package /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-Scripting.cab"
Dism /Image:D:\MountIt /Add-Package /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-Scripting_en-us.cab"

3. WMI:


Dism /Image:D:\MountIt /Add-Package /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-WMI.cab"
Dism /Image:D:\MountIt /Add-Package /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-WMI_en-us.cab"

4. .NET Framework (NetFX):


Dism /Image:D:\MountIt /Add-Package /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-NetFX.cab"
Dism /Image:D:\MountIt /Add-Package /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-NetFX_en-us.cab"

5. Powershell:


Dism /Image:D:\MountIt /Add-Package /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-Powershell.cab"
Dism /Image:D:\MountIt /Add-Package /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-Powershell_en-us.cab"

After loading those optional components, we need to add in a driver for interrogating the battery status on ThinkPads.


Dism /Image:D:\MountIt /Add-Driver /Driver:D:\MountIt\Windows\INF\Battery.inf

Next we load any networking drivers needed.  I suggest using the WinPE Boot Image Driver Packs.


Dism /Image:D:\MountIt /Add-Driver /Driver:<Path to extracted WinPE Drivers>

The last item is to edit the StartNet.cmd file located at
D:\MountIt\Windows\System32.  Add the following lines of code to the .cmd file after the "wpeinit" line. Change <Server>, <Share>, <password>, and <domain>\<user> to your appropriate values. Save and then close the file.



@echo OFF
echo Loading 3rd party PNP drivers
wpeinit
echo Completed 3rd party PNP driver load
echo Creating directory: X:\BIOS
mkdir X:\BIOS
cd X:\BIOS
echo Completed directory creation
echo Attempting conection to share.
@ipconfig > ipconfig.txt
@ping <SERVER> > ping.txt
@net use T: \\<SERVER>\<SHARE> <PASSWORD> /user:<DOMAIN>\<USER>
IF NOT %ErrorLevel% EQU 0 goto EndDrv
echo Copying powershell script file locally.
xcopy T:\BIOSVersions\BiosUpdater.ps1 /iqvy
Powershell -ExecutionPolicy UnRestricted -File X:\BIOS\BiosUpdater.ps1
:EndDrv
echo Connection to server\share not established.  Please reboot and try again.

Finally we use Dism to close up the WinPE.wim file.


Dism /Unmount-Image /MountDir:D:\MountIt /commit

Once the WinPE.wim file closes up completely, load it into WDS or your favorite PXE Service and update a BIOS.

When your ThinkCentre, ThinkPad, or ThinkStation client PXE boots this boot image, it will automatically launch the BiosUpdate.ps1 script.  The script will find the appropriate BIOS update source files on the network share and will execute the BIOS update silently.  The script will then reboot the system at which time the BIOS will be updated.  Once complete the system will reboot back to Windows.