Script to install all updates in software center

<#
.SYNOPSIS
    Installs every missing software update currently visible in Software Center.

.DESCRIPTION
    Intended for use with Configuration Manager's Run Scripts feature. The
    script queries the local ConfigMgr Client SDK for deployed, applicable,
    missing updates that are visible in Software Center, then submits all of
    them to CCM_SoftwareUpdatesManager in one installation request.

    Installation continues asynchronously after this script finishes. Reboot
    and maintenance-window behavior remain controlled by the ConfigMgr
    deployment and client settings; this script does not force a reboot.

.NOTES
    Run context: Local System (the normal SCCM Run Scripts context)
    PowerShell: Windows PowerShell 5.1
#>

$ErrorActionPreference = 'Stop'

$Namespace     = 'root\ccm\ClientSDK'
$UpdateClass   = 'CCM_SoftwareUpdate'
$MaximumRows   = 25

$EvaluationStateNames = @{
    0  = 'None'
    1  = 'Available'
    2  = 'Submitted'
    3  = 'Detecting'
    4  = 'Pre-download'
    5  = 'Downloading'
    6  = 'Waiting to install'
    7  = 'Installing'
    8  = 'Pending soft reboot'
    9  = 'Pending hard reboot'
    10 = 'Waiting for reboot'
    11 = 'Verifying'
    12 = 'Install complete'
    13 = 'Error'
    14 = 'Waiting for service window'
    15 = 'Waiting for user logon'
    16 = 'Waiting for user logoff'
    17 = 'Waiting for job user logon'
    18 = 'Waiting for user reconnect'
    19 = 'Pending user logoff'
    20 = 'Pending update'
    21 = 'Waiting to retry'
    22 = 'Waiting for presentation mode'
    23 = 'Waiting for orchestration'
}

function ConvertTo-HexErrorCode {
    param(
        [Parameter(Mandatory = $true)]
        [UInt32]$Code
    )

    return ('0x{0:X8}' -f $Code)
}

function Get-DisplayName {
    param(
        [Parameter(Mandatory = $true)]
        [System.Management.ManagementBaseObject]$Update
    )

    $name = [string]$Update.Name
    $name = $name -replace '[\r\n\t]+', ' '

    if ($name.Length -gt 72) {
        $name = $name.Substring(0, 69) + '...'
    }

    return $name
}

try {
    # CCM_SoftwareUpdate enumerates deployed, applicable updates that are not
    # installed. ComplianceState 0 means missing; UserUIExperience means the
    # update is visible in Software Center.
    $updates = @(
        Get-WmiObject -Namespace $Namespace `
            -Query "SELECT * FROM $UpdateClass WHERE ComplianceState = 0 AND UserUIExperience = TRUE"
    )

    if ($updates.Count -eq 0) {
        Write-Output @"
Computer: $env:COMPUTERNAME
Status: No missing updates are currently listed in Software Center.
Updates submitted: 0
"@
        return
    }

    [System.Management.ManagementObject[]]$updateObjects = $updates
    $manager = [WmiClass]"\\.\${Namespace}:CCM_SoftwareUpdatesManager"
    $installResult = $manager.InstallUpdates($updateObjects)
    $returnCode = [UInt32]$installResult.ReturnValue

    if ($returnCode -ne 0) {
        throw "CCM_SoftwareUpdatesManager.InstallUpdates returned $(ConvertTo-HexErrorCode -Code $returnCode)."
    }

    # Give the ConfigMgr client a moment to change the initial evaluation state.
    Start-Sleep -Seconds 5

    $currentUpdates = @(
        Get-WmiObject -Namespace $Namespace -Class $UpdateClass
    )

    $currentById = @{}
    foreach ($currentUpdate in $currentUpdates) {
        $currentById[[string]$currentUpdate.UpdateID] = $currentUpdate
    }

    $lines = @()
    $lines += "Computer: $env:COMPUTERNAME"
    $lines += 'Status: Installation request submitted successfully.'
    $lines += "Updates submitted: $($updates.Count)"
    $lines += 'Reboot: Not forced by this script; ConfigMgr deployment settings apply.'
    $lines += ''
    $lines += ('{0,-12} {1,-28} {2,-5} {3}' -f 'KB', 'Initial state', 'Done', 'Update')
    $lines += ('-' * 122)

    $reported = 0
    foreach ($update in ($updates | Sort-Object ArticleID, Name)) {
        if ($reported -ge $MaximumRows) {
            break
        }

        $kb = if ([string]::IsNullOrWhiteSpace([string]$update.ArticleID)) {
            '-'
        }
        else {
            'KB' + [string]$update.ArticleID
        }

        $current = $currentById[[string]$update.UpdateID]
        if ($null -eq $current) {
            $stateName = 'Completed/not required'
            $percent = '100%'
        }
        else {
            $stateNumber = [Int32]$current.EvaluationState
            $stateName = if ($EvaluationStateNames.ContainsKey($stateNumber)) {
                $EvaluationStateNames[$stateNumber]
            }
            else {
                "Unknown ($stateNumber)"
            }

            $percent = '{0}%' -f [Int32]$current.PercentComplete

            if ([UInt32]$current.ErrorCode -ne 0) {
                $stateName = "$stateName / $(ConvertTo-HexErrorCode -Code ([UInt32]$current.ErrorCode))"
            }
        }

        if ($stateName.Length -gt 28) {
            $stateName = $stateName.Substring(0, 25) + '...'
        }

        $lines += ('{0,-12} {1,-28} {2,-5} {3}' -f `
            $kb, $stateName, $percent, (Get-DisplayName -Update $update))
        $reported++
    }

    if ($updates.Count -gt $MaximumRows) {
        $lines += ''
        $lines += "$($updates.Count - $MaximumRows) additional update(s) were submitted but omitted from this SCCM output."
    }

    Write-Output ($lines -join [Environment]::NewLine)
}
catch {
    $message = $_.Exception.Message -replace '[\r\n]+', ' '
    Write-Output @"
Computer: $env:COMPUTERNAME
Status: Failed to submit Software Center updates.
Error: $message
"@
    exit 1
}

Comments

Popular posts from this blog

Mastering Windows Service Management with NSSM (Non-Sucking Service Manager)

SCCM Task Sequence GUI - How to set up the TS to work with a GUI

Windows 10 Setup script - V 3.0.0.0