Modern BIOS Management with the Administration Service

CharlesEndpoint Management15 Comments

2021-04-27 update: The solution now works over CMG. Please see this post for details.

If you haven’t seen my first blog post about modern driver management, the quick summary is that the solution uses packages created with the Driver Automation Tool and the administration service to retrieve information on these packages and identify the most suitable driver package to apply in a task sequence.

In this second part, I’ll be discussing the changes and improvements done to the existing solution to also dynamically retrieve and filter and apply BIOS updates.

Note: Download link for the task sequence exports is located at the end of the blog post.

Invoke-GetPackageIDFromAdminService improvements

When I initially wrote the script that queries the AdminService, I wanted to make sure I could reuse the same script to return both BIOS and Driver packages. But when it was time to actually design the task sequence for applying BIOS packages, I found out that I forgot something to determine the system’s current BIOS version and filter out any BIOS packages which were not an upgrade to the current system.

New parameters for BIOS information

I needed to provide more information to the script as a parameter regarding the system BIOS. The 2 following parameters were added to the script:

  • CurrentBIOSVersion
  • CurrentBIOSReleaseDate

Why do you need the release date of the BIOS?
Good question! The original solution by the Nickolaj Andersen and Maurice Daly of MSEndPointMgr uses the BIOS release date for Lenovo systems to determine if the BIOS package is an update or not. Now why exactly? I’m guessing Lenovo was not very good at keeping some sort of standard naming or versioning for their BIOS versions.

Example of a Lenovo BIOS package created with the Driver Automation Tool

BIOS filtering

Now that we have the current BIOS version and release date, we need to evaluate and filter out older BIOS package versions. We only want the script to return a package ID if the package is actually an upgrade for the device.

For this part, I had to dig into the Invoke-CMDownloadBIOSPackage.ps1 script and in the script of the Driver Automation Tool to see how they were filtering and extracting version information for the different vendors. I ended up with the following code to compare BIOS versions.

Add-TextToCMLog $LogFile  "Filtering package results to only BIOS packages that would be an upgrade to the current BIOS." $component 1
$ApplicableBIOSPackages = New-Object System.Collections.ArrayList
If($Manufacturer -ne "Lenovo"){
	#Check if any of the packages has a BIOS version higher than the current BIOS version
	If($CurrentBIOSVersion -and $CurrentBIOSVersion -ne "Unknown"){
		Add-TextToCMLog $LogFile  "Filtering package results to only packages that have a BIOS version higher than  `"$($CurrentBIOSVersion)`"" $component 1

		foreach($package in $Packages){
			switch($Manufacturer){
				"Dell"{
					If($package.Version -as [Version]){
						If($CurrentBIOSVersion -as [Version]){
							If(([Version]$Package.Version) -gt [Version]$CurrentBIOSVersion){
								[void]$ApplicableBIOSPackages.Add($package)
							}
						}ElseIf($CurrentBIOSVersion -like "A*"){
							#Moving from A__ version to a proper version number is considered an upgrade for Dell systems
							[void]$ApplicableBIOSPackages.Add($package)
						}
					}ElseIf(($Package.Version -like "A*") -and ($CurrentBIOSVersion -like "A*")){
						If(([Int32]::Parse(($Package.Version).TrimStart("A"))) -gt ([Int32]::Parse(($CurrentBIOSVersion).TrimStart("A")))){
							[void]$ApplicableBIOSPackages.Add($package)
						}
					}
				}
				"Hewlett-Packard"{
					$packageVersion = ($package.Version).TrimEnd(".")
					$packageVersion = $packageVersion.Split(" ")[0] #Example: 02.02.03 A 1 --> Will only use 02.02.03 for evaluating
					If($packageVersion -as [Version]){
						If($CurrentBIOSVersion -as [Version]){
							If([Version]$packageVersion -gt [Version]$CurrentBIOSVersion){
								[void]$ApplicableBIOSPackages.Add($package)
							}
						}Else{#Attempting to extract a version number from the current BIOS version provided
							$CleanBIOSVersion = $CurrentBIOSVersion.TrimEnd(".")
							$CleanBIOSVersion = $CleanBIOSVersion.Split(" ")[0]
							If($CleanBIOSVersion -as [Version]){
								If([Version]$packageVersion -gt [Version]$CleanBIOSVersion){
									[void]$ApplicableBIOSPackages.Add($package)
								}
							}
						}
					}ElseIf($packageVersion -match ".*F\.(\d+)$"){
						$packageVersion = $matches[1]
						If($CurrentBIOSVersion -match ".*F\.(\d+)$"){
							If([int32]$packageVersion -gt [int32]$matches[1]){
								[void]$ApplicableBIOSPackages.add($package)
							}
						}
					}
				}
				"Microsoft"{
					Add-TextToCMLog $LogFile  "No BIOS package will be returned, Microsoft provides firmware updates as part of their driver packages." $component 2
				}
				default{
					#Any other manufacturer: Compare versions only if they both parse as [Version] objects
					If(($package.Version -as [Version]) -and ($CurrentBIOSVersion -as [Version])){
						If([Version]($package.Version) -gt [Version]$CurrentBIOSVersion){
							[void]$ApplicableBIOSPackages.Add($package)
						}
					}
				}
			}
		}
	}Else{
		Add-TextToCMLog $LogFile  "No current BIOS version specified, cannot compare BIOS version." $component 3
	}
}Else{
	#Lenovo Only: Check if any of the remaining packages have a BIOS Release Date newer than the current BIOS Release Date
	Add-TextToCMLog $LogFile  "Filtering package results to only packages that have a BIOS release date newer than `"$($CurrentBIOSReleaseDate)`"." $component 1
	$BIOSReleaseDate = [datetime]::ParseExact($CurrentBIOSReleaseDate,"yyyyMMdd",$null)
	foreach($package in $Packages){
		If($package.Description -match "\(Models included:(.*)\) \(Release Date:(.*)\)"){
			Try{
				$ReleaseDate = [datetime]::ParseExact($matches[2],"yyyyMMdd",$null)
				If($ReleaseDate -gt $BIOSReleaseDate){
					[void]$ApplicableBIOSPackages.Add($package)
				}
			}Catch{
				Add-TextToCMLog $LogFile  "Failed to parse `"$matches[2]`" as a BIOS release date for package `"$($package.Name)`", skipping..." $component 2
			}
		}
	}
}
Example of the log file created when querying for a BIOS package

With these enhancements done on the script, we are now ready to use a task sequence to dynamically apply BIOS packages.

Task Sequences modifications

Modify the “Query AdminService for PackageID” TS

We need to provide the current BIOS version and release date to the script in the task sequence.

New TS variables used for BIOS packages

Then we pass this information as a parameter to the Invoke-GetPackageIDFromAdminService script.

Create the “Apply BIOS package” task sequence

The task sequence is similar to the one I had created for drivers. For applying the actual BIOS update, I’ve reused the scripts by the guys at MSEndpointMgr.

Regarding HP BIOS updates

If your HP devices have a BIOS password set (I hope you do), then you’ll need to specify 2 more variables:

  • PasswordBinFilename: Name of the .bin file to use when applying BIOS update
  • PasswordBinPackageID: PackageID of the package containing the .bin file.

I had to modify the original Invoke-HPBIOSUpdate script to add a new parameter to specify the path to the .bin file because the original script would expect the .bin file to be in the same folder as the script.

I also did not like the fact that the HP BIOS utility writes a log file in whatever directory it’s located in with no options to specify a different log path, so I added a step to move the log file to _SMSTSLogPath.

Download

Direct link to the task sequences

Link to the github repository

As always, feel free to contact me if you have any suggestions for improvements.

Thank you.

15 Comments on “Modern BIOS Management with the Administration Service”

  1. I don’t understand how I’m supposed to implement 2 different task sequences on an endpoint? 1 queries the Admin Service, and the other actually does the applying of the BIOS – Why aren’t they just together ? How can the information pulled from the first TS be used in any way in a different TS ? Thanks

    1. Hi Aron, the “Apply BIOS package” TS runs the “Query AdminService for PackageID” as a child task sequence. I separated the querying of the adminservice in a different TS because I reuse the exact same steps when applying driver packages.
      You’re only supposed to run the “Apply BIOS package” TS, you can either run the TS independently or add it as a step in your existing task sequence to take care of BIOS updates.

    1. Hi Kapil, No I haven’t had the chance to work on making the script work via CMG. I will update this blog post when I do.
      Thanks.

  2. Hi Charles,
    I’m currently moving away from WebService that I’ve been using extensively for many years now, and, as the guys from MSEndpintMgr haven’t updated their BIOS script to use AdminService, I’ve found your post.
    In my current bare metal TS, I’m calling your 2 TS but I’d like to add a condition on the BIOS version and if = the current BIOS, we could bypass the reboot to WinPE step that’s not necessary in this case.
    Currently, it’s rebooting even if it doesn’t need to apply a new BIOS.
    Is it me doing something wrong?
    I don’t see any condition related to this in your TS.
    Thanks for your help

    1. Hello,

      Normally the Apply BIOS Package TS will only reboot if an applicable BIOS package is found, the reboot step is inside the “Package Found” group. So if no package is returned by Invoke-GetPackageIDFromAdminService, there should not be a reboot occuring at this point.
      What does the GetPackageID_BIOSPackage.log file say? Is it returning a BIOS package even though your device is already at the latest version?

    2. The MSEndpintMgr script has been using the AdminService for about 4 months now, for BIOS and Driver?

      1. Do you have an updated link with that info? The site still shows the web service configuration

  3. Could you please provide your modified Invoke-HPBIOSUpdate.ps1 script? I have a password set on the BIOS but I am unsure what to add to the script to call the right location.

    1. Hi Alexander, if you look at the “Apply BIOS Package” task sequence there is a group called “Password” for the HP section with a disabled step called README. It has the details on what you need to do to be able to update the BIOS on HP machines with a password.
      In short, you need to:
      1) Create a .BIN file of your BIOS password with the HPQPswd.exe utility
      2) Create a package that includes this .BIN file and distribute the package
      3) Enable the “Set PasswordBinFileName” TS step and set it to the name of your .BIN file
      4) Enable the “Set PasswordBinPackageID” TS step and set it to the PackageID of the package you created in step 2

      If both of the steps are enabled, the “Apply BIOS Package” task sequence will download and make use of your .BIN file when upgrading the BIOS on HP machines.

    1. Hi Rob,
      If the task sequence variable “BIOSPassword” exists, it will be used when upgrading BIOS for Dell and Lenovo systems.
      You can either set the task sequence variable directly in the “Apply BIOS Package” TS or you can set it before running the task sequence itself.

Leave a Reply

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