Updating BIOS & Drivers using a Task Sequence, OSD, IPU, Stand alone.
Why DCU (Dell Command Update)? Why not driver packages? First off, this solution isn't right for some, and frankly is a horrible solution in many environments, but for others, it can be a life saver. If you don't have time to keep all of your driver packages updated for your 30,40,50 + Dell models in your environment, if you have good internet bandwidth on the clients, and you just want to make sure your machines are at the latest BIOS / Drivers at time of OSD, then this works pretty well.
Disclaimer... I use this in my Lab, I have a Dell 7470, so that's the extent of my testing. I used to use this at my previous organization, with 30+ models and worked very well. It's handy because if a new model comes in the door, you don't have any prep work, connect it up, and image, drivers and bios come right from Dell.
Other notes, this builds off my original post a few years back (when I used it in production). There is some other tips there that might come in handy if you run into any issue with brand new models requiring storage drivers or network drivers: https://garytown.com/say-goodbye-to-dell-driver-management-use-dell-command-update-in-osd
I've currently tested running this Task Sequence as a child TS in OSD & IPU scenarios, as well as just deploying the TS itself to a device and running it stand alone. This works in OSD after the OS is laid down, it does not support WinPE, so if you want to update your BIOS in the WinPE stage... sorry.
Please report any observations or tips in the comments, thanks. Other observations I've made about DCU: https://garytown.com/dell-command-update-tips-for-enterprise-automation
Requirements: CCTK Package, (more about that later), Dell Command Update Package 2.4 (or use the step I created that downloads and installs DCU from the internet), Custom Policy.XML files (I've included mine in the download), ideally some idea of the Task Sequences I've provided for WaaS to understand the reporting steps.
Do not use anything newer then 2.4 (at this time), 3.X currently has no CLI support. I hear they will be updating 3.x to include CLI, but no ETA. I have asked for many CLI enhancements, which would remove the need to even import a policy, as well as provide a BIOS password and have DCU suspend bitlocker with a parameter. I'm still waiting on all that. 🙂
Now to the goods...
Download: Dell Command Update Task Sequence (222 downloads)
What this Task Sequence Does (High Level):
- Checks for DCU and Installs if DCU is not installed then reboots
- Creates Folders for Logs & DCU Templates, the copies down my templates
- Creates Variables used later in reporting
- Check if BIOS Update Available
- Import BIOS / FIRMWARE DCU Template
- Runs DCU in Report Mode, generating a Report only if update exist
- Check if Report exist, if Yes, Run DCU BIOS Group
- if No Report, skip group to cleanup
- Update Group Runs
- Gets Info (Gather)
- Records Current BIOS info (for Reporting)
- Removes Current BIOS Password (CCTK)
- Suspend Bitlocker (if Enabled)
- Runs DCU to Update BIOS
- Adds BIOS Password back
- Reboots Machine
- Records new BIOS info (for Reporting)
- Enables Bitlocker (if Bitlocker is used)
- Clean Up
- Clears out the DCU Policy
- Check for Driver Updates
- Import Driver DCU Template
- Run DCU in Report Only mode
- if Report is generated run DCU Update group, else skip
- DCU Updates
- Run DCU and install all available Drivers
- Reboots
- DCU Cleanup
- Cleans up folders and files used
- Imports DCU Defaults Policy
- Run Script to record results to registry (For Reporting)
So that is the basics of it. This was the easiest way I could do it to control the reboot, so it would only reboot if updates were installed, it also creates a nice report (if updates are available) of what updates were available, and hence installed. I also separate the BIOS from Drivers for tighter control. I only wanted the BIOS Password removed for the shortest possible time, which was by doing it this way.
Another reason I did it this was to show it can be modular, you don't have to download and install everything Dell is pushing, you can pick and choose. You can greatly customize your Policy XML file to have it do other things as well, like Dell Command Suite Apps, etc. I'd recommend spending time in the GUI changing settings, run the scan, see what you get, then change it, scan again to see the differences. For my environment, I basically narrowed it down to BIOS/FIRMWARE settings check boxes for one Policy template, and Drivers Category, and all sub categories checked for my Drivers Policy. Check out my previous post for more info on that.
Steps & Conditions
Few notes about some odd things... All Steps that call dcu-cli.exe, I've set to continue on error. Importing a Policy always kills the TS and throws error, but I've noticed it does import the policy despite the error. I've also had it error on running the updates, returning non-0 exit codes, so I've set those to continue as well. Also, to get the dcu-cli to run, I had to do cmd.exe /c start /wait in front of the command or I'd get a really messed up error, I'll try to record that here later once I reproduce the issue. It was odd, everything worked perfectly in Elevated Command Prompt, but not during a TS.. that fixed it.
- Dell Command Update
WMI Query: select * from Win32_ComputerSystem where Manufacturer like "%Dell%" - Install DCU
- Set TSVar DCU Installed
- DCUInstalled = TRUE
Condition: File exist: C:\Program Files (x86)\Dell\CommandUpdate\dcu-cli.exe
- DCUInstalled = TRUE
- Set TS Var "SMSTS_UpdateTime"
1powershell.exe -command "&{$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment; $tsenv.Value('SMSTS_UpdateTime') = Get-Date -Format yyyyMMddTHHmmss}" - Install DCU 2.4.0 from Internet
Condition Var DCUInstall not True
1powershell.exe -command "Invoke-WebRequest -Uri "https://downloads.dell.com/FOLDER05055451M/1/Dell-Command-Update_DDVDP_WIN_2.4.0_A00.EXE" -OutFile "$env:temp\DCU.exe" -UseBasicParsing -Verbose ; start-process -FilePath "$env:temp\DCU.exe" -ArgumentList '/s /f'" - Restart Computer if DCU Installed
Condition Var DCUINstalled not True - Make Folder for DCU Results
1cmd.exe /c if Not Exist "%programdata%\DCU\DCUSettings" (md %programdata%\DCU\DCUSettings) - Make Folder DCULogs for Logs
1cmd.exe /c if Not Exist "%programdata%\DCULogs" (md %programdata%\DCULogs) - Copy XML File Local
Package = your package with the DCU Policy Setting XML files
1xcopy *.xml %programdata%\DCU\DCUSettings /Y - Set Var SetOSDInfoType = DCU
- SetOSDInfoType = True
Condition: SetOSDInfoType not exist
- SetOSDInfoType = True
- Set TS Var "SMSTS_Build" if not set
- Condition: SMSTS_Build not exist
1powershell.exe -command "$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment; $tsenv.Value('SMSTS_Build') = Get-ItemPropertyValue 'HKLM:SOFTWARE\WOW6432Node\Microsoft\Windows NT\CurrentVersion' 'Releaseid'"
- Condition: SMSTS_Build not exist
- Set TSVar DCU Installed
- Run DCU BIOS
- Set TS Var "SMSTS_BIOSStartTime"
1powershell.exe -command "&{$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment; $tsenv.Value('SMSTS_BIOSStartTime') = Get-Date -f 's'}"
- Set TS Var "SMSTS_BIOSStartTime"
- Run DCU Report if BIOS upgrade available
- Modify Dell DCU - Import Settings - BIOS
1cmd.exe /c start /wait C:\"Program Files (x86)"\Dell\CommandUpdate\dcu-cli.exe /import /policy %programdata%\DCU\DCUSettings\BIOSFirmware.xml - Run Dell Command Update Report Only (BIOS)
1cmd.exe /c start /wait C:\"Program Files (x86)"\Dell\CommandUpdate\dcu-cli.exe /report c:\ProgramData\DCU\DCUUpdatesBIOS.xml - Copy Report to Log Folder
1cmd.exe /c if Exist "%programdata%\DCU\DCUUpdatesBIOS.xml" (copy %programdata%\DCU\DCUUpdatesBIOS.xml %programdata%\DCULogs\DCUUpdatesBIOS%SMSTS_UpdateTime%.xml)
- Modify Dell DCU - Import Settings - BIOS
- Run DCU if BIOS Updates exist (Condition: File: %programdata%\DCU\DCUUpdatesBIOS.xml Exist)
- Gather Variables - Run PS Script from Gallery
- Set TS Var CurrentBiosVer
1powershell.exe -command "&{$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment; $tsenv.Value('CurrentBiosVer') = (Get-WmiObject win32_bios).SMBIOSBIOSVersion}" - Dell BIOS - Enable HAPI - HAPI\HAPIInstall.bat
Requires your CCTK Package - Start OSDDoNotLogCommand: TSVar: OSDDoNotLogCommand = True
- Set Password Variable - BIOSPWD = YOURBIOSPASSWORD
- Dell BIOS - Remove Password: Requires your CCTK Package
1cctk --setuppwd="" --valsetuppwd=%BIOSPWD% - Suspend Bitlocker (Condition TSVar IsBDE = True)
1Manage-bde.exe -protectors -disable c: - Stop OSDDoNotLogCommand: TSVar: OSDDoNotLogCommand = False
- Run Dell Command Update Install BIOS
1"C:\Program Files (x86)\Dell\CommandUpdate\dcu-cli.exe" /log c:\windows\ccm\logs /silent - Start OSDDoNotLogCommand: TSVar: OSDDoNotLogCommand = True
- Dell BIOS - Remove Password: Requires your CCTK Package
1cctk --setuppwd=%BIOSPWD% - Stop OSDDoNotLogCommand: TSVar: OSDDoNotLogCommand = False
- Restart Computer
- Set TS Var NewBiosVer
1powershell.exe -command "&{$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment; $tsenv.Value('NewBiosVer') = (Get-WmiObject win32_bios).SMBIOSBIOSVersion}" - Enable Bitlocker (Condition TSVar IsBDE = True)
1Manage-bde.exe -protectors -enable c:
- DCU BIOS CleanUp (No Conditions on Group)
- Clear DCU Policy
1reg delete HKLM\SOFTWARE\Dell\CommandUpdate\Preferences /F - Set TS Var "SMSTS_BIOSFinishTime"
1powershell.exe -command "&{$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment; $tsenv.Value('SMSTS_BIOSFinishTime') = Get-Date -f 's'}"
- Clear DCU Policy
- Run DCU Drivers
- Set TS Var "SMSTS_DriversStartTime"
1powershell.exe -command "&{$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment; $tsenv.Value('SMSTS_DriversStartTime') = Get-Date -f 's'}"
- Set TS Var "SMSTS_DriversStartTime"
- Run DCU Report if Drivers available
- Modify Dell DCU - Import Settings -All drivers
1cmd.exe /c start /wait C:\"Program Files (x86)"\Dell\CommandUpdate\dcu-cli.exe /import /policy %programdata%\DCU\DCUSettings\Drivers-All.xml - Run Dell Command Update Report Only (DRIVERS)
1cmd.exe /c start /wait C:\"Program Files (x86)"\Dell\CommandUpdate\dcu-cli.exe /report c:\ProgramData\DCU\DCUUpdatesDrivers.xml - Copy Report to Log Folder
1cmd.exe /c if Exist "%programdata%\DCU\DCUUpdatesDrivers.xml" (copy %programdata%\DCU\DCUUpdatesDrivers.xml %programdata%\DCULogs\DCUUpdatesDrivers%SMSTS_UpdateTime%.xml)
- Modify Dell DCU - Import Settings -All drivers
- Run DCU if Updates Exist (Condition :File: %programdata%\DCU\DCUUpdatesDrivers.xml exist)
- Run Dell Command Update Install Driver Updates
1"C:\Program Files (x86)\Dell\CommandUpdate\dcu-cli.exe" /log c:\windows\ccm\logs /silent - Restart
- Run Dell Command Update Install Driver Updates
- DCU CleanUp
- Set TS Var "SMSTS_DriversFinishTime"
1powershell.exe -command "&{$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment; $tsenv.Value('SMSTS_DriversFinishTime') = Get-Date -f 's'}" - Remove Folder ProgramData\DCU
1cmd.exe /c if Exist "%programdata%\DCU\" (rd %programdata%\DCU /S /Q) - Modify Dell DCU - Import Settings -Defaults
1cmd.exe /c start /wait C:\"Program Files (x86)"\Dell\CommandUpdate\dcu-cli.exe /import /policy %programdata%\DCU\DCUSettings\Defaults.xml - Record Results for Reporting
- Run PowerShell Script
- Parameters:-ID "%SMSTS_Build%" -WMI -Registry -Class "%SetOSDInfoType%" -Namespace "GARYTOWN"
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584<#.SYNOPSISSets information during OSD / IPU for Dell BIOS & Drivers.DESCRIPTIONThis script will add build, task sequence, and other information to the OS so that it can later be examined or inventoried.Information can be added to the registry, WMI, or both..PARAMETER RegistryThis switch will add information to the following location:- Registry.PARAMETER WMIThis switch will add information to the following location:- WMI Repository.EXAMPLESet-OSDInfo.ps1 -WMI -RegistryWill add all information to the following locations:- Registry- WMI Repository.NOTESModified from the versions by Stephane van Gulick from www.powershellDistrict.comThis was completely borrowed from Jason Sandys SetOSDInfo script... it's my go to for this...LINKhttp://blog.configmgrftw.comhttps://garytown.com for Modifications to Jason's Original Script.VERSION2019.01.20#>[cmdletBinding()]Param([Parameter(Mandatory=$false)][switch]$WMI,[Parameter(Mandatory=$false)][switch]$Registry,[Parameter(Mandatory=$false)][String]$Namespace,[Parameter(Mandatory=$false)][String]$Class,[Parameter(Mandatory=$true)][String]$ID,[Parameter(Mandatory=$false)][String]$AttributePrefix = "WaaS_")# Start-Transcript >> $env:temp\PowerShellTranscript.logFunction Get-WMINamespace{<#.SYNOPSISGets information about a specified WMI namespace..DESCRIPTIONReturns information about a specified WMI namespace..PARAMETER NamespaceSpecify the name of the namespace where the class resides in (default is "root\cimv2")..EXAMPLEGet-WMINamespaceLists all WMI namespaces..EXAMPLEGet-WMINamespace -Namespace cimv2Returns the cimv2 namespace..NOTESVersion: 1.0.LINKhttp://blog.configmgrftw.com#>[CmdletBinding()]Param([Parameter(Mandatory=$false,valueFromPipeLine=$true)][string]$Namespace)begin{Write-Verbose "Getting WMI namespace $Namespace"}Process{if ($Namespace){$filter = "Name = '$Namespace'"$return = Get-WmiObject -Namespace "root" -Class "__namespace" -filter $filter}else{$return = Get-WmiObject -Namespace root -Class __namespace}}end{return $return}}Function New-WMINamespace{<#.SYNOPSISThis function creates a new WMI namespace..DESCRIPTIONThe function creates a new WMI namespsace..PARAMETER NamespaceSpecify the name of the namespace that you would like to create..EXAMPLENew-WMINamespace -Namespace "ITLocal"Creates a new namespace called "ITLocal".NOTESVersion: 1.0.LINKhttp://blog.configmgrftw.com#>[CmdletBinding()]Param([Parameter(Mandatory=$true,valueFromPipeLine=$true)][string]$Namespace)if (!(Get-WMINamespace -Namespace "$Namespace")){Write-Verbose "Attempting to create namespace $($Namespace)"$newNamespace = ""$rootNamespace = [wmiclass]'root:__namespace'$newNamespace = $rootNamespace.CreateInstance()$newNamespace.Name = $Namespace$newNamespace.Put() | out-nullWrite-Verbose "Namespace $($Namespace) created."}else{Write-Verbose "Namespace $($Namespace) is already present. Skipping.."}}Function Get-WMIClass{<#.SYNOPSISGets information about a specified WMI class..DESCRIPTIONReturns the listing of a WMI class..PARAMETER ClassNameSpecify the name of the class that needs to be queried..PARAMETER NamespaceSpecify the name of the namespace where the class resides in (default is "root\cimv2")..EXAMPLEget-wmiclassList all the Classes located in the root\cimv2 namespace (default location)..EXAMPLEget-wmiclass -classname win32_biosReturns the Win32_Bios class..EXAMPLEget-wmiclass -Class MyCustomClassReturns information from MyCustomClass class located in the default namespace (root\cimv2)..EXAMPLEGet-WMIClass -Namespace ccm -Class *List all the classes located in the root\ccm namespace.EXAMPLEGet-WMIClass -NameSpace ccm -Class ccm_clientReturns information from the cm_client class located in the root\ccm namespace..NOTESVersion: 1.0.LINKhttp://blog.configmgrftw.com#>[CmdletBinding()]Param([Parameter(Mandatory=$false,valueFromPipeLine=$true)][string]$Class,[Parameter(Mandatory=$false)][string]$Namespace = "cimv2")begin{Write-Verbose "Getting WMI class $Class"}Process{if (Get-WMINamespace -Namespace $Namespace){$namespaceFullName = "root\$Namespace"Write-Verbose $namespaceFullNameif (!$Class){$return = Get-WmiObject -Namespace $namespaceFullName -Class * -list}else{$return = Get-WmiObject -Namespace $namespaceFullName -Class $Class -list}}else{Write-Verbose "WMI namespace $Namespace does not exist."$return = $null}}end{return $return}}Function New-WMIClass{<#.SYNOPSISThis function creates a new WMI class..DESCRIPTIONThe function create a new WMI class in the specified namespace.It does not create a new namespace however..PARAMETER ClassSpecify the name of the class that you would like to create..PARAMETER NamespaceSpecify the namespace where class the class should be created.If not specified, the class will automatically be created in "root\cimv2".PARAMETER AttributesSpecify the attributes for the new class..PARAMETER KeySpecify the names of the key attribute (or attributes) for the new class..EXAMPLENew-WMIClass -ClassName "OSD_Info"Creates a new class called "OSD_Info".EXAMPLENew-WMIClass -ClassName "OSD_Info1","OSD_Info2"Creates two classes called "OSD_Info1" and "OSD_Info2" in the root\cimv2 namespace.NOTESVersion: 1.0.LINKhttp://blog.configmgrftw.com#>[CmdletBinding()]Param([Parameter(Mandatory=$true,valueFromPipeLine=$true)][string]$Class,[Parameter(Mandatory=$false)][string]$Namespace = "cimv2",[Parameter(Mandatory=$false)][System.Management.Automation.PSVariable[]]$Attributes,[Parameter(Mandatory=$false)][string[]]$Key)$namespaceFullName = "root\$Namespace"if (!(Get-WMINamespace -Namespace $Namespace)){Write-Verbose "WMI namespace $Namespace does not exist."}elseif (!(Get-WMIClass -Class $Class -NameSpace $Namespace)){Write-Verbose "Attempting to create class $($Class)"$newClass = ""$newClass = New-Object System.Management.ManagementClass($namespaceFullName, [string]::Empty, $null)$newClass.name = $Classforeach ($attr in $Attributes){$attr.Name -match "$AttributePrefix(?<attributeName>.*)" | Out-Null$attrName = $matches['attributeName']$newClass.Properties.Add($attrName, [System.Management.CimType]::String, $false)Write-Verbose " added attribute: $attrName"}foreach ($keyAttr in $Key){$newClass.Properties[$keyAttr].Qualifiers.Add("Key", $true)Write-Verbose " added key: $keyAttr"}$newClass.Put() | out-nullWrite-Verbose "Class $($Class) created."}else{Write-Verbose "Class $($Class) is already present. Skipping..."}}Function New-WMIClassInstance{<#.SYNOPSISCreates a new WMI class instance..DESCRIPTIONThe function creates a new instance of the specified WMI class..PARAMETER ClassSpecify the name of the class to create a new instance of..PARAMETER NamespaceSpecify the name of the namespace where the class is located (default is Root\cimv2)..PARAMETER AttributesSpecify the attributes and their values using PSVariables..EXAMPLE$MyNewInstance = New-WMIClassInstance -Class OSDInfoCreates a new instance of the WMI class "OSDInfo" and sets its attributes..NOTESVersion: 1.0.LINKhttp://blog.configmgrftw.com#>[CmdletBinding()]Param([Parameter(Mandatory=$true)][ValidateScript({$_ -ne ""})][string]$Class,[Parameter(Mandatory=$false)][string]$Namespace="cimv2",[Parameter(Mandatory=$false)][System.Management.Automation.PSVariable[]]$Attributes)$classPath = "root\$($Namespace):$($Class)"$classObj = [wmiclass]$classPath$classInstance = $classObj.CreateInstance()Write-Verbose "Created instance of $Class class."foreach ($attr in $Attributes){$attr.Name -match "$AttributePrefix(?<attributeName>.*)" | Out-Null$attrName = $matches['attributeName']if ($attr.Value){$attrVal = $attr.Value}else{$attrVal = ""}$classInstance[$attrName] = $attrVal" added attribute value for $($attrName): $($attrVal)" >> $env:temp\newWMIInstance.log}$classInstance.Put()}Function New-RegistryItem{<#.SYNOPSISSets a registry value in the specified key under HKLM\Software..DESCRIPTIONSets a registry value in the specified key under HKLM\Software..PARAMETER KeySpecies the registry path under HKLM\SOFTWARE\ to create.Defaults to OperatingSystemDeployment..PARAMETER ValueNameThis parameter specifies the name of the Value to set..PARAMETER ValueThis parameter specifies the value to set..ExampleNew-RegistryItem -ValueName Test -Value "abc".NOTES-Version: 1.0#>[cmdletBinding()]Param([Parameter(Mandatory=$false)][string]$Key = "OperatingSystemDeployment",[Parameter(Mandatory=$true)][string]$ValueName,[Parameter(Mandatory=$false)][string]$Value)begin{$registryPath = "HKLM:SOFTWARE\WaaS\$ID"}Process{if ($registryPath -eq "HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\"){write-verbose "The registry path that is tried to be created is the uninstall string.HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\."write-verbose "Creating this here would have as consequence to erase the whole content of the Uninstall registry hive."exit}##Creating the registry nodeif (!(test-path $registryPath)){write-verbose "Creating the registry key at : $($registryPath)."try{New-Item -Path $registryPath -force -ErrorAction stop | Out-Null}catch [System.Security.SecurityException]{write-warning "No access to the registry. Please launch this function with elevated privileges."}catch{write-host "An unknown error occurred : $_ "}}else{write-verbose "The registry key already exists at $($registryPath)"}##Creating the registry string and setting its valuewrite-verbose "Setting the registry string $($ValueName) with value $($Value) at path : $($registryPath) ."try{New-ItemProperty -Path $registryPath -Name $ValueName -PropertyType STRING -Value $Value -Force -ErrorAction Stop | Out-Null}catch [System.Security.SecurityException]{write-host "No access to the registry. Please launch this function with elevated privileges."}catch{write-host "An unknown error occurred : $_ "}}End{}}try{$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment}catch{Write-Verbose "Not running in a task sequence."}$keyValue = "ID"#New-Variable -Name "$($AttributePrefix)$keyValue" -Value $IDif ($tsenv){#Get what kind of TS: CompatScan (CS) / InPlace Uprade (IPU) / Operating System Deployment (OSD) - Must have Step in TS that lets script knowif ($tsenv.Value("SetOSDInfoType")) {$SetOSDInfoType = $tsenv.Value("SetOSDInfoType")}#Gets the Time in Minutes it takes to run Parts of the Task Sequence - Requires you to set a Start Variable & Finish Variable$DifferenceBIOS = ([datetime]$TSEnv.Value('SMSTS_BIOSFinishTime')) - ([datetime]$TSEnv.Value('SMSTS_BIOSStartTime'))$DifferenceBIOS = [math]::Round($DifferenceBIOS.TotalMinutes)$DifferenceDrivers = ([datetime]$TSEnv.Value('SMSTS_DriversFinishTime')) - ([datetime]$TSEnv.Value('SMSTS_DriversStartTime'))$DifferenceDrivers = [math]::Round($DifferenceDrivers.TotalMinutes)if ($SetOSDInfoType -eq "OSD" -or $SetOSDInfoType -eq "DCU"){$ID = $SetOSDInfoType}if ($SetOSDInfoType -eq "IPU" -or $SetOSDInfoType -eq "OSD") #This will run during an IPU or OSD TS (Assuming you're using the methods I've setup in my WaaS blogs){New-Variable -Name "$($AttributePrefix)$($SetOSDInfoType)_BIOSUpgradeTime" -Value $DifferenceBIOSNew-Variable -Name "$($AttributePrefix)$($SetOSDInfoType)_DriverUpgradeTime" -Value $DifferenceDriversif($tsenv.Value('CurrentBiosVer')){New-Variable -Name "$($AttributePrefix)$($SetOSDInfoType)_OriginalBiosVer" -Value $tsenv.Value('CurrentBiosVer')}if($tsenv.Value('NewBiosVer')){New-Variable -Name "$($AttributePrefix)$($SetOSDInfoType)_UpdatedBiosVer" -Value $tsenv.Value('NewBiosVer')}}else{$AttributePrefix = "DCU_" #This will run if this is setup as a standalone TS.New-Variable -Name "$($AttributePrefix)$($SetOSDInfoType)_BIOSUpgradeTime" -Value $DifferenceBIOSNew-Variable -Name "$($AttributePrefix)$($SetOSDInfoType)_DriverUpgradeTime" -Value $DifferenceDriversif($tsenv.Value('CurrentBiosVer')){New-Variable -Name "$($AttributePrefix)$($SetOSDInfoType)_OriginalBiosVer" -Value $tsenv.Value('CurrentBiosVer')}if($tsenv.Value('NewBiosVer')){New-Variable -Name "$($AttributePrefix)$($SetOSDInfoType)_UpdatedBiosVer" -Value $tsenv.Value('NewBiosVer')}}}#$customInfo = @()#$customInfo = $tsenv.getVariables() | where {$_ -match "$($AttributePrefix).*"}#Foreach ($infoItem in $customInfo)#{# New-Variable -Name $infoItem -Value $tsenv.value($infoItem)#}$customAttributes = Get-Variable -Name "$AttributePrefix*"if ($PSBoundParameters.ContainsKey("WMI")){New-WMINamespace -Namespace $NamespaceNew-WMIClass -Namespace $Namespace -Class $Class -Attributes $customAttributes -Key $keyValueNew-WMIClassInstance -Namespace $Namespace -Class $Class -Attributes $customAttributes}if ($PSBoundParameters.ContainsKey("Registry")){foreach ($attr in $customAttributes){$attr.Name -match "$AttributePrefix(?<attributeName>.*)" | Out-Null$attrName = $matches['attributeName']if ($attr.Value){$attrVal = $attr.Value}else{$attrVal = ""}Write-Verbose "Setting registry value named $attrName to $attrVal"New-RegistryItem -Key "$($Class)\$ID" -ValueName $attrName -Value $attrVal}}
- Set TS Var "SMSTS_DriversFinishTime"
OK, So that is the long beast... you can remove many of those steps if you don't care about recording metrics. The Script should look familiar if you know my work, it’s 99% stolen from Jason Sandys blog. If you use my SetOSDInfo script in your WaaS task sequences, you could easliy just add the few changes into that, but to make this standalone, I created a separate script. Yes, you could acutally do everything in about 20 lines of code instead of several hundred.. but I like to re-use common code sets... to make the script I copy and past pretty much all of it, then added the 15-ish lines I needed.
Notes about the command line steps, you'll see
Results when run with OSD:
It records before & after BIOS versions, along with the time taken to run the BIOS Update and the Driver Updates.
Note that is also logs the activities log to c:\windows\ccm\logs folder, which is a very detailed log (in XML) of the DCU-CLI step process
If you need help making a CCTK package, please check out this Post by Mike Terrill: https://miketerrill.net/2015/08/24/how-to-create-a-dell-command-configure-package-in-configmgr/
Posted on GARYTOWN.COM
Nice blog, Gary! A year or so ago I was using DCU in task sequences and never had to use the start /wait and C:\"Program Files (x86)". I thought maybe this was a symptom of version 2.4.0 of DCU but I also just tried version 2.3.1 which I was using a year ago, and had the same issue with it. So it seems like a change in CM may have caused this weird behavior (tested with 1810).
Based on your Dell Command Update post you did a couple of years ago, I manage all my 12 Dell models and Lenovo 7 models using Dell Command Update & Dell Repository Manager for Dell and Lenovo Update retriever and Thininstaller for Lenovo and it works really well. I used to have it setup like you where I would just download the latest drivers and BIOS updates from Dell directly but I had multiple times where Dells ftp site went down so my SCCM builds would fail because it could not connect to the site. I use Dell Repository Manager now to make a local repository so I can manage what drivers get installed and I am now not dependant on Dell's website every time I build a machine. I would love to send you my documentation for how I manage my Dell and Lenovo machines and our process. you have helped me out many times over the 5 years of SCCM/desktop administration.