Peaceful mountain landscape with clear reflections in a calm lake.

Automating Software Update Deployments in MECM with PowerShell

Introduction

This automates the process of identifying, packaging, and deploying updates in Microsoft Endpoint Configuration Manager (MECM). It analyzes missing updates, selects the top 10 most needed updates, creates a software update group and deployment package, and distributes the package to a specified distribution point.

Use Case

This is highly useful for:

  • Automated Patching: Streamlining the patching process in MECM, reducing manual effort and improving efficiency.
  • Targeted Updates: Deploying the most critical updates based on the number of computers needing them.
  • Simplified Management: Centralizing and automating software update deployments for better control and manageability.

Script

# Requires -RunAsAdministrator

$SiteCode = "<Your MECM Site Code>" 
$ProviderMachineName = "<Your MECM Provider Machine Name>" 
$CollectionName = "<Your MECM Collection Name>" 
$initParams = @{}
$CMPSSuppressFastNotUsedCheck = $true

function Write-Log {
    param($Message)
    $logMessage = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss'): $Message"
    Write-Host $logMessage -ForegroundColor Cyan
    Add-Content -Path "$($env:TEMP)\SCCM_Updates_$(Get-Date -Format 'yyyy-MM-dd').log" -Value $logMessage
}

Write-Log "Starting Update Analysis"

try {
    if(-not (Get-Module ConfigurationManager)) {
        Import-Module "$($ENV:SMS_ADMIN_UI_PATH)\..\ConfigurationManager.psd1" @initParams 
    }

    if(-not (Get-PSDrive -Name $SiteCode -PSProvider CMSite -ErrorAction SilentlyContinue)) {
        New-PSDrive -Name $SiteCode -PSProvider CMSite -Root $ProviderMachineName @initParams
    }

    Set-Location "$($SiteCode):\"
    Write-Log "Connected to SCCM"

    Write-Log "Analyzing needed updates..."
    
    # Get raw update count first
    $rawUpdates = Get-CMSoftwareUpdate
    Write-Log "Raw update count: $($rawUpdates.Count)"
    
    $allUpdates = $rawUpdates | Where-Object {
        $_.IsSuperseded -eq $false -and
        ($_.LocalizedDisplayName -like "*Security Update*" -or 
         $_.LocalizedDisplayName -like "*Cumulative Update*") -and
        ($_.LocalizedDisplayName -like "*Windows 10*" -or 
         $_.LocalizedDisplayName -like "*Windows 11*") -and
        $_.NumMissing -gt 0
    }

    if (-not $allUpdates) {
        Write-Log "No updates found matching criteria"
        return
    }

    Write-Log "Filtered update count: $($allUpdates.Count)"
    $allUpdates | Select-Object LocalizedDisplayName, IsSuperseded, NumMissing | Format-Table -AutoSize

    Write-Log "Found $($allUpdates.Count) candidate updates"

    # Debug section for update properties 
    Write-Log "Debug: Checking update properties..."
    $allUpdates | ForEach-Object { 
        Write-Log "Update: $($_.LocalizedDisplayName) Missing: $($_.NumMissing)"
    }

    Write-Log "Starting to sort updates..."
    try {
        $topUpdates = $allUpdates | Sort-Object {[int]$_.NumMissing} -Descending | Select-Object -First 10 
        Write-Log "Successfully sorted updates"
    }
    catch {
        Write-Log "Error sorting updates: $_"
        return
    }

    if (-not $topUpdates) {
        Write-Log "No updates after sorting"
        return
    }

    $timestamp = Get-Date -Format "HHmm"
    $packageName = "$(Get-Date -Format 'yyyy-MM-dd')-$timestamp Top 10 Most Needed Updates"
    $sourcePath = "\\<Your Network Share>\$packageName" 
    
    # Test network path access
    try {
        if (-not (Test-Path "\\<Your Network Share>")) { 
            Write-Log "Cannot access base network path"
            return
        }

        if (-not (Test-Path $sourcePath)) {
            New-Item -ItemType Directory -Path $sourcePath -Force
            Write-Log "Created package directory: $sourcePath"
        }
    }
    catch {
        Write-Log "Error accessing network path: $_"
        return
    }
    
    try {
        $SUGName = "$packageName SUG" 
        Write-Log "Creating Software Update Group: $SUGName"
        $newSUG = New-CMSoftwareUpdateGroup -Name $SUGName 
        Write-Log "Created Software Update Group: $SUGName"

        foreach ($update in $topUpdates) {
            if ($update.CI_ID) { 
                try {
                    Add-CMSoftwareUpdateToGroup -SoftwareUpdateGroupName $SUGName -SoftwareUpdateId $update.CI_ID 
                    Write-Log "Added: $($update.LocalizedDisplayName)"
                } catch {
                    Write-Log "Failed to add update: $($update.LocalizedDisplayName) - $_" 
                }
            }
            else {
                Write-Log "Skipping update with no CI_ID: $($update.LocalizedDisplayName)" 
            }
        }

        Write-Log "Creating deployment package..."
        $newPackage = New-CMSoftwareUpdateDeploymentPackage -Name $packageName -Path $sourcePath 
        if ($newPackage) {
            Write-Log "Created package ID: $($newPackage.PackageID)"
            
            Write-Log "Starting content distribution..."
            Start-CMContentDistribution -DeploymentPackageId $newPackage.PackageID `
                                        -DistributionPointName "$ProviderMachineName" 
            
            Write-Log "Creating deployment..."
            New-CMSoftwareUpdateDeployment -SoftwareUpdateGroupName $SUGName `
                                            -CollectionName $CollectionName `
                                            -DeploymentName "$packageName Deployment" `
                                            -DeploymentType Required `
                                            -TimeBasedOn LocalTime `
                                            -SendWakeupPacket $true `
                                            -VerbosityLevel AllMessages `
                                            -Duration 7 ` 
                                            -UserNotification DisplayAll `
                                            -SoftwareInstallation $true `
                                            -AllowRestart $false `
                                            -RequirePostRebootFullScan $true `
                                            -UpdateDeploymentPackageName $packageName 
            Write-Log "Deployment created successfully"

            $summaryPath = "$sourcePath\UpdateList.txt" 
            $topUpdates | ForEach-Object {
                @"
Title: $($_.LocalizedDisplayName)
Article ID: KB$($_.ArticleID)
Size: $([math]::Round($_.ContentSize/1MB, 2)) MB
Computers Needing: $($_.NumMissing)
----------------------------------------
"@ | Out-File -FilePath $summaryPath -Append 
            }
        }
        else {
            Write-Log "Failed to create deployment package" 
        }
    } catch {
        Write-Log "Deployment creation error: $_" 
    }
} catch {
    Write-Log "Script error: $_" 
}

Set-Location $env:SystemDrive
Write-Log "Process completed"

Explanation

  • Prerequisites: The script requires administrator privileges and defines variables for the MECM site code, provider machine name, and target collection.
  • Connect to MECM: It imports the ConfigurationManager module and establishes a connection to the MECM site.
  • Identify Missing Updates: The script retrieves all software updates, filters for relevant updates based on criteria (not superseded, security/cumulative updates for 10/11, missing on clients), and selects the top 10 updates based on the number of missing installations.
  • Create Software Update Group: A new software update group is created and the selected top 10 updates are added to it.
  • Create Deployment Package: A deployment package is created with the selected updates, and the content is distributed to the specified distribution point.
  • Deploy Updates: The script creates a deployment for the software update group, targeting the specified collection with configured settings for scheduling, user notifications, and installation behavior.
  • Logging: The script uses a Write-Log function to record progress and errors to a log file.
  • : try-catch blocks are used to handle potential errors during the process.

Outro

This script provides a comprehensive solution for automating software update deployments in MECM. By automating this process, administrators can ensure their systems are patched consistently and efficiently, reducing security risks and improving overall system stability.

Tags

PowerShell, MECM, SCCM, Software Updates, Patching, , Deployment, , Windows 10, Windows 11

Leave a Reply

Your email address will not be published. Required fields are marked *

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

WordPress Cookie Plugin by Real Cookie Banner