Introduction
This is something that I’ve wanted to do for a while; to always install the latest BIOS and drivers automatically during OSD.
Keeping BIOS and driver versions up to date, can be a tedious and time consuming task, and I wanted to take on a more cloud-like approach.
For that reason, I’ve spent some time on Lenovo Thin Installer as well as Lenovo System Update, but they didn’t quite live up to my expectations and need for flexibility.
Instead – and by coincident – I stumbled upon this awesome PowerShell module: jantari/LSUClient
It does exactly what Thin Installer and System Update offers, as well as giving you the flexibility of PowerShell. What’s not to like?
Configuration Manager
So, how do you put that PowerShell module to use with ConfigMgr during OSD?
- First off, I’m running the PowerShell module from within a nested Task Sequence. This is how all of my main Task Sequence is built; with the use of nested Task Sequences.
NOTE: Find the nested Task Sequence used in this post, available as a download in the very end of the post 🙂
- Within the nested task sequence, I start off by copying down the actual module.
- This is done from a regular package, meaning you will have to download the module and package it yourself.
- This can obviously be done differently and directly from the PowerShell Gallery (Install-Module, Import-Module). However, I have made changes to the module, and this approach also enables me to leave out the NuGet package provider.
NOTE: Be aware of any new versions coming out from the author himself. I’m currently hardcoding the version to be at 1.3.1. This will change, if a new version is being bumped by the author, and you intend to use the new version.
xcopy ".\*.*" "%ProgramFiles%\WindowsPowerShell\Modules\LSUClient\1.3.1" /D /E /C /I /Q /H /R /Y /S
Run LSUClient. This is where all the good stuff happens. Find the referenced script on my GitHub here: PowerShell/Run-LSUClientModule-OSD.ps1 at master · imabdk/PowerShell (github.com)
The script does following in headlines:
- Gets the current Lenovo computer model of the device running the script
- Using that for flexibility and filtering
- Imports the LSUClient module
- Runs the Get-LSUClient module via 2 different functions
- Again, used for filtering, depending on what model that’s running the script
- I needed this due to a very specific model acting up with the Intel Graphics driver
- Again, used for filtering, depending on what model that’s running the script
- Next up, I do some conditional clean up.
- When the script is run as SYSTEM, the packages downloaded by the module is stored in Windir\Temp. I prefer to clean those up, if everything went smooth, as they can take up a considerable amount of disk space.
- And I prefer to clean up the installed module as well, as that is no longer needed at this point.
What else?
The script I created to initiate the LSUClient module, also does some light registry tattooing. This is mostly done, in case I want to inventory any of this.
The model number and the model name of the device is listed, as well which drivers that was found and installed:
The LSUClient module as well as my script initiating the module, does logging into smsts.log:
Important!
I still recommend that you include the drivers for your NIC before moving into FullOS, otherwise you will start seeing slow downloads and other connectivity issues.
That translates into, you still need to do whatever you did with your drivers prior to this, but just for the drivers for the NIC.
I used to apply all drivers compressed as WIM, and I did a post on the topic last year: Apply drivers compressed with WIM during OSD with Configuration Manager.
I still do that, but now the .wim file only contains my NIC drivers for the specific model, and therefore it requires almost zero disk space.
- This also serves as a backup plan, in case something goes completely amiss with the new approach. It’s quite easy to mount the corresponding .wim file and add the necessary drivers 🙂
ENJOY 🙂
Download the nested Task Sequence: imab.dk-LSUClient.zip (6255 downloads )
I implemented this to test the scenario and seems like I am gettting this error:
I implemented this like this:
– Downloaded latest release from here https://github.com/jantari/LSUClient/releases/tag/v1.3.2
– Created a package: PowerShell Module LSU Client 1.3.2 and distributed
– Downloaded your TS zip and Imported to our SCCM console
– Changed the package(Copy LSU Client Module) to my new package created PowerShell Module LSU Client 1.3.2
– Changed the copy folder to 1.3.2
-Changed the condition to 1.3.2
– Perform the test
What error are you getting? 🙂
I did the same as Sri, here is the error I receive:
The task sequence execution engine failed executing the action (Run LSUClient) in the group (TS – OSD – LSUClient Module) with the error code 1
Action output: … is NOT set to ‘True’)
Executing command line: Run PowerShell Script with options (0, 4)
VERBOSE: Script is running.
VERBOSE: Lenovo device is detected. Continuing.
VERBOSE: Lenovo modelnumber: 20S0 – Lenovo model: ThinkPad T14 Gen 1
VERBOSE: LSUClient module not loaded. Continuing.
VERBOSE: Running LSUClient with default function
Process completed with exit code 1
VERBOSE: Script failed to carry out one or more actions.
VERBOSE: The term ‘Get-LSUpdate’ is not recognized as the name of a cmdlet, function, script file, or operable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
VERBOSE: Script is done running.
PowerShell command line returned code 1
䕖䉒协㩅匠牣灩⁴�捩獩搠瑥捥整䌠湯楴畮湩䕖䉒协㩅䰠湥癯潭敤湬浵敢㩲㈠匰‰敌潮潶洠摯汥›桔湩偫摡吠㐱䜠湥ㄠ䕖䉒协㩅䰠啓汃敩瑮洠摯汵潮⁴潬摡摥潃瑮湩極杮嘮剅佂䕓›畒湮湩卌䍕楬�. The operating system reported error 1: Incorrect function.
Something is not right in your end with the module itself. Can you load the module manually with Import-Module LSUClient and run Get-LSUpdate?
I’ve just implemented this, and had a problem where it was failing to run. Ran it seperately on another machine within Windows and noticed that the latest version is 1.3.2 and the script was naming the folder 1.3.1 which in turn failed the LSU because it couldn’t find the right location. After renaming the folder it ran like a dream.
Right, there’s something like that to be aware of, as I do package the module manually due to some custom changes. I did notice that the version got bump recently by the author. Thanks for bringing it up! 🙂
Must confess I am seeing the same issues as all those above, same error. Quite frustrating as this looks like it could be a nice solution.
Make sure to modify whatever needs to modified in order to cater for the new version (1.3.2). If you can download and import the module separately (Install-Module LSUClient, Import-Module LSUClient), above will work just fine, if you remember to pay attention to the version.
If Get-LSUpdate is not recognized as a cmdlet, something about importing the module is not right in your end. I’m happy to assist if you provide me some more details 🙂
Do I need to edit any of the scripting to match the package ID’s within the task sequence?
or do I just select Overwrite Ignore Dependencies when importing nested task sequence?
I have version 1.3.3 and have edited the copy folder and conditions for 1.3.3
do i need to edit the powershell script within the task sequence?
Only thing i touched there so far was company name.
I also set up only to install the nic via wim , that seems to work but it doesnt seem to download and install updated drivers, testing on a T480s.
So disregard my last post, i got this to work in OSD v1.3.3, but it keeps failing to finish.
Installing NIC via .wim file
on my Test T480s it only downloads one driver, installs it and then starts another but never downloads it and just fails to finish.
Then tested on a T590, downloads about 6 drivers , but the process never finishes installing them all and just hangs on the process in OSD , need to hard boot the laptop.
i havent tried v1.3.1 or 1.3.2 , is it possible jantari’s script has changed to the point where it as effected yours?
hello very cool article. Is this also possible with HP models?
NO, only lenovo. HP should have another software for that if i remember correctly
This works a one step in TS:
$ModuleName = “LSUClient”
#Setup LOCALAPPDATA Variable
[System.Environment]::SetEnvironmentVariable(‘LOCALAPPDATA’,”$env:SystemDrive\Windows\system32\config\systemprofile\AppData\Local”)
$WorkingDir = $env:TEMP
#PowerShellGet from PSGallery URL
if (!(Get-Module -Name PowerShellGet)){
$PowerShellGetURL = “https://psg-prod-eastus.azureedge.net/packages/powershellget.2.2.5.nupkg”
Invoke-WebRequest -UseBasicParsing -Uri $PowerShellGetURL -OutFile “$WorkingDir\powershellget.2.2.5.zip”
$Null = New-Item -Path “$WorkingDir\2.2.5” -ItemType Directory -Force
Expand-Archive -Path “$WorkingDir\powershellget.2.2.5.zip” -DestinationPath “$WorkingDir\2.2.5”
$Null = New-Item -Path “$env:ProgramFiles\WindowsPowerShell\Modules\PowerShellGet” -ItemType Directory -ErrorAction SilentlyContinue
Move-Item -Path “$WorkingDir\2.2.5” -Destination “$env:ProgramFiles\WindowsPowerShell\Modules\PowerShellGet\2.2.5”
}
#PackageManagement from PSGallery URL
if (!(Get-Module -Name PackageManagement)){
$PackageManagementURL = “https://psg-prod-eastus.azureedge.net/packages/packagemanagement.1.4.7.nupkg”
Invoke-WebRequest -UseBasicParsing -Uri $PackageManagementURL -OutFile “$WorkingDir\packagemanagement.1.4.7.zip”
$Null = New-Item -Path “$WorkingDir\1.4.7” -ItemType Directory -Force
Expand-Archive -Path “$WorkingDir\packagemanagement.1.4.7.zip” -DestinationPath “$WorkingDir\1.4.7”
$Null = New-Item -Path “$env:ProgramFiles\WindowsPowerShell\Modules\PackageManagement” -ItemType Directory -ErrorAction SilentlyContinue
Move-Item -Path “$WorkingDir\1.4.7” -Destination “$env:ProgramFiles\WindowsPowerShell\Modules\PackageManagement\1.4.7”
}
#Import PowerShellGet
Import-Module PowerShellGet
#Install Module from PSGallery
Install-Module -Name $ModuleName -Force -AcceptLicense -SkipPublisherCheck
Import-Module -Name $ModuleName -Force
$companyName = “SSAB”
$global:regKey = “HKLM:\SOFTWARE\$companyName\OSDDrivers”
function Get-LenovoComputerModel() {
$lenovoVendor = (Get-CimInstance -ClassName Win32_ComputerSystemProduct).Vendor
if ($lenovoVendor = “LENOVO”) {
Write-Verbose -Verbose “Lenovo device is detected. Continuing.”
$global:lenovoModel = (Get-CimInstance -ClassName Win32_ComputerSystemProduct).Version
$modelRegEx = [regex]::Match((Get-CimInstance -ClassName CIM_ComputerSystem -ErrorAction SilentlyContinue -Verbose:$false).Model, ‘^\w{4}’)
if ($modelRegEx.Success -eq $true) {
$global:lenovoModelNumber = $modelRegEx.Value
Write-Verbose -Verbose “Lenovo modelnumber: $global:lenovoModelNumber – Lenovo model: $global:lenovoModel”
} else {
Write-Verbose -Verbose “Failed to retrieve computermodel”
}
} else {
Write-Verbose -Verbose “Not a Lenovo device. Aborting.”
exit 1
}
}
function Load-LSUClientModule() {
if (-NOT(Get-Module -Name LSUClient)) {
Write-Verbose -Verbose “LSUClient module not loaded. Continuing.”
if (Get-Module -Name LSUClient -ListAvailable) {
Write-Verbose -Verbose “LSUClient module found available. Try importing and loading it.”
try {
Install-Module -Name ‘LSUClient’ -Force -ErrorAction Stop
#Install-Module -Name “%ProgramFiles%\WindowsPowerShell\Modules\LSUClient\1.4.2\LSUClient.psm1”
#Import-Module -Name “%ProgramFiles%\WindowsPowerShell\Modules\LSUClient\1.4.2\LSUClient.psm1”
Write-Verbose -Verbose “Successfully imported and loaded the LSUClient module.”
} catch {
Write-Verbose -Verbose “Failed to import the LSUClient module. Aborting.”
#exit 1
}
}
} else {
Write-Verbose -Verbose “LSUClient module already imported and loaded.”
}
}
function Run-LSUClientModuleDefault() {
$regKey = $global:regKey
if (-NOT(Test-Path -Path $regKey)) { New-Item -Path $regKey -Force | Out-Null }
$updates = Get-LSUpdate | Where-Object { $_.Installer.Unattended }
foreach ($update in $updates) {
Install-LSUpdate $update -Verbose
New-ItemProperty -Path $regKey -Name $update.ID -Value $update.Title -Force | Out-Null
}
}
#No Intel Graphics Driver
#Some weird shit going on with the package here on certain models, making the script run forever
function Run-LSUClientModuleCustom() {
$regKey = $global:regKey
if (-NOT(Test-Path -Path $regKey)) { New-Item -Path $regKey -Force | Out-Null }
$updates = Get-LSUpdate | Where-Object { $_.Installer.Unattended -AND $_.Title -notlike “Intel HD Graphics Driver*”}
foreach ($update in $updates) {
Install-LSUpdate $update -Verbose
New-ItemProperty -Path $regKey -Name $update.ID -Value $update.Title -Force | Out-Null
}
}
try {
Write-Verbose -Verbose “Script is running.”
Get-LenovoComputerModel
Load-LSUClientModule
if ($global:lenovoModelNumber -eq “20QF”) {
Write-Verbose -Verbose “Running LSUClient with custom function”
Run-LSUClientModuleCustom
} else {
Write-Verbose -Verbose “Running LSUClient with default function”
Run-LSUClientModuleDefault
}
}
catch [Exception] {
Write-Verbose -Verbose “Script failed to carry out one or more actions.”
Write-Verbose -Verbose $_.Exception.Message
exit 1
}
finally {
$currentDate = Get-Date -Format g
if (-NOT(Test-Path -Path $regKey)) { New-Item -Path $regKey -Force | Out-Null }
New-ItemProperty -Path $regKey -Name “_RunDateTime” -Value $currentDate -Force | Out-Null
New-ItemProperty -Path $regKey -Name “_LenovoModelNumber” -Value $global:lenovoModelNumber -Force | Out-Null
New-ItemProperty -Path $regKey -Name “_LenovoModel” -Value $global:lenovoModel -Force | Out-Null
Write-Verbose -Verbose “Script is done running.”
}