This is an evolving post and is related to a project aiming to automate Software Updates in our environment.
The environment is a little complex as we have to work with other teams to get the list of Bulletin-IDs of S/W Updates (or patches) which will be deployed to our environment. So we can't probably go for ADR in CM 2012.
So we will look at automating this Patching scenario from the very beginning where we get a list of Bulletin-ID's from outside source.
Note - This post is inspired by Steve Rachui's post on Automating Software Updates, where he has shown some very cool stuff (link at the end check that one out).
Thanks to Stephane >:D< for sharing his WMI Functions which helped me a lot, along with cool B-) people like Kaido,Peter & David who have shared there awesome work :). All the resources link are at the end of the page.
Below are the steps we will follow, which should get someone started who is looking to achieve the same:
One can also filter on a lot of parameters here like the Product, OS etc.
For my environment want to only select the patches applicable to Server OS (server 2008 R2 & Server 2012 R2 )
Above will give an out-gridview window for me to select the products am targeting...I will select Windows Server 2008 and Windows Server 2012 & 2012 R2
Now once I have the Products selected I will filter out all the earlier Software Updates I have that apply on these Products :
Now let's see what is there in the $DeployPatches which is our Final list of S/W updates which are going to be deployed:
Now the the UNC path where am storing all the downloaded patches looks like below :
Note - There are caveats around running the BitsTransfer as a job. I am testing it and once done will post it
The deployment package is created and now to tell it to where to get/add the patches from we will invoke a method on the WMI Object.
Using the old trick of asking PowerShell the Method Overloaddefinition (where I just dot reference the Method name) , you will get below:
So it tells need to pass an array of ContentIDs this deployment package will store and then the array of where these are stored (note it's an array) and then a Boolean value telling if we want to refresh DP's.
Below is how we can get this information. Note how using the ContenID as the folder name helps here to get the information we need:
Make sure that the number of elements in both the arrays are same ;)
Below is what it should be like:
Now ready to invoke the method :
If you have done everything correct this should give you something like below:
Meanwhile if you get any error or even success I urge you to take a look at the SMSProv.log which is your best friend if you are looking to automate ConfigMgr.
After all is done you can verify the same in the console too :
Deploy Package is created :
Have a look inside the Deployment Package...Updates do reflect:
After all is done if we take a look back to the UNC path :
It needs cleanup period:
========================================================================
[UPDATE]
I am trying this in my environment and learning the challenges faced as I progress. The process am following for this might not be the prefect one but I would love to take any inputs/feedback.
P.S. - Have a very cool & awesome idea in my mind but will post about it when it's done.
========================================================================
Resources :
Automating Software Updates - Steve Rachui
http://blogs.msdn.com/b/steverac/archive/2014/06/12/automating-software-updates.aspx
S/W Update WMI Functions - Stephane Van Gulick
http://powershelldistrict.com/?p=340
Add Update Content to a deployment Package - Peter van der Woude
www.petervanderwoude.nl/post/add-update-content-to-a-deployment-package-via-powershell-in-configmgr-2012/
Create a New Software update group in ConfigMgr - David O'Brien
http://www.david-obrien.net/2012/12/02/create-a-new-software-update-group-in-configmgr/
ConfigMgr PowerShell SDK - Kaido
http://cm12sdk.net/?page_id=10
The environment is a little complex as we have to work with other teams to get the list of Bulletin-IDs of S/W Updates (or patches) which will be deployed to our environment. So we can't probably go for ADR in CM 2012.
So we will look at automating this Patching scenario from the very beginning where we get a list of Bulletin-ID's from outside source.
Note - This post is inspired by Steve Rachui's post on Automating Software Updates, where he has shown some very cool stuff (link at the end check that one out).
Thanks to Stephane >:D< for sharing his WMI Functions which helped me a lot, along with cool B-) people like Kaido,Peter & David who have shared there awesome work :). All the resources link are at the end of the page.
Below are the steps we will follow, which should get someone started who is looking to achieve the same:
- Get list of patches to be deployed
- Create a Software Update Group
- Download the Software Updates
- Create deployment Package
- Deploy them
Note - that this is just groundwork for my project....so finer details like logging, error handling will be taken care of later. I went with the WMI way of doing this as encountered that the cmdlets shipped with CM are little buggy right now but if it suits you go ahead and try those too.
1. Get list (CI_ID) of Patches to be deployed
This is the first and it's important to put in the logic to only select the patches you are looking for.
There is a patchList.txt file containing the approved Bulletin-ID's which are to be deployed to the Machines.
Now we have list of all the S/W Updates as per the Bulletin-ID's , we need to filter out all the Software Updates applicable to my Environment (NumMissing property tells me on how many machines it is missing)
There is a patchList.txt file containing the approved Bulletin-ID's which are to be deployed to the Machines.
001
002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 |
# Use Splatting
$hash = @{ #open a CIM sesison to the ConfigMgr Server cimsession = New-CimSession -ComputerName dexsccm NameSpace = 'Root\SMS\Site_DEX' #point to the SMS Namespace ErrorAction = 'Stop' } $BulletinIDList = get-content C:\temp\BulletinIDList.txt # loop through the contents of the text file to build you a query for ( $i = 0 ; $i -lt $BulletinIDList.count;$i++) { if ($i -eq 0) { $query = "Select * from SMS_SoftwareUpdate WHERE (BulletinID='$($BulletinIDList[$i])')" } else { $query = $query + " OR (BulletinID='$($BulletinIDList[$i])')" } } #Get the Software Updates corresponding to the Bulletin-IDs $BulletinIDPatches = Get-CimInstance -Query $query @hash |
Now we have list of all the S/W Updates as per the Bulletin-ID's , we need to filter out all the Software Updates applicable to my Environment (NumMissing property tells me on how many machines it is missing)
001
002 |
#Filter out not Applicable patches
$BulletinIDPatches = $BulletinIDPatches | where NumMissing -ne 0 |
One can also filter on a lot of parameters here like the Product, OS etc.
For my environment want to only select the patches applicable to Server OS (server 2008 R2 & Server 2012 R2 )
001
002 |
#Select Products you are targeting
$productcategories = Get-CimInstance -ClassName SMS_UpdateCategoryInstance -Filter 'CategoryTypeName="Product"' | Out-GridView -Title "Slect the Products to target" -PassThru |
Above will give an out-gridview window for me to select the products am targeting...I will select Windows Server 2008 and Windows Server 2012 & 2012 R2
Now once I have the Products selected I will filter out all the earlier Software Updates I have that apply on these Products :
001
002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 |
# will use a HashTable for filtering later
$ProductIDHash = @{} #create a Hash Table used to filter out later $productcategories | ForEach-Object -Process {$ProductIDHash.Add("$($($_.CategoryInstance_UniqueID) -replace 'Product:')","$($_.LocalizedCategoryInstanceName)")} #Filter out S/W updates which do not apply to the Products we want $DeployPatches = $BulletinIDPatches | ForEach-Object -Process { # Check if the Software Update is applicable to the list of Products we selected and doesn't target Itanium Architecture if ( $ProductIDHash.Contains($([System.Xml.XmlDocument]$_.ApplicabilityCondition).ApplicabilityRule.ProductId) -and ( $_.LocalizedDisplayName -notlike "*Itanium*") ) { # Adding a note property to the S/W update Object in Pipeline to know which product it applies to $_ | Add-Member -MemberType NoteProperty -Name AppliesTo -Value "$($ProductIDHash.$($([System.Xml.XmlDocument]$_.ApplicabilityCondition).ApplicabilityRule.ProductId))" Write-Output -InputObject $_ } } |
Now let's see what is there in the $DeployPatches which is our Final list of S/W updates which are going to be deployed:
PS> $DeployPatches | format-list -Property localizeddisplayname,appliesto localizeddisplayname : Security Update for Windows Server 2008 R2 x64 Edition (KB2957503) AppliesTo : Windows Server 2008 R2 localizeddisplayname : Security Update for Windows Server 2008 R2 x64 Edition (KB2957189) AppliesTo : Windows Server 2008 R2 localizeddisplayname : Security Update for Windows Server 2008 R2 x64 Edition (KB2939576) AppliesTo : Windows Server 2008 R2 localizeddisplayname : Cumulative Security Update for Internet Explorer 10 for Windows Server 2008 R2 Service Pack 1 for x64-based Systems (KB2957689) AppliesTo : Windows Server 2008 R2 localizeddisplayname : Security Update for Windows Server 2008 R2 x64 Edition (KB2957509) AppliesTo : Windows Server 2008 R2
Note that In order to Script the entire solution we will have to work with CI_ID as the SDK documentation states :
The CI_ID value identifies the software updates information across several classes. For the purposes of using the Configuration Manager SDK interfaces, the CI_ID value is vital
2. Create a Software Update Group
There is a cmdlet in the Configuration Manager Module by the name New-CMSoftwareUpdateGroup , but I wanted to stick to the WMI way of doing this.
We have to create an instance of the Class SMS_AuthorizationList and for this we will have to switch back to WMI ....can't be done using CIM :(
There is an post by David O'Brien which covers this very topic...link at the end.
So after this if you head to the ConfigMgr Console you will see the Update Group created along with the appropriate Updates inside that.
Software Updates inside above Update Group
Note - Compare this with the PowerShell Console Output of $DeployPatches | format-list -Property localizeddisplayname,appliesto
We have to create an instance of the Class SMS_AuthorizationList and for this we will have to switch back to WMI ....can't be done using CIM :(
001
002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 |
#region create Software Update Group (SMS_AuthorizationList)
#prepare the Default Paramater Values to work with Get-WMIObject $PSDefaultParameterValues =@{"get-wmiobject:namespace"="Root\SMS\site_DEX";"get-WMIObject:computername"="DexSCCM"} #Get a reference to the WMI Class SMS_CI_LocalizedProperties $class = Get-WmiObject -Class SMS_CI_LocalizedProperties -list #instantiate the Class $LocalizedProperties = $class.CreateInstance() $LocalizedProperties.DisplayName="TEST Software Update Group" $LocalizedProperties.Description="Testing PS Automation" $class = Get-WmiObject -Class SMS_AuthorizationList -list $UpdateGroup = $class.CreateInstance() $UpdateGroup.LocalizedInformation = $LocalizedProperties $UpdateGroup.Updates = $DeployPatches | select -ExpandProperty CI_ID $UpdateGroup.put() $UpdateGroup.get() #endregion create Software Update Group (SMS_AuthorizationList) |
There is an post by David O'Brien which covers this very topic...link at the end.
So after this if you head to the ConfigMgr Console you will see the Update Group created along with the appropriate Updates inside that.
Software Updates inside above Update Group
Note - Compare this with the PowerShell Console Output of $DeployPatches | format-list -Property localizeddisplayname,appliesto
3. Download the Software Updates
Now need to download all the Software Updates in a temporary location, we do this in a UNC path. So we create a new PSDrive pointing to that location. Then we use the relation between the classes SMS_SoftwareUpdate , SMS_CIToContent and SMS_CIContentFiles described in the SDK to get the URL for the download.
Then later BITS is used to download the patches..right now I am doing this in my own LAB and will try this later in the QA and add support to handle failed downloads cause of network bandwidth costs.
Will be using the ContentID to create a folder where the corresponding patch will be downloaded. You will see in next section why I need to do this.
Then later BITS is used to download the patches..right now I am doing this in my own LAB and will try this later in the QA and add support to handle failed downloads cause of network bandwidth costs.
Will be using the ContentID to create a folder where the corresponding patch will be downloaded. You will see in next section why I need to do this.
001
002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 |
#region Download the Software Updates
#Create a new PSDrive where the Patches will be downloaded New-PSDrive -Name M -PSProvider FileSystem -Root "\\dexsccm\patches\2014" $downloadpath = "M:" $DownloadInfo =foreach ($CI_ID in $UpdateGroup.Updates) { $contentID = Get-CimInstance -Query "Select ContentID,ContentUniqueID,ContentLocales from SMS_CITOContent Where CI_ID='$CI_ID'" @hash #Filter out the English Local and ContentID's not targeted to a particular Language $contentID = $contentID | Where {($_.ContentLocales -eq "Locale:9") -or ($_.ContentLocales -eq "Locale:0") } foreach ($id in $contentID) { $contentFile = Get-CimInstance -Query "Select FileName,SourceURL from SMS_CIContentfiles WHERE ContentID='$($ID.ContentID)'" @hash [pscustomobject]@{Source = $contentFile.SourceURL ; Destination = "$downloadpath\$($id.ContentID)\$($contentFile.FileName)"; } } } #test and create the Destination Folders if needed $DownloadInfo.destination | ForEach-Object -Process { If (! (test-path -Path "filesystem::$(Split-Path -Path $_)")) { New-Item -ItemType directory -Path "$(Split-Path -Path $_)" } } $DownloadInfo | Start-BitsTransfer #endregion Download the Software Updates |
Now the the UNC path where am storing all the downloaded patches looks like below :
Note - There are caveats around running the BitsTransfer as a job. I am testing it and once done will post it
4. Create Deployment Package
Need to create the deployment package which will eventually get distributed to the Distribution Points and then deployed to the Clients.
001
002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 |
#region Create the Deployment Package
#Get the Class $class = Get-WmiObject -Class SMS_SoftwareUpdatesPackage -List #Instantiate the Class Object $DeployPackage = $class.CreateInstance() #set the appropriate properties on the Instance $DeployPackage.Name = "Test Deploy Package" $DeployPackage.SourceSite = "DEX" $DeployPackage.PkgSourcePath = "\\dexsccm\patches\2014" $DeployPackage.Description = "testing PS Automation" $DeployPackage.PkgSourceFlag = [int32]2 #Value of 2 -->Stores Software Updates #persist the changes $DeployPackage.put() #get the latest WMI Instance back $DeployPackage.get() #endregion Create the Deployment Package |
Using the old trick of asking PowerShell the Method Overloaddefinition (where I just dot reference the Method name) , you will get below:
PS> $DeployPackage.AddUpdateContent OverloadDefinitions ------------------- System.Management.ManagementBaseObject AddUpdateContent(System.UInt32[] ContentIDs, System.String[] ContentSourcePath, System.Boolean bRefreshDPs)
So it tells need to pass an array of ContentIDs this deployment package will store and then the array of where these are stored (note it's an array) and then a Boolean value telling if we want to refresh DP's.
Below is how we can get this information. Note how using the ContenID as the folder name helps here to get the information we need:
001
002 003 004 005 006 |
#get the Array of content source path
$contentsourcepath = Get-ChildItem -path 'M:' | select -ExpandProperty Fullname #get the array of ContentIDs $allContentIDs = $contentsourcepath | foreach {Split-Path -Path $_ -Leaf} |
Make sure that the number of elements in both the arrays are same ;)
Below is what it should be like:
Now ready to invoke the method :
001
002 003 |
# call the Method...May the force be with me ;)
$DeployPackage.AddUpdateContent($allContentIDs,$contentsourcepath,$true) |
Meanwhile if you get any error or even success I urge you to take a look at the SMSProv.log which is your best friend if you are looking to automate ConfigMgr.
After all is done you can verify the same in the console too :
Deploy Package is created :
Have a look inside the Deployment Package...Updates do reflect:
After all is done if we take a look back to the UNC path :
It needs cleanup period:
001
002 003 |
#final cleanup
$DownloadInfo.destination | ForEach-Object -Process { Remove-Item -Path (split-path -path $_) -Recurse -verbose} |
5. Deploy them
Leaving this for now one can achieve this either using WMI or the Cmdlet.
CM Cmdlet is Start-CMSoftwareUpdateDeployment.
The WMI Class you would be interested in is SMS_UpdatesAssignment
Go ahead explore & automate <:-P
Go ahead explore & automate <:-P
========================================================================
[UPDATE]
I am trying this in my environment and learning the challenges faced as I progress. The process am following for this might not be the prefect one but I would love to take any inputs/feedback.
P.S. - Have a very cool & awesome idea in my mind but will post about it when it's done.
========================================================================
Resources :
Automating Software Updates - Steve Rachui
http://blogs.msdn.com/b/steverac/archive/2014/06/12/automating-software-updates.aspx
S/W Update WMI Functions - Stephane Van Gulick
http://powershelldistrict.com/?p=340
Add Update Content to a deployment Package - Peter van der Woude
www.petervanderwoude.nl/post/add-update-content-to-a-deployment-package-via-powershell-in-configmgr-2012/
Create a New Software update group in ConfigMgr - David O'Brien
http://www.david-obrien.net/2012/12/02/create-a-new-software-update-group-in-configmgr/
ConfigMgr PowerShell SDK - Kaido
http://cm12sdk.net/?page_id=10