Dell Command Update via Task Sequence

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 (768 downloads)

image

What this Task Sequence Does (High Level):

  1. Checks for DCU and Installs if DCU is not installed then reboots
  2. Creates Folders for Logs & DCU Templates, the copies down my templates
  3. Creates Variables used later in reporting
  4. Check if BIOS Update Available
    1. Import BIOS / FIRMWARE DCU Template
    2. Runs DCU in Report Mode, generating a Report only if update exist
    3. Check if Report exist, if Yes, Run DCU BIOS Group
    4. if No Report, skip group to cleanup
  5. Update Group Runs
    1. Gets Info (Gather)
    2. Records Current BIOS info (for Reporting)
    3. Removes Current BIOS Password (CCTK)
    4. Suspend Bitlocker (if Enabled)
    5. Runs DCU to Update BIOS
    6. Adds BIOS Password back
    7. Reboots Machine
    8. Records new BIOS info (for Reporting)
    9. Enables Bitlocker (if Bitlocker is used)
  6. Clean Up
    1. Clears out the DCU Policy
  7. Check for Driver Updates
    1. Import Driver DCU Template
    2. Run DCU in Report Only mode
    3. if Report is generated run DCU Update group, else skip
  8. DCU Updates
    1. Run DCU and install all available Drivers
    2. Reboots
  9. DCU Cleanup
    1. Cleans up folders and files used
    2. Imports DCU Defaults Policy
    3. 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
    • Set TS Var "SMSTS_UpdateTime"
      powershell.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

      powershell.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
      cmd.exe /c if Not Exist "%programdata%\DCU\DCUSettings" (md %programdata%\DCU\DCUSettings)
    • Make Folder DCULogs for Logs
      cmd.exe /c if Not Exist "%programdata%\DCULogs" (md %programdata%\DCULogs)
    • Copy XML File Local
      Package = your package with the DCU Policy Setting XML files

      xcopy *.xml %programdata%\DCU\DCUSettings /Y
    • Set Var SetOSDInfoType = DCU
      • SetOSDInfoType = True
        Condition: SetOSDInfoType not exist
    • Set TS Var "SMSTS_Build" if not set
      • Condition: SMSTS_Build not exist
        powershell.exe -command "$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment; $tsenv.Value('SMSTS_Build') = Get-ItemPropertyValue 'HKLM:SOFTWARE\WOW6432Node\Microsoft\Windows NT\CurrentVersion' 'Releaseid'"
  • Run DCU BIOS
    • Set TS Var "SMSTS_BIOSStartTime"
      powershell.exe -command "&{$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment; $tsenv.Value('SMSTS_BIOSStartTime') = Get-Date -f 's'}"
  • Run DCU Report if BIOS upgrade available
    • Modify Dell DCU - Import Settings - BIOS
      cmd.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)
      cmd.exe /c start /wait C:\"Program Files (x86)"\Dell\CommandUpdate\dcu-cli.exe /report c:\ProgramData\DCU\DCUUpdatesBIOS.xml
    • Copy Report to Log Folder
      cmd.exe /c if Exist "%programdata%\DCU\DCUUpdatesBIOS.xml" (copy %programdata%\DCU\DCUUpdatesBIOS.xml %programdata%\DCULogs\DCUUpdatesBIOS%SMSTS_UpdateTime%.xml)
  • Run DCU if BIOS Updates exist (Condition: File: %programdata%\DCU\DCUUpdatesBIOS.xml Exist)
    • Gather Variables - Run PS Script from Gallery
    • Set TS Var CurrentBiosVer
      powershell.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
      cctk --setuppwd="" --valsetuppwd=%BIOSPWD%
    • Suspend Bitlocker (Condition TSVar IsBDE = True)
      Manage-bde.exe -protectors -disable c:
    • Stop OSDDoNotLogCommand: TSVar: OSDDoNotLogCommand = False
    • Run Dell Command Update Install BIOS
      "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
      cctk --setuppwd=%BIOSPWD%
    • Stop OSDDoNotLogCommand: TSVar: OSDDoNotLogCommand = False
    • Restart Computer
    • Set TS Var NewBiosVer
      powershell.exe -command "&{$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment; $tsenv.Value('NewBiosVer') = (Get-WmiObject win32_bios).SMBIOSBIOSVersion}"
    • Enable Bitlocker (Condition TSVar IsBDE = True)
      Manage-bde.exe -protectors -enable c:
  • DCU BIOS CleanUp (No Conditions on Group)
    • Clear DCU Policy
      reg delete HKLM\SOFTWARE\Dell\CommandUpdate\Preferences  /F
    • Set TS Var "SMSTS_BIOSFinishTime"
      powershell.exe -command "&{$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment; $tsenv.Value('SMSTS_BIOSFinishTime') = Get-Date -f 's'}"
  • Run DCU Drivers
    • Set TS Var "SMSTS_DriversStartTime"
      powershell.exe -command "&{$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment; $tsenv.Value('SMSTS_DriversStartTime') = Get-Date -f 's'}"
  • Run DCU Report if Drivers available
    • Modify Dell DCU - Import Settings -All drivers
      cmd.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)
      cmd.exe /c start /wait C:\"Program Files (x86)"\Dell\CommandUpdate\dcu-cli.exe /report c:\ProgramData\DCU\DCUUpdatesDrivers.xml
    • Copy Report to Log Folder
      cmd.exe /c if Exist "%programdata%\DCU\DCUUpdatesDrivers.xml" (copy %programdata%\DCU\DCUUpdatesDrivers.xml %programdata%\DCULogs\DCUUpdatesDrivers%SMSTS_UpdateTime%.xml)
  • Run DCU if Updates Exist (Condition :File: %programdata%\DCU\DCUUpdatesDrivers.xml exist)
    • Run Dell Command Update Install Driver Updates
      "C:\Program Files (x86)\Dell\CommandUpdate\dcu-cli.exe" /log c:\windows\ccm\logs /silent
    • Restart
  • DCU CleanUp
    • Set TS Var "SMSTS_DriversFinishTime"
      powershell.exe -command "&{$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment; $tsenv.Value('SMSTS_DriversFinishTime') = Get-Date -f 's'}"
    • Remove Folder ProgramData\DCU
      cmd.exe /c if Exist "%programdata%\DCU\" (rd %programdata%\DCU /S /Q)
    • Modify Dell DCU - Import Settings -Defaults
      cmd.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"
        <#
        .SYNOPSIS
        	Sets information during OSD / IPU for Dell BIOS & Drivers 
           
        .DESCRIPTION 
            This 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 Registry
            This switch will add information to the following location:
                - Registry
        
        .PARAMETER WMI
            This switch will add information to the following location:
                - WMI Repository
            
        .EXAMPLE
             Set-OSDInfo.ps1 -WMI -Registry
        
             Will add all information to the following locations:
                - Registry
                - WMI Repository 
        
        .NOTES
            Modified from the versions by Stephane van Gulick from www.powershellDistrict.com
        	This was completely borrowed from Jason Sandys SetOSDInfo script... it's my go to for this..
        .LINK
        	http://blog.configmgrftw.com
        	https://garytown.com for Modifications to Jason's Original Script
        
        .VERSION
            2019.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.log
        
        Function Get-WMINamespace
        {
          <#
        	.SYNOPSIS
        		Gets information about a specified WMI namespace.
        
        	.DESCRIPTION
        		Returns information about a specified WMI namespace.
        
            .PARAMETER  Namespace
        		Specify the name of the namespace where the class resides in (default is "root\cimv2").
        
        	.EXAMPLE
        		Get-WMINamespace
                Lists all WMI namespaces.
        
        	.EXAMPLE
        		Get-WMINamespace -Namespace cimv2
                Returns the cimv2 namespace.
        
        	.NOTES
        		Version: 1.0
        
        	.LINK
        		http://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
        {
        <#
        	.SYNOPSIS
        		This function creates a new WMI namespace.
        
        	.DESCRIPTION
        		The function creates a new WMI namespsace.
        
            .PARAMETER Namespace
        		Specify the name of the namespace that you would like to create.
        
        	.EXAMPLE
        		New-WMINamespace -Namespace "ITLocal"
                Creates a new namespace called "ITLocal"
        		
        	.NOTES
        		Version: 1.0
        
        	.LINK
        		http://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-null
        		
        		Write-Verbose "Namespace $($Namespace) created."
        
        	}
        	else
        	{
        		Write-Verbose "Namespace $($Namespace) is already present. Skipping.."
        	}
        }
        
        Function Get-WMIClass
        {
          <#
        	.SYNOPSIS
        		Gets information about a specified WMI class.
        
        	.DESCRIPTION
        		Returns the listing of a WMI class.
        
        	.PARAMETER  ClassName
        		Specify the name of the class that needs to be queried.
        
            .PARAMETER  Namespace
        		Specify the name of the namespace where the class resides in (default is "root\cimv2").
        
        	.EXAMPLE
        		get-wmiclass
                List all the Classes located in the root\cimv2 namespace (default location).
        
        	.EXAMPLE
        		get-wmiclass -classname win32_bios
                Returns the Win32_Bios class.
        
        	.EXAMPLE
        		get-wmiclass -Class MyCustomClass
                Returns information from MyCustomClass class located in the default namespace (root\cimv2).
        
            .EXAMPLE
        		Get-WMIClass -Namespace ccm -Class *
                List all the classes located in the root\ccm namespace
        
        	.EXAMPLE
        		Get-WMIClass -NameSpace ccm -Class ccm_client
                Returns information from the cm_client class located in the root\ccm namespace.
        
        	.NOTES
        		Version: 1.0
        
        	.LINK
        		http://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 $namespaceFullName
        		
        			if (!$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
        {
        <#
        	.SYNOPSIS
        		This function creates a new WMI class.
        
        	.DESCRIPTION
        		The function create a new WMI class in the specified namespace.
                It does not create a new namespace however.
        
        	.PARAMETER Class
        		Specify the name of the class that you would like to create.
        
            .PARAMETER Namespace
        		Specify the namespace where class the class should be created.
                If not specified, the class will automatically be created in "root\cimv2"
        
            .PARAMETER Attributes
        		Specify the attributes for the new class.
        
            .PARAMETER Key
        		Specify the names of the key attribute (or attributes) for the new class.
        
        	.EXAMPLE
        		New-WMIClass -ClassName "OSD_Info"
                Creates a new class called "OSD_Info"
            .EXAMPLE
                New-WMIClass -ClassName "OSD_Info1","OSD_Info2"
                Creates two classes called "OSD_Info1" and "OSD_Info2" in the root\cimv2 namespace
        
        	.NOTES
        		Version: 1.0
        
        	.LINK
        		http://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 = $Class
        
                foreach ($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-null
        			
        		Write-Verbose "Class $($Class) created."
        	}
        	else
        	{
        		Write-Verbose "Class $($Class) is already present. Skipping..."
            }
        
        }
        
        Function New-WMIClassInstance
        {
            <#
        	.SYNOPSIS
        		Creates a new WMI class instance.
        
        	.DESCRIPTION
        		The function creates a new instance of the specified WMI class.
        
        	.PARAMETER  Class
        		Specify the name of the class to create a new instance of.
        
        	.PARAMETER Namespace
                Specify the name of the namespace where the class is located (default is Root\cimv2).
        
        	.PARAMETER Attributes
                Specify the attributes and their values using PSVariables.
        
        	.EXAMPLE
                $MyNewInstance = New-WMIClassInstance -Class OSDInfo
                
                Creates a new instance of the WMI class "OSDInfo" and sets its attributes.
        		
        	.NOTES
        		Version: 1.0
        
        	.LINK
        		http://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
        {
        <#
        .SYNOPSIS
        	Sets a registry value in the specified key under HKLM\Software.
           
        .DESCRIPTION 
            Sets a registry value in the specified key under HKLM\Software.
        	
        	
        .PARAMETER Key
            Species the registry path under HKLM\SOFTWARE\ to create.
            Defaults to OperatingSystemDeployment.
        
        
        .PARAMETER ValueName
            This parameter specifies the name of the Value to set.
        
        .PARAMETER Value
            This parameter specifies the value to set.
            
        .Example
             New-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 node
                if (!(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 value
                write-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 $ID
        
        if ($tsenv)
        {
                #Get what kind of TS: CompatScan (CS) / InPlace Uprade (IPU) / Operating System Deployment (OSD) - Must have Step in TS that lets script know
                if ($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 $DifferenceBIOS
            New-Variable -Name "$($AttributePrefix)$($SetOSDInfoType)_DriverUpgradeTime" -Value $DifferenceDrivers
            if($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 $DifferenceBIOS
            New-Variable -Name "$($AttributePrefix)$($SetOSDInfoType)_DriverUpgradeTime" -Value $DifferenceDrivers
            if($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 $Namespace
            New-WMIClass -Namespace $Namespace -Class $Class -Attributes $customAttributes -Key $keyValue
            New-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
        
            }
        }

         

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:
image
It records before & after BIOS versions, along with the time taken to run the BIOS Update and the Driver Updates.

XML Reports:
image

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

2 thoughts on “Dell Command Update via Task Sequence”

  1. 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).

    Reply
  2. 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.

    Reply

Leave a Comment

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