UEFI

Lenovo BIOS Manipulation: Set-LenovoBIOSSetting

Changing a BIOS setting on a Lenovo, is a two step process:

  • Change the setting(s) in question
  • Commit (or Save) the all of your changes

Explanation

The script supports execution against remote systems to silently and easily toggle various BIOS settings.

By default the script relies on Get-LenovoBIOSSetting for validation purposes:

  1. It confirms the Setting you want to change is a valid setting.
  2. It confirms the count of the results returned from the previous command is equal to 1.
    This is done to ensure you specified an explicit setting like ‘Boot Up Num-Lock Status’ and not something generic like ‘Boot%’ which could return ‘Boot Agent’, ‘Boot Up Num-Lock Status’, ‘BootMode’, ‘BootOrder’ and so on.
  3. Finally, it will attempt to validate the Value you specified by evaluating the ‘OptionalValue’s returned by query.

Once it passes validation – or you bypass it – it then proceeds.

However there is a safety net and that is the requirement of the -Commit parameter to actually flip the bits.  If the -Commit parameter isn’t included, which is the default, it doesn’t make any changes.

Function Set-LenovoBIOSSetting


#region Function Set-LenovoBIOSSetting
Function Set-LenovoBIOSSetting
    {
        [cmdletbinding(SupportsShouldProcess=$True)]
        Param
            (
                [Parameter(Mandatory=$false)]
                    [string[]]$ComputerName = $env:computername,

                [Parameter(Mandatory=$true)]
                    [string]$Setting,

                [Parameter(Mandatory=$true)]
                [Alias('NewValue')]
                    [string]$Value,

                [Parameter(Mandatory=$false)]
                    [switch]$SkipCheck = $false,

                [Parameter(Mandatory=$false)]
                    [switch]$Commit
            )

        Begin { $NewValue = $Value }

        Process
            {
                foreach($Computer in $ComputerName)
                    {
                        if($SkipCheck -eq $false)
                            {
                                # Validate Setting - Part 1: Ensure this is a legitimate settng in the BIOS
                                $CurrentSettings = Get-LenovoBIOSSetting -ComputerName $Computer -Setting $Setting
                                if([string]::IsNullOrEmpty($CurrentSettings)) { Write-Output "ERROR INVALID SETTING SPECIFIED: [$Setting]"; throw }

                                # Validate Setting - Part 2: Ensure an 'exact' BIOS setting was specified like 'USB Port 1' not 'USB%' which could match several.
                                if(@($CurrentSettings).Count -gt 1) { Write-Output "ERROR SETTING [$Setting] RETURNED TOO MANY MATCHES: [$($CurrentSettings.Setting -join ', ')]"; throw }

                                # Validate New Value - If the OptionalValue isn't 'N/A' then we can verify the user supplied
                                if($CurrentSettings.OptionalValue -ne 'N/A')
                                    {
                                        if($($CurrentSettings.OptionalValue -split ',') -cnotcontains $Value) { Write-Output "ERROR: INVALID VALUE SPECIFIED [$Value] - MUST BE ONE OF: [$($CurrentSettings.OptionalValue)]"; throw }
                                    }

                                if($CurrentSettings.CurrentValue -eq $NewValue) { Write-Output "No change made for [$Setting] on [$Computer] because new and current values are identical:`r`nCurrent Value: [$($CurrentSettings.CurrentValue)]`r`nUpdated Value: [$NewValue]"; return }

                                $Action = "from [$($CurrentSettings.CurrentValue)] to [$NewValue]"
                            }
                        else { $Action = "to [$NewValue]" }

                        if($PSCmdlet.ShouldProcess("Setting [$Setting] on [$Computer] $Action"))
                            {
                                If($Commit -eq $true)
                                    {
                                        $OriginalEAP = $ErrorActionPreference
                                        $ErrorActionPreference = 'Stop'

                                        Try { $SetResult = (gwmi -class Lenovo_SetBiosSetting -namespace root\wmi -ComputerName $Computer -ErrorAction Stop).SetBiosSetting("$Setting,$NewValue") }
                                        Catch { Write-Output "ERROR: FAILED TO SET BIOS SETTING [$Setting] TO [$NewValue] ON [$Computer]: $_"; throw }

                                        $ErrorActionPreference = $OriginalEAP

                                        Switch($SetResult.return)
                                            {
                                                'Success' { Write-Output "$($SetResult.Return)fully set [$Setting] on [$Computer] $Action" }
                                                default
                                                    {
                                                        Write-Output "ERROR: FAILED TO SET [$Setting] TO [$NewValue] ON [$Computer]: [$($SetResult.Return)]"
                                                        if($Firmware -eq 'UEFI') { Write-Output "HINT: Firmware is UEFI [$Firmware] and you //may// first need to disable either 'OS Optimized Defaults' or 'SecureBoot' as these lock certain settings in the BIOS." }
                                                        throw
                                                    }
                                            }
                                    }
                                Else
                                    {
                                        Write-Output "WARNING: Commit is false [$Commit]; NOT Changing BIOS Setting [$Setting] on [$Computer] $Action!"
                                    }
                            }
                    }
            }

        End {  }
    }
#endregion Function Set-LenovoBIOSSetting

One Final Tidbit: Save-LenovoBIOSSettings

This one is important and I intentionally put this down here at the bottom.  Like I mentioned in the beginning, making BIOS change is a two step process, and so far we’ve only done one of the two.

The final step is to save the changes so that they’re committed.  Yes, I realize I’m using the parameter -Commit above which may be a little confusing but that’s just to make sure you don’t accidentally shoot yourself in the foot: write a change you didn’t mean to write and then forget what you had previously, so you struggle to get back to where you were before.

I intentionally didn’t include the ‘save’ code in the function above because I prefer to set all the changes first:

  • Switch to UEFI via ‘Boot Mode’ and ‘Boot Priority’ or ‘SecureBoot’
  • Set boot device order
  • Enable/Disable USB ports
  • Enable/Disable Bluetooth
  • Enable/Disable IPv4NetworkStack and/or IPv6NetworkStack
  • And so on…

Once all the changes are staged and no errors were encountered, I save the settings once via a call to a function which also requires the -Commit parameter to help prevent accidental foot shooting:


#region Function Set-LenovoBIOSSetting
Function Set-LenovoBIOSSetting
    {
        [cmdletbinding(SupportsShouldProcess=$True)]
        Param
            (
                [Parameter(Mandatory=$false)]
                    [string[]]$ComputerName = $env:computername,

                [Parameter(Mandatory=$true)]
                    [string]$Setting,

                [Parameter(Mandatory=$true)]
                [Alias('NewValue')]
                    [string]$Value,

                [Parameter(Mandatory=$false)]
                    [switch]$SkipCheck = $false,

                [Parameter(Mandatory=$false)]
                    [switch]$Commit
            )

        Begin { $NewValue = $Value }

        Process
            {
                foreach($Computer in $ComputerName)
                    {
                        if($SkipCheck -eq $false)
                            {
                                # Validate Setting - Part 1: Ensure this is a legitimate settng in the BIOS
                                $CurrentSettings = Get-LenovoBIOSSetting -ComputerName $Computer -Setting $Setting
                                if([string]::IsNullOrEmpty($CurrentSettings)) { Write-Output "ERROR INVALID SETTING SPECIFIED: [$Setting]"; throw }

                                # Validate Setting - Part 2: Ensure an 'exact' BIOS setting was specified like 'USB Port 1' not 'USB%' which could match several.
                                if(@($CurrentSettings).Count -gt 1) { Write-Output "ERROR SETTING [$Setting] RETURNED TOO MANY MATCHES: [$($CurrentSettings.Setting -join ', ')]"; throw }

                                # Validate New Value - If the OptionalValue isn't 'N/A' then we can verify the user supplied
                                if($CurrentSettings.OptionalValue -ne 'N/A')
                                    {
                                        if($($CurrentSettings.OptionalValue -split ',') -cnotcontains $Value) { Write-Output "ERROR: INVALID VALUE SPECIFIED [$Value] - MUST BE ONE OF: [$($CurrentSettings.OptionalValue)]"; throw }
                                    }

                                if($CurrentSettings.CurrentValue -eq $NewValue) { Write-Output "No change made for [$Setting] on [$Computer] because new and current values are identical:`r`nCurrent Value: [$($CurrentSettings.CurrentValue)]`r`nUpdated Value: [$NewValue]"; return }

                                $Action = "from [$($CurrentSettings.CurrentValue)] to [$NewValue]"
                            }
                        else { $Action = "to [$NewValue]" }

                        if($PSCmdlet.ShouldProcess("Setting [$Setting] on [$Computer] $Action"))
                            {
                                If($Commit -eq $true)
                                    {
                                        $OriginalEAP = $ErrorActionPreference
                                        $ErrorActionPreference = 'Stop'

                                        Try { $SetResult = (gwmi -class Lenovo_SetBiosSetting -namespace root\wmi -ComputerName $Computer -ErrorAction Stop).SetBiosSetting("$Setting,$NewValue") }
                                        Catch { Write-Output "ERROR: FAILED TO SET BIOS SETTING [$Setting] TO [$NewValue] ON [$Computer]: $_"; throw }

                                        $ErrorActionPreference = $OriginalEAP

                                        Switch($SetResult.return)
                                            {
                                                'Success' { Write-Output "$($SetResult.Return)fully set [$Setting] on [$Computer] $Action" }
                                                default
                                                    {
                                                        Write-Output "ERROR: FAILED TO SET [$Setting] TO [$NewValue] ON [$Computer]: [$($SetResult.Return)]"
                                                        if($Firmware -eq 'UEFI') { Write-Output "HINT: Firmware is UEFI [$Firmware] and you //may// first need to disable either 'OS Optimized Defaults' or 'SecureBoot' as these lock certain settings in the BIOS." }
                                                        throw
                                                    }
                                            }
                                    }
                                Else
                                    {
                                        Write-Output "WARNING: Commit is false [$Commit]; NOT Changing BIOS Setting [$Setting] on [$Computer] $Action!"
                                    }
                            }
                    }
            }

        End {  }
    }
#endregion Function Set-LenovoBIOSSetting

Obviously you’re welcome to do as you wish in your environment, even roll the execution of the WMI method to the Set-LenovoBIOSSettings function.

Just be safe and sure of what you’re doing 🙂

Good Providence to you as you reconfigure your BIOS’!

Advertisements

Preparing for Windows 10: Switching to UEFI on Lenovo ThinkPad & ThinkCentre

think this has been talked about elsewhere but I don’t have the direct link/s(?) anymore so … sorry if you think I’m stealing thunder.

You know how people say “Oh I hate that” when they really don’t really hate it?  Well I truly abhor the idea of people doing things that could be automated.  I’m not trying to put people out of a job here!  But our time is expensive and better suited for more important tasks like putting out the occasional fire, providing excellent customer service and just contributing to IT being an agile and proactive entity in the organization.

As we prepare to pilot Windows 10, we need to go from Legacy BIOS to UEFI on our fleet of Lenovo workstations and, to help our teams on the ground make this transition as smooth as possible, I started exploring how to go about doing this.

When I initially looked at Lenovo hardware a handful of years ago now I learned that Lenovo provided some sample VBScripts to help configure the BIOS on various hardware.  I leveraged those scripts to enable TPM on our demo ThinkPads and ThinkCentres and set boot order.  Fortunately it was nothing but a bunch of WMI calls making it easy to manipulate in VBScript.  Now that I’m on the PowerShell boat, it’s even easier.  (That isn’t to say there aren’t challenges because there’s always a challenge!)

TL;DR

In its simplest form,  you can query the BIOS on a Lenovo via:

gwmi -class Lenovo_BiosSetting -namespace root\wmi | % { if ($_.CurrentSetting -ne "") { $_.CurrentSetting } }

And you can set a BIOS setting on a Lenovo via:

(gwmi -class Lenovo_SetBiosSetting -namespace root\wmi).SetBiosSetting("$Setting,$Value")

At the moment, we have several models of machines in different families (ThinkPad, ThinkCentre and ThinkStation) spanning anywhere from 1 to 4 generations.  To further complicate things, each of those families, and the generations within, don’t necessarily have the same BIOS options or BIOS values which makes figuring things out a little tricky.

The good news is that once you figure out what needs to change it’s easy.
The bad news is that you have to figure out what needs to change, and that includes order of operations.

Bare Bones Config

I could be mistaken, but I do believe that the X240’s and T440’s and up share similar BIOS options which means if you get one working, you pretty much have them all working.  Still, once you think you have it sorted, I’d do a quick query to verify the settings and values match up across them all.

You’d be forgiven for thinking that you could enable UEFI  on a ThinkPad system via something like:

(gwmi -class Lenovo_SetBiosSetting -namespace root\wmi).SetBiosSetting("Boot Mode","UEFI Only")
(gwmi -class Lenovo_SetBiosSetting -namespace root\wmi).SetBiosSetting("Boot Priority","UEFI First")

Turns out those options are not exposed because, well, that would make sense so of course they’re not there.  Instead you have to enable ‘Secure Boot’ which flips those bits for you:

(gwmi -class Lenovo_SetBiosSetting -namespace root\wmi).SetBiosSetting("SecureBoot","Enable")

Ok semi smart!  So you mosey on over to your ThinkCentre, like an M900, and try to do the same but that doesn’t work either.  Why would it – that would be too easy.

Reminds me of one of my favorite scenes in Groundhog Day.

As it turns out the ThinkCentre is the complete opposite of the ThinkPad:
You can set the ‘Boot Priority’ and ‘Boot Mode’ but you cannot set ‘Secure Boot’.

(gwmi -class Lenovo_SetBiosSetting -namespace root\wmi).SetBiosSetting("Boot Mode","UEFI Only")
(gwmi -class Lenovo_SetBiosSetting -namespace root\wmi).SetBiosSetting("Boot Priority","UEFI First")

*Le sigh*

It’s completely nonsensical but that’s what happens when you have siloed engineering teams working on different, but similar, products.

At the moment, I don’t have an answer for enabling Secure Boot on ThinkCentre’s but it will likely require using SRWIN or SRDOS, and I believe it may require human intervention whereas the WMI calls do not.  If I find a solution, you’ll be the second to know. 🙂

 

Good Providence!

UEFI Windows 10 Installation via USB

Most organizations are running Windows 7 on either legacy hardware or UEFI capable hardware but have disabled UEFI in favor of the legacy BIOS emulation and using an MBR partitioning style versus GPT.  Prior to Windows 7, most deployment tools didn’t work with UEFI and there were almost no UEFI benefits for Windows 7, which is why the legacy configuration was preferred.  But in Windows 10, there are some benefits like faster startup time, better support for resume/hibernate, security etc. that you’ll want to take advantage of.

Although not ideal for Windows 10, you could keep using legacy BIOS emulation (which will work just fine, and “be supported for years to come”) and deal with UEFI for new devices or as devices are returned to IT and prepared for redistribution.  But if you want to take advantage of the new capabilities Windows 10 on UEFI enabled devices offers, you’ll essentially have to do a hardware swap because there’s no good way to ‘convert’ as it requires:

  • changing the firmware settings on the devices
  • changing the disk from an MBR disk to a GPT disk
  • changing the partition structure

All coordinated as part of an OS refresh or an upgrade.

Now, I wouldn’t go as far as to say it’s not possible to automate the above (I love me a good challenge), but the recommended procedure is to capture the state from the old device, do a bare metal deployment on a properly configured UEFI device then restore the data onto said device.

If you’re imaging machines with MDT or SCCM and are PXE booting, all you need to do is:

  • add the x64 boot media to your task sequence
  • deploy your task sequence to a device collection that contains the machine you wish to image in question
  • reconfigure the BIOS for UEFI

If however you’re imaging machines by hand with physical boot media, you’ll want a properly configured USB drive to execute the installation successfully.

There are loads of blogs that talk about creating bootable USB media but the majority of them don’t speak to UEFI.  And those that do touch on UEFI, almost all of them miss that one crucial step which is what allows for a proper UEFI based installation.

What you need:

  • 4GB+ USB drive
  • a UEFI compatible system
  • some patience

Terminology

  • USB drive = a USB stick or USB thumb drve – whatever you want to call it
  • USB hard drive = an external hard drive connected via USB; not the same as above
  • Commands I’m referencing are in italics
  • Commands you have to type are in bold italics

Step 1 – Locate your USB Drive

Open an elevated command prompt & run diskpart
At the diskpart console, type: lis dis
At the diskpart console, type: lis vol

You should have a screen that looks similar to this:
Diskpart_lisdis_lisvol

I frequently have two USB hard drives and one USB drive plugged into my machine, so when I have to re-partition the USB drive, I have to be super extra careful.  So to make sure I’m not screwing up, I rely on a few things to make sure I’m picking the proper device.

First: The dead giveaway lies in the ‘lis vol‘ command which shows you the ‘Type’ of device.  We know USB drives can be removed and they’re listed as ‘Removable’.  There’s only one right now, Volume 8 which is assigned drive letter E.

Second: I know that my USB drive is 8GB in size, so I’m looking at the ‘Size’ column in both the ‘lis vol‘ and ‘lis dis‘ commands to confirm I’m looking at the right device.  And from ‘lis dis‘ I see my USB drive is Disk 6.

Step 2 – Prepare USB Drive

From the diskpart console, we’re going to issue a bunch of commands to accomplish our goal.

Select the proper device: sel dis 6

Issue these seven diskpart commands to prepare the USB drive:

  1. cle
  2. con gpt
  3. cre par pri
  4. sel par 1
  5. for fs=fat32 quick
  6. assign
  7. exit

That’s it!  The second diskpart command above is the *most critical step* for properly preparing your USB drive for installing Windows on UEFI enabled hardware, and nearly all the popular sites omit that step.  Bonkers!
Feel free to close the command window now.

Step 3 – Prepare the Media

With your USB drive properly setup now, all you need to do is mount the Windows 10 ISO and copy the contents to the USB drive.

If you’re on Windows 8 or Windows 10 already, right right-click the ISO and ‘Mount’.
If you’re on Windows 7, use something like WinCDEmu to mount the ISO.

Once mounted, you can copy the contents from the ‘CD’ top the USB drive.

Step 4 – Image

A this point all that’s left to do is

  • boot your machine(s)
  • make sure your BIOS is setup for UEFI versus Legacy BIOS; or simply enable ‘Secure Boot’ which on many machines sets UEFI as the default automatically
  • boot from your USB drive
  • install Windows

 

Hopefully this has helped point you in the right direction for taking advantage of all Windows 10 on UEFI enabled hardware has to offer.

 

Good Providence!