Skip to main content
OSDCloudGUI with ConfigMgr: Installing Updates
  1. Posts/

OSDCloudGUI with ConfigMgr: Installing Updates

·1781 words
Michael Escamilla
Author
Michael Escamilla
Table of Contents
Update June 12, 2024

These additional steps are no longer required with the addition of some SetupComplete features that will update windows using features built into OSDCloud. See my post below on utilizing the SetupComplete option:**


So, I think the next thing we should add to our OSDCloudGUI Task Sequence, is making sure the Operating System has the latest Cumulative Update.

Task Sequence Preview

Additional Information
#

The whole idea for this is based on @gwblok blog post below. Import his TS into your ConfigMgr and look through it. Lots of good stuff!

What are we going to do?
#

  • Determine what Operating System was installed
  • Download Updates for that OS
  • Apply the Updates
  • Cleanup our Downloaded Files

Determine what Operating System was installed
#

  1. Get the ‘UBR’ (Update Build Revision)
    • This is the full Build number of the image Eg: ‘10.0.22621.2283’

PowerShell Script

PowerShell Script

TSVariable

TSVariable

Using DISM, lets get the Image Version from “C:\”

# Get OS Information
$OSInfo = DISM.exe /image:c:\ /Get-CurrentEdition
$OSInfoUBR = ($OSInfo | Where-Object { $_ -match "Image Version" }).replace("Image Version: ","")
$OSInfoUBR

This will output That OS UBR to a Task sequence variable named: 'OSInfoUBR'

We will use this Variable later and Convert the Information to more normalized naming.

  1. Convert ‘UBR’ to ‘OSName’ and ‘OSBuildName’
    • We want ‘OSName’ to be like ‘Windows 11’ or ‘Windows 10’
    • And ‘OSBuildName’ needs to be like ‘22H2’ or ‘21H2’
    • These will be used later against the Get-WSUSXML command
Dynamic Variables
Dynamic Variables Zoomed

What it looks like in the Task Sequence

Get UBR
Get UBR
Set OS Info
Set ‘OSName’ and ‘OSBuildName’

Download Updates
#

  1. Set a location to download updates
    • Save the Download path to a TS Variable
    • Helps with Updating it later if you want it somewhere else
Set Download Location
  1. Download Updates
-OSName '%OSName%' -OSBuildName '%OSBuildName%' -DownloadPath '%OSDCloudDownloadPath%'
PowerShell Script
PowerShell Script
Script Parameters
Script Parameters

Below is the full script for this TS Step

param (
    [string]$OSName,
    [string]$OSBuildName,
    [string]$DownloadPath
)

<#
    Took these Functions from @gwblock
    https://garytown.com/osdcloud-configmgr-integrated-win11-osd
#>
# Creates a TSProgressUI Variable for the script
function Confirm-TSProgressUISetup() {
    if ($null -eq $Script:TaskSequenceProgressUi) {
        try {
            $Script:TaskSequenceProgressUi = New-Object -ComObject Microsoft.SMS.TSProgressUI 
        }
        catch {
            throw "Unable to connect to the Task Sequence Progress UI! Please verify you are in a running Task Sequence Environment. Please note: TSProgressUI cannot be loaded during a prestart command.`n`nErrorDetails:`n$_"
        }
    }
}
# Gets the Task Sequence Environment information
function Confirm-TSEnvironmentSetup() {
    if ($null -eq $Script:TaskSequenceEnvironment) {
        try {
            $Script:TaskSequenceEnvironment = New-Object -ComObject Microsoft.SMS.TSEnvironment 
        }
        catch {
            throw "Unable to connect to the Task Sequence Environment! Please verify you are in a running Task Sequence Environment.`n`nErrorDetails:`n$_"
        }
    }
}
# Update the TSProgressUI
function Show-TSActionProgress() {

    param(
        [Parameter(Mandatory = $true)]
        [string] $Message,
        [Parameter(Mandatory = $true)]
        [long] $Step,
        [Parameter(Mandatory = $true)]
        [long] $MaxStep
    )
    # Create TsProgressUI Script:Variable
    Confirm-TSProgressUISetup
    # Gets TS Environment Info
    Confirm-TSEnvironmentSetup

    # Update the Progress UI
    $Script:TaskSequenceProgressUi.ShowActionProgress(`
            $Script:TaskSequenceEnvironment.Value("_SMSTSOrgName"), `
            $Script:TaskSequenceEnvironment.Value("_SMSTSPackageName"), `
            $Script:TaskSequenceEnvironment.Value("_SMSTSCustomProgressDialogMessage"), `
            $Script:TaskSequenceEnvironment.Value("_SMSTSCurrentActionName"), `
            [Convert]::ToUInt32($Script:TaskSequenceEnvironment.Value("_SMSTSNextInstructionPointer")), `
            [Convert]::ToUInt32($Script:TaskSequenceEnvironment.Value("_SMSTSInstructionTableSize")), `
            $Message, `
            $Step, `
            $MaxStep)
}

# Import Module
Show-TSActionProgress -Message "Importing 'OSD' PSModule" -Step 1 -MaxStep 2
Import-Module OSD -Force

# Get Updates Information
Show-TSActionProgress -Message "Finding Updates for: [$($OSName) $($OSBuildName)]" -Step 1 -MaxStep 2
$Updates = Get-WSUSXML -Catalog "$($OSName)" -UpdateBuild "$($OSBuildName)" -UpdateArch x64 | Where-Object { $_.UpdateGroup -eq 'LCU' -or $_.UpdateGroup -eq 'Optional' }
Start-Sleep -Seconds 5
Show-TSActionProgress -Message "Updates Found: [ $($Updates.Count) ]" -Step 2 -MaxStep 2
Start-Sleep -Seconds 5

# Download Updates
[long]$StepCount = 0
Show-TSActionProgress -Message "Downloading Updates" -Step $StepCount -MaxStep $($Updates.Count + 1)
foreach ($Update in $Updates) {
    $StepCount++
    Show-TSActionProgress -Message "Downloading Update: [$($Update.Title)]" -Step $StepCount -MaxStep ($Updates.Count + 1)
    Save-WebFile -SourceUrl $Update.FileUri -DestinationDirectory "$($DownloadPath)" -DestinationName $Update.FileName -Verbose
    Start-Sleep -Seconds 5
}

The two main parts of the script are:

  1. Searching for Updates
    • This uses the Get-WSUSXML command that is part of the OSD Module
    • Then filter that list so that it is only Cumulative Updates and Optional
# Get Updates Information
Show-TSActionProgress -Message "Finding Updates for: [$($OSName) $($OSBuildName)]" -Step 1 -MaxStep 2
$Updates = Get-WSUSXML -Catalog "$($OSName)" -UpdateBuild "$($OSBuildName)" -UpdateArch x64 | Where-Object { $_.UpdateGroup -eq 'LCU' -or $_.UpdateGroup -eq 'Optional' }
Start-Sleep -Seconds 5
Show-TSActionProgress -Message "Updates Found: [ $($Updates.Count) ]" -Step 2 -MaxStep 2
Start-Sleep -Seconds 5
  1. Downloading the Updates
    • This uses the Save-WebFile command that is part of the OSD Module
    • We loop through all the Updates that were found, and Save them to the DownloadPath
# Download Updates
[long]$StepCount = 0
Show-TSActionProgress -Message "Downloading Updates" -Step $StepCount -MaxStep $($Updates.Count + 1)
foreach ($Update in $Updates) {
    $StepCount++
    Show-TSActionProgress -Message "Downloading Update: [$($Update.Title)]" -Step $StepCount -MaxStep ($Updates.Count + 1)
    Save-WebFile -SourceUrl $Update.FileUri -DestinationDirectory "$($DownloadPath)" -DestinationName $Update.FileName -Verbose
    Start-Sleep -Seconds 5
}

What it looks like in the Task Sequence:

Set Download Path
Set Download Path
Import ‘OSD’ Module
Import ‘OSD’ Module
Search for Updates
Search for Updates
Confirm how many Found
Confirm how many Found
Download Updates
Download Updates

Install Updates
#

  • This PowerShell script takes the TS Variable ‘OSDCloudDownloadPath’ and attempts to install all ‘files’ in that path
-DownloadPath '%OSDCloudDownloadPath%'
PowerShell Script
PowerShell Script
Script Parameters
Script Parameters

Below is the full script for this TS Step

param (
    [string]$DownloadPath
)

<#
    Took these Functions from @gwblock
    https://garytown.com/osdcloud-configmgr-integrated-win11-osd
#>
# Creates a TSProgressUI Variable for the script
function Confirm-TSProgressUISetup() {
    if ($null -eq $Script:TaskSequenceProgressUi) {
        try {
            $Script:TaskSequenceProgressUi = New-Object -ComObject Microsoft.SMS.TSProgressUI 
        }
        catch {
            throw "Unable to connect to the Task Sequence Progress UI! Please verify you are in a running Task Sequence Environment. Please note: TSProgressUI cannot be loaded during a prestart command.`n`nErrorDetails:`n$_"
        }
    }
}
# Gets the Task Sequence Environment information
function Confirm-TSEnvironmentSetup() {
    if ($null -eq $Script:TaskSequenceEnvironment) {
        try {
            $Script:TaskSequenceEnvironment = New-Object -ComObject Microsoft.SMS.TSEnvironment 
        }
        catch {
            throw "Unable to connect to the Task Sequence Environment! Please verify you are in a running Task Sequence Environment.`n`nErrorDetails:`n$_"
        }
    }
}
# Update the TSProgressUI
function Show-TSActionProgress() {

    param(
        [Parameter(Mandatory = $true)]
        [string] $Message,
        [Parameter(Mandatory = $true)]
        [long] $Step,
        [Parameter(Mandatory = $true)]
        [long] $MaxStep
    )
    # Create TsProgressUI Script:Variable
    Confirm-TSProgressUISetup
    # Gets TS Environment Info
    Confirm-TSEnvironmentSetup

    # Update the Progress UI
    $Script:TaskSequenceProgressUi.ShowActionProgress(`
            $Script:TaskSequenceEnvironment.Value("_SMSTSOrgName"), `
            $Script:TaskSequenceEnvironment.Value("_SMSTSPackageName"), `
            $Script:TaskSequenceEnvironment.Value("_SMSTSCustomProgressDialogMessage"), `
            $Script:TaskSequenceEnvironment.Value("_SMSTSCurrentActionName"), `
            [Convert]::ToUInt32($Script:TaskSequenceEnvironment.Value("_SMSTSNextInstructionPointer")), `
            [Convert]::ToUInt32($Script:TaskSequenceEnvironment.Value("_SMSTSInstructionTableSize")), `
            $Message, `
            $Step, `
            $MaxStep)
}

# Get Update files
Show-TSActionProgress -Message "Finding Update files in Directory: [$($DownloadPath)]" -Step 1 -MaxStep 2
Write-Host "Finding Update files in Directory: [$($DownloadPath)]"
$UpdateFiles = Get-ChildItem -Path "$($DownloadPath)" | Where-Object { $_.PSIsContainer -eq $false }
Show-TSActionProgress -Message "Successfully Found: [$($UpdateFiles.Count)] Updates" -Step 2 -MaxStep 2

# Install Updates
Show-TSActionProgress -Message "Installing Updates" -Step 1 -MaxStep $($UpdateFiles.Count + 1)
# Set Scratch Directory
$ScratchDir = "$($DownloadPath)\_Update-Scratch"
# Create Directory if it doesn't Exists
if (!(Test-Path -Path $ScratchDir)) {
    New-Item -Path $ScratchDir -ItemType Directory -Force | Out-Null -Verbose
}
# Install Updates
[long]$StepCount = 0
Show-TSActionProgress -Message "Installing Updates" -Step $StepCount -MaxStep $($Updates.Count + 1)
foreach ($Update in $UpdateFiles) {
    $StepCount++
    Write-Host "Installing Update: [$($Update.FullName)]"
    Show-TSActionProgress -Message "Installing Update: [$($Update.Name)]" -Step $StepCount -MaxStep ($UpdateFiles.Count + 1)
    Add-WindowsPackage -Path "C:\" -PackagePath "$($Update.FullName)" -NoRestart -ScratchDirectory "$($ScratchDir)" -Verbose
    Start-Sleep -Seconds 5
}

The three main parts of the script are:

  1. Get Downloaded Update Files
    • Gets all ‘files’ in the root of the ‘DownloadPath’
# Get Update files
Show-TSActionProgress -Message "Finding Update files in Directory: [$($DownloadPath)]" -Step 1 -MaxStep 2
Write-Host "Finding Update files in Directory: [$($DownloadPath)]"
$UpdateFiles = Get-ChildItem -Path "$($DownloadPath)" | Where-Object { $_.PSIsContainer -eq $false }
Show-TSActionProgress -Message "Successfully Found: [$($UpdateFiles.Count)] Updates" -Step 2 -MaxStep 2
  1. Create Scratch Directory
# Set Scratch Directory
$ScratchDir = "$($DownloadPath)\_Update-Scratch"
# Create Directory if it doesn't Exists
if (!(Test-Path -Path $ScratchDir)) {
    New-Item -Path $ScratchDir -ItemType Directory -Force | Out-Null -Verbose
}
  1. Install Updates
    • Loop through all the Updates Files found in part 1
    • Uses Add-WindowsPackage to apply Update
# Install Updates
[long]$StepCount = 0
Show-TSActionProgress -Message "Installing Updates" -Step $StepCount -MaxStep $($Updates.Count + 1)
foreach ($Update in $UpdateFiles) {
    $StepCount++
    Write-Host "Installing Update: [$($Update.FullName)]"
    Show-TSActionProgress -Message "Installing Update: [$($Update.Name)]" -Step $StepCount -MaxStep ($UpdateFiles.Count + 1)
    Add-WindowsPackage -Path "C:\" -PackagePath "$($Update.FullName)" -NoRestart -ScratchDirectory "$($ScratchDir)" -Verbose
    Start-Sleep -Seconds 5
}

What it looks like in the Task Sequence:

Find Files in Download Path
Find Files in Download Path
Install Update
Install Update
Install Next Update
Install Next Update

Cleanup
#

  • This PowerShell script takes the TS Variable ‘OSDCloudDownloadPath’ and deletes the whole directory
-DownloadPath '%OSDCloudDownloadPath%'
PowerShell Script
PowerShell Script
PowerShell Script Parameters
PowerShell Script Parameters

Below is the full script for this TS Step

param (
    [string]$DownloadPath
)

<#
    Took these Functions from @gwblock
    https://garytown.com/osdcloud-configmgr-integrated-win11-osd
#>
# Creates a TSProgressUI Variable for the script
function Confirm-TSProgressUISetup() {
    if ($null -eq $Script:TaskSequenceProgressUi) {
        try {
            $Script:TaskSequenceProgressUi = New-Object -ComObject Microsoft.SMS.TSProgressUI 
        }
        catch {
            throw "Unable to connect to the Task Sequence Progress UI! Please verify you are in a running Task Sequence Environment. Please note: TSProgressUI cannot be loaded during a prestart command.`n`nErrorDetails:`n$_"
        }
    }
}
# Gets the Task Sequence Environment information
function Confirm-TSEnvironmentSetup() {
    if ($null -eq $Script:TaskSequenceEnvironment) {
        try {
            $Script:TaskSequenceEnvironment = New-Object -ComObject Microsoft.SMS.TSEnvironment 
        }
        catch {
            throw "Unable to connect to the Task Sequence Environment! Please verify you are in a running Task Sequence Environment.`n`nErrorDetails:`n$_"
        }
    }
}
# Update the TSProgressUI
function Show-TSActionProgress() {

    param(
        [Parameter(Mandatory = $true)]
        [string] $Message,
        [Parameter(Mandatory = $true)]
        [long] $Step,
        [Parameter(Mandatory = $true)]
        [long] $MaxStep
    )
    # Create TsProgressUI Script:Variable
    Confirm-TSProgressUISetup
    # Gets TS Environment Info
    Confirm-TSEnvironmentSetup

    # Update the Progress UI
    $Script:TaskSequenceProgressUi.ShowActionProgress(`
            $Script:TaskSequenceEnvironment.Value("_SMSTSOrgName"), `
            $Script:TaskSequenceEnvironment.Value("_SMSTSPackageName"), `
            $Script:TaskSequenceEnvironment.Value("_SMSTSCustomProgressDialogMessage"), `
            $Script:TaskSequenceEnvironment.Value("_SMSTSCurrentActionName"), `
            [Convert]::ToUInt32($Script:TaskSequenceEnvironment.Value("_SMSTSNextInstructionPointer")), `
            [Convert]::ToUInt32($Script:TaskSequenceEnvironment.Value("_SMSTSInstructionTableSize")), `
            $Message, `
            $Step, `
            $MaxStep)
}

# Delete Download Path
Show-TSActionProgress -Message "Deleting Download Path: [$($DownloadPath)]" -Step 1 -MaxStep 2
Remove-Item -Path "$($DownloadPath)" -Recurse -Force -Verbose

# Verify Download Path is Deleted
Show-TSActionProgress -Message "Verifying Download Path has been Delted" -Step 2 -MaxStep 3
if (Test-Path -Path "$($DownloadPath)") {
    Show-TSActionProgress -Message "Successfully Deleted Download Path" -Step 3 -MaxStep 3
}

The only main part of the script is:

  1. Delete Download Path
    • Delete the whole DownloadPath directory
# Delete Download Path
Show-TSActionProgress -Message "Deleting Download Path: [$($DownloadPath)]" -Step 1 -MaxStep 2
Remove-Item -Path "$($DownloadPath)" -Recurse -Force -Verbose

What it looks like in the Task Sequence:

Delete Download Path
Delete Download Path

Verify Path was Deleted
Verify Path was Deleted

Success!
#

Now your OSDCloud installed OS will be update to date with the latest Cumulative Update at first boot.

I’m not sure if this is the best way to do it, but the goal was to make it dynamic to whatever OS was installed using OSDCloud. Hopefully this helps anyone looking to do something similar.

Download the Full Task Sequence

Video of the full install below: