MSEndpointMgr

Create a Software Update Group defined by date in ConfigMgr with PowerShell

As a ConfigMgr administrator, one of the main functionality that many of us are working with is Software Updates. Over time, I’ve eventually decided to automate many of the tasks involved in managing Software Updates, like my console extension to automatically clean Software Update Groups which I’ve shared on this blog. Another tasks that I often have to perform is to combine a set of Software Update Groups into a single group, resulting in a better overview. An example would be to combine the Software Update Groups automatically created by an Automatic Deployment Rule for each month during 2015 into a single Software Update Group for all the Software Updates released (and deployed in your environment) in 2015.
You may be thinking that this can easily be done by creating a Saved search in the ConfigMgr console, executing that and simply just set the dates correctly. Although, you’re correct, I’ve seen many occasions where this is forgotten and it’s not being taken care of by the admins responsible. In my opinion, this particular tasks that you perform for instance once a year, is something that we could simply just automate.
To automate this whole process, there are several tasks that you’d need for a full automated workflow:

  • Scheduled task/runbook to execute a script
  • PowerShell script creating a Software Update Group
  • PowerShell script to deploy the newly created Software Update Group

In this post, I’ll be sharing the PowerShell script to create the Software Update Group, like explained in the steps above. I’ve chosen not to embed the last step in the code for this script, mainly due to that there’s not really any generic way that organizations configure their deployments. However, with the logic of creating the Software Update Group from the script in this post, and if you fully want to automate the whole process, you should be well on your way.

Script

Just like all of my scripts, I’ve uploaded it to my script repository on GitHub. In addition, you’ll also find the script below.

<#
.SYNOPSIS
    Create a Software Update Group containing Software Updates defined by Product and Update Classification between a range of years.
.DESCRIPTION
    This script will create a Software Update Group containing Software Updates defined by Product and Update Classification between a range of years.
.PARAMETER SiteServer
    Site server where the SMS Provider is installed
.PARAMETER Name
    Name of the Software Update Group.
.PARAMETER Products
    Define a set of Products.
.PARAMETER UpdateClassifications
    Define a set of Update Classifications.
.PARAMETER StartYear
    Set the start year from which released or revised patches will be evaluated against when adding to Software Update Group.
.PARAMETER EndYear
    Set the end year when released or revised patches will be evaluated against when adding to Software Update Group.
.EXAMPLE
    Create a Software Update Groups containing Security Updates and Critical Updates for Windows 7 and Windows 8.1 between 2014 and 2015:
    .\New-CMSoftwareUpdateGroupBetweenYear.ps1 -SiteServer CM01 -Name "SUM - Windows Clients - 2014-2015" -Products "Windows 7","Windows 8.1" -UpdateClassifications "Critical Updates","Security Updates" -StartYear 2014 -EndYear 2015 -Verbose
    Create a Software Update Groups containing Security Updates and Critical Updates for Windows Server 2012 R2 between 2015 and 2015:
    .\New-CMSoftwareUpdateGroupBetweenYear.ps1 -SiteServer CM01 -Name "SUM - Windows Clients - 2014-2015" -Products "Windows Server 2012 R2" -UpdateClassifications "Critical Updates","Security Updates" -StartYear 2015 -EndYear 2015 -Verbose
.NOTES
    FileName: New-CMSoftwareUpdateGroupBetweenYear.ps1
    Author:   Nickolaj Andersen
    Contact:  @NickolajA
    Created:  2016-03-22
    Updated:  2016-03-22
    Version:  1.0.0
#>
[CmdletBinding(SupportsShouldProcess=$true)]
param(
    

[parameter(Mandatory=$true, HelpMessage=”Site server where the SMS Provider is installed.”)]

[ValidateNotNullOrEmpty()] [ValidateScript({Test-Connection -ComputerName $_ -Count 1 -Quiet})] [string]$SiteServer,

[parameter(Mandatory=$true, HelpMessage=”Name of the Software Update Group.”)]

[ValidateNotNullOrEmpty()] [string]$Name,

[parameter(Mandatory=$true, HelpMessage=”Define a set of Products.”)]

[ValidateNotNullOrEmpty()] [ValidateSet(“Windows 7”, “Windows 8.1”, “Windows 10”, “Windows 10 LTSB”, “Windows Server 2008”, “Windows Server 2008 R2”, “Windows Server 2012”, “Windows Server 2012 R2”, “Office 2007”, “Office 2010”, “Office 2013”, “Office 2016”, “Office 365 Client”)] [string[]]$Products,

[parameter(Mandatory=$true, HelpMessage=”Define a set of Update Classifications.”)]

[ValidateNotNullOrEmpty()] [ValidateSet(“Critical Updates”, “Security Updates”, “Feature Packs”, “Updates”, “Update Rollups”)] [string[]]$UpdateClassifications,

[parameter(Mandatory=$true, HelpMessage=”Set the start year from which released or revised patches will be evaluated against when adding to Software Update Group.”)]

[ValidateNotNullOrEmpty()] [string]$StartYear,

[parameter(Mandatory=$true, HelpMessage=”Set the end year when released or revised patches will be evaluated against when adding to Software Update Group.”)]

[ValidateNotNullOrEmpty()] [string]$EndYear ) Begin { # Determine SiteCode from WMI try { Write-Verbose -Message “Determining Site Code for Site server: ‘$($SiteServer)'” $SiteCodeObjects = Get-WmiObject -Namespace “root\SMS” -Class SMS_ProviderLocation -ComputerName $SiteServer -ErrorAction Stop foreach ($SiteCodeObject in $SiteCodeObjects) { if ($SiteCodeObject.ProviderForLocalSite -eq $true) { $SiteCode = $SiteCodeObject.SiteCode Write-Verbose -Message “Site Code: $($SiteCode)” } } } catch [System.UnauthorizedAccessException] { Write-Warning -Message “Access denied” ; break } catch [System.Exception] { Write-Warning -Message “Unable to determine Site Code” ; break } } Process { # Functions function New-SoftwareUpdateGroupList { param(

[parameter(Mandatory=$true)]

[ValidateNotNullOrEmpty()] [string]$SoftwareUpdateGroupName,

[parameter(Mandatory=$false)]

[System.Collections.ArrayList]$UpdatesList ) # Create a new SMS_CI_LocalizedProperties instance (embedded object) $LocalizedProperties = ([WmiClass]”\\$($SiteServer)\root\SMS\site_$($SiteCode):SMS_CI_LocalizedProperties”).CreateInstance() $LocalizedProperties.DisplayName = $SoftwareUpdateGroupName $LocalizedProperties.Description = “Automatically generated by script” $LocalizedProperties.LocaleID = 1033 # Create a new SMS_AuthorizationList instance $AuthorizationListArguments = @{ LocalizedInformation = [array]$LocalizedProperties } try { Set-WmiInstance -Namespace “root\SMS\site_$($SiteCode)” -Class SMS_AuthorizationList -ComputerName $SiteServer -Arguments $AuthorizationListArguments -ErrorAction Stop | Out-Null Write-Verbose -Message “Successfully created ‘$($SoftwareUpdateGroupName)’ software update group” } catch [System.Exception] { Write-Warning -Message “Unable to create ‘$($SoftwareUpdateGroupName)’ software update group, breaking build operation. Line: $($_.InvocationInfo.ScriptLineNumber)” ; break } # Add list of CI_ID’s to Software Update Group $SoftwareUpdateGroup = Get-WmiObject -Namespace “root\SMS\site_$($SiteCode)” -Class SMS_AuthorizationList -ComputerName $SiteServer -Filter “LocalizedDisplayName like ‘$($SoftwareUpdateGroupName)'” -ErrorAction Stop if ($SoftwareUpdateGroup -ne $null) { $SoftwareUpdateGroup.Get() $SoftwareUpdateGroup.Updates = $UpdatesList $SoftwareUpdateGroup.Put() | Out-Null Write-Verbose -Message “Successfully added ‘$($UpdatesList.Count)’ software updates to ‘$($SoftwareUpdateGroupName)’ software update group” } } # Build Software Udates updates classification hash-table $UpdateClassificationsTable = @{} $UpdateClassificationObjects = Get-WmiObject -Namespace “root\SMS\site_$($SiteCode)” -Class SMS_CategoryInstance -Filter “CategoryTypeName like ‘UpdateClassification'” foreach ($UpdateClassificationObject in $UpdateClassificationObjects) { $UpdateClassificationsTable.Add($UpdateClassificationObject.LocalizedCategoryInstanceName, $UpdateClassificationObject.CategoryInstance_UniqueID) } # Build Software Udates products hash-table $ProductsTable = @{} $ProductObjects = Get-WmiObject -Namespace “root\SMS\site_$($SiteCode)” -Class SMS_CategoryInstance -Filter “CategoryTypeName like ‘Product’ AND LocalizedCategoryInstanceName not like ‘Windows Live’ AND LocalizedCategoryInstanceName not like ‘Visual Studio 2010 Tools for Office Runtime'” foreach ($ProductObject in $ProductObjects) { $ProductsTable.Add($ProductObject.LocalizedCategoryInstanceName, $ProductObject.CategoryInstance_UniqueID) } # Defince beginning of Software Updates query $SoftwareUpdatesQuery = “SELECT SMS_SoftwareUpdate.* FROM SMS_SoftwareUpdate WHERE (SMS_SoftwareUpdate.CI_ID NOT IN (SELECT CI_ID FROM SMS_CIAllCategories WHERE CategoryInstance_UniqueID=’UpdateClassification:3689bdc8-b205-4af4-8d4a-a63924c5e9d5′))” # Construct year part of query $SoftwareUpdatesQuery = -join @($SoftwareUpdatesQuery, ” AND (DateRevised >=’01/01/$($StartYear) 00:00:00′ AND DateRevised <=’12/31/$($EndYear) 00:00:00′ )”) # Construct expired updates part of query $SoftwareUpdatesQuery = -join @($SoftwareUpdatesQuery, ” AND (IsExpired =’0′ ) AND “) # Construct Products part of query $ProductsCount = 0 $SoftwareUpdatesQuery = -join @($SoftwareUpdatesQuery, “( “) foreach ($Product in $Products) { $ProductsCount++ if ($ProductsCount -lt $Products.Count) { $SoftwareUpdatesQuery = -join @($SoftwareUpdatesQuery, “CI_ID in (select CI_ID from SMS_CIAllCategories where CategoryInstance_UniqueID=’$($ProductsTable[$Product])’) OR “) } else { $SoftwareUpdatesQuery = -join @($SoftwareUpdatesQuery, “CI_ID in (select CI_ID from SMS_CIAllCategories where CategoryInstance_UniqueID=’$($ProductsTable[$Product])’)”) } } $SoftwareUpdatesQuery = -join @($SoftwareUpdatesQuery, ” )”) # Construct superseded part of query $SoftwareUpdatesQuery = -join @($SoftwareUpdatesQuery, ” AND (IsSuperseded =’0′ ) AND “) # Construct classification part of query $UpdateClassificationsCount = 0 $SoftwareUpdatesQuery = -join @($SoftwareUpdatesQuery, “( “) foreach ($UpdateClassification in $UpdateClassifications) { $UpdateClassificationsCount++ if ($UpdateClassificationsCount -lt $UpdateClassifications.Count) { $SoftwareUpdatesQuery = -join @($SoftwareUpdatesQuery, “CI_ID in (select CI_ID from SMS_CIAllCategories where CategoryInstance_UniqueID=’$($UpdateClassificationsTable[$UpdateClassification])’) OR “) } else { $SoftwareUpdatesQuery = -join @($SoftwareUpdatesQuery, “CI_ID in (select CI_ID from SMS_CIAllCategories where CategoryInstance_UniqueID=’$($UpdateClassificationsTable[$UpdateClassification])’)”) } } $SoftwareUpdatesQuery = -join @($SoftwareUpdatesQuery, ” )”) # Create Software Update Group $SoftwareUpdates = Get-WmiObject -Namespace “root\SMS\site_$($SiteCode)” -Query $SoftwareUpdatesQuery if ($SoftwareUpdates -ne $null) { New-SoftwareUpdateGroupList -SoftwareUpdateGroupName $Name -UpdatesList $SoftwareUpdates.CI_ID } else { Write-Warning -Message “Specified search for Software Updates between ‘$($StartYear)’ and ‘$($EndYear)’ did not return any Software Updates” } }

How to use the script

If you’ve paid attention to the script above, I’ve added validation to only allow a set of Products and Update Classifications in the param block. If you need any other items, simply just extend the ValidateSet() function. Executing the script is pretty straight forward, follow the steps below:
1. Open an elevated PowerShell prompt and browse to where you’ve saved the script.
2. Run the following command:

.\New-CMSoftwareUpdateGroupBetweenYear.ps1 -SiteServer CM01 -Name "SUM - Windows Clients - 2015" -Products "Windows 7","Windows 8.1" -UpdateClassifications "Critical Updates","Security Updates" -StartYear 2015 -EndYear 2015 -Verbose

190_2
I hope this helps on your journey for automating your everything.

Nickolaj Andersen

Chief Technical Architect and Enterprise Mobility MVP since 2016. Nickolaj has been in the IT industry for the past 10 years specializing in Enterprise Mobility and Security, Windows devices and deployments including automation. Awarded as PowerShell Hero in 2015 by the community for his script and tools contributions. Creator of ConfigMgr Prerequisites Tool, ConfigMgr OSD FrontEnd, ConfigMgr WebService to name a few. Frequent speaker at conferences such as Microsoft Ignite, NIC Conference and IT/Dev Connections including nordic user groups.

8 comments

  • HI Mate,
    its working like a charm,
    what If i have to create SUG for 2010 – 2014 which requires all update classification ?
    Is there any way by which only X64 updates can be included

  • Nickolaj,
    Thank you for once again sharing your fantastic work. I’m still a novice when it comes to Powershell, so it’s helpful to see how it should be done. I actually grabbed your entire Powershell GitHub folder, and look forward to incorporating pieces into some of our automation and orchestration processes. You are a gentleman and a scholar!

    • Hi Matt,
      I’m glad that you appreciate my work. Let me know if you run into any issues or need some help/advice on implementing some automation to ConfigMgr.
      Regards,
      Nickolaj

  • Hi,
    Thank you for the script. How would I make a group for all products that are synct by the SUP?
    Thanks,

Sponsors