SCVMM (System Center: Virtual Machine Manager) 2012 and 2012R2 can manage the patch compliance on your servers. That’s a great feature but normally involves some manual work as you have to add each update to the Baselines manually.
My colleague Mikael Nyström (MVP) made a script to handle this automatically, which I’ve developed a bit further.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
<# .Synopsis Script to automatically keep SCVMM Baselines in sync with WSUS .DESCRIPTION Script that synchronizes WSUS Updates with SCVMM, both adding new updates and removes old inactive updates. .EXAMPLE Update-BaseLineUpdates $Baselinename # Author Current Author, Markus Lassfolk @Truesec Original Author, Mikael Nyström @Truesec # Version 1.2 Markus Lassfolk - Added section to remove inactive updates # Version 1.0 Markus Lassfolk - Initial Release # Version 0.5 Mikael Nyström #> Function Update-BaseLineUpdates{ Param ( [Parameter(Mandatory=$false, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, ValueFromRemainingArguments=$false, Position=0)] [String] $BaseLineName ) $baseline = Get-SCBaseline -Name $BaseLineName # Set-SCBaseline -Baseline $baseline -Name $BaseLineName -Description $BaseLineName -RemoveUpdates $baseline.Updates write-host $baseline.UpdateCount : Current number of Updates in Baseline $BaseLineName $addedUpdateList = "" $addedUpdateList = @() if ($baseline.UpdateCount -eq 0) { write-host "No previous updates in" $BaselineName", adding all existing updates for" $BaseLineName "from WSUS" $addedUpdateList += Get-SCUpdate | Where-Object -Property UpdateClassification -EQ -Value $BaseLineName | Where-Object -Property IsApproved -Like -Value "True" | Where-Object -Property IsDeclined -Like -Value "False"| Where-Object -Property IsExpired -Like -Value "False" | Where-Object -Property IsSuperseded -Like -Value "False" | Where-Object -Property Products -like "*Windows Server 2012*" write-host $addedUpdateList.Count ": New updates to add in" $Baseline Set-SCBaseline -Baseline $baseline -Name $BaseLineName -Description $BaseLineName -AddUpdates $addedUpdateList -RunAsynchronously } if ($baseline.UpdateCount -gt 0 ) { write-host "Scanning Newest 500 WSUS Updates for matching updates for $BaselineName" $LatestUpdates = Get-SCUpdate -Newest 500 | Where-Object -Property UpdateClassification -EQ -Value $BaseLineName | Where-Object -Property IsApproved -Like -Value "True" | Where-Object -Property IsDeclined -Like -Value "False"| Where-Object -Property IsExpired -Like -Value "False" | Where-Object -Property IsSuperseded -Like -Value "False" | Where-Object -Property Products -like "*Windows Server 2012*" write-host $LatestUpdates.Count ": Updates found, verifying if update(s) already exist in" $BaseLineName Compare-Object -ReferenceObject $baseline.Updates -DifferenceObject $LatestUpdates -IncludeEqual | % { if($_.SideIndicator -eq '=>') { $addedUpdateList += Get-SCUpdate -ID $_.inputobject.id } } write-host $addedUpdateList.Count : New updates to be added to SCVMM for $BaseLineName write-host $addedUpdateList | ft Set-SCBaseline -Baseline $baseline -Name $BaseLineName -Description $BaseLineName -AddUpdate $addedupdateList -RunAsynchronously } write-host "Scan WSUS for Updates that should not be Checked anymore" $remove = "" $remove = @() $removeUpdateList = "" $removeUpdateList = @() $remove += Get-SCUpdate | Where-Object -Property UpdateClassification -EQ -Value $BaseLineName | Where-Object -Property IsApproved -Like -Value "False" | Where-Object -Property Products -like "*Windows Server 2012*" $remove += Get-SCUpdate | Where-Object -Property UpdateClassification -EQ -Value $BaseLineName | Where-Object -Property IsDeclined -Like -Value "True"| Where-Object -Property Products -like "*Windows Server 2012*" $remove += Get-SCUpdate | Where-Object -Property UpdateClassification -EQ -Value $BaseLineName | Where-Object -Property IsExpired -Like -Value "True" | Where-Object -Property Products -like "*Windows Server 2012*" $remove += Get-SCUpdate | Where-Object -Property UpdateClassification -EQ -Value $BaseLineName | Where-Object -Property IsSuperseded -Like -Value "True" | Where-Object -Property Products -like "*Windows Server 2012*" write-host $remove.count "Remove Unapproved/Superseded/Expired/Declined updates" Compare-Object -ReferenceObject $baseline.Updates -DifferenceObject $remove -IncludeEqual | % { if($_.SideIndicator -eq '==') { $removeUpdateList += Get-SCUpdate -ID $_.inputobject.id } } Set-SCBaseline -Baseline $baseline -Name $BaseLineName -Description $BaseLineName -RemoveUpdates $RemoveupdateList } Function Add-BaseLine{ Param ( [Parameter(Mandatory=$false, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, ValueFromRemainingArguments=$false, Position=0)] [String] $BaseLineName ) $baseline = New-SCBaseline -Name $BaseLineName -Description $BaseLineName $scope = Get-SCVMHostGroup -Name "All Hosts" Set-SCBaseline -Baseline $baseline -AddAssignmentScope $scope $scope2 = Get-SCVMMManagedComputer ForEach($Server in $scope2){ Set-SCBaseline -Baseline $baseline -Name $baseLine -AddAssignmentScope $Server } } Write-Host "Synchronizing with WSUS Server" Get-SCUpdateServer | Start-SCUpdateServerSynchronization . Update-BaseLineUpdates "Security Updates" . Update-BaseLineUpdates "Critical Updates" . Update-BaseLineUpdates "Updates" . Update-BaseLineUpdates "Update Rollups" #. Update-BaseLineUpdates "Definition Updates" #. Update-BaseLineUpdates "Service Packs" #. Update-BaseLineUpdates "Feature Packs" write-host "Start Compliance Scan for all Servers" Get-SCVMMManagedComputer | Start-SCComplianceScan |
The script has a few Pre-Requisites;
- A WSUS Server defined in SCVMM
- Approved patches for “Windows Server 2012” and “Windows Server 2012 R2” in WSUS
- Pre-Defined Baselines (you can use Add-Baseline to create them) with these names;
- Security Updates
- Critical Updates
- Updates
- Update Rollups
That’s it! You can now run the script and automatically import all matching updates.
The following actions will be performed;
- Synchronize updates with WSUS
- Check if there are any updates in the Baseline already
- If the baseline is empty, import ALL matching updates
- If the baseline is NOT empty, check the Newest 500 updates and import all matching updates
- Remove inactive updates
- Repeat for all Baselines
- Start a compliance scan
The script will not initiate any remediation. And as the script normally only checks the newest 500 updates, it has to be run fairly regular. In my environment, 500 updates is about 1 month of updates. Though to be safe, run it once a week.
Hi there.
Thank you for this solution. It works with my SCVMM 2012 R2.
I would like to schedule this script but it doesn’t work properly (Task Scheduler is configured to run this script as SYSTEM).
TS returns 0 (success) after running task manually, but SCVMM Jobs doesn’t show any changes….
Any ideas how to Schedule it?
Tried as vanilla as possible but still same result. Giving up and using full sync. But for reference if anyone else is searching and finds this post.
Get-SCUpdate -KBArticle 2884846 = False
Get-SCUpdateServer | Start-SCUpdateServerSynchronization
Get-SCUpdate -KBArticle 2884846 = False
Get-SCUpdateServer | Start-SCUpdateServerSynchronization -ForceFullUpdateCatalogImport
Get-SCUpdate -KBArticle 2884846 = True
Ok, well the workaround works for me. Too bad I couldn’t get confirmation that it wasn’t me that was doing someting wrong but. Might to some more test installs in different ways just for fun. Will let you know if I get different results.
I’m sorry Patrik but I’ve not been able to reproduce the problem in three different environments, or find any relevant logfiles to dig deeper into. Sorry 🙁
Same problem in UR6.
UR5
Will upgrade later today and see I it helps. Do you know if there is a log file for the synchronization ? Tried running with -debug but got nothing.
Hmm interesting. Are you using UR6?
I found the issue, at least i think.
If i do a Start-SCUpdateServerSynchronization it says it syncing but IsApproved status doesn’t update in vmm (get-scupdate). But if i do Start-SCUpdateServerSynchronization -ForceFullUpdateCatalogImport the IsApproved status is uppdated from Wsus.
Tried a couple og updates and I’m able to reproduce the issue every time.
It could be this line (54);
$LatestUpdates = Get-SCUpdate -Newest 500 | Where-Object -Property UpdateClassification -EQ -Value $BaseLineName | Where-Object -Property IsApproved -Like -Value “True” | Where-Object -Property IsDeclined -Like -Value “False”| Where-Object -Property IsExpired -Like -Value “False” | Where-Object -Property IsSuperseded -Like -Value “False” | Where-Object -Property Products -like “*Windows Server 2012*”
It’s only checking the latest 500 updates in WSUS. And if there are no “Windows Server 2012*” updates in the last 500 (can be a lot of anti-virus definition updates) it won’t import anything. And if you don’t run the script often enough, it may miss updates due to that.
So I’ve changed that in my production environment to look like this;
$LatestUpdates = Get-SCUpdate | Where-Object -Property UpdateClassification -EQ -Value $BaseLineName | Where-Object -Property IsApproved -Like -Value “True” | Where-Object -Property IsDeclined -Like -Value “False”| Where-Object -Property IsExpired -Like -Value “False” | Where-Object -Property IsSuperseded -Like -Value “False”
So it’s importing all updates for all products and not just checking the last 500.
Also as I noticed it would be miss for example IE and .NET Framework updates.
I’v approved the updates in WSUS and synced but noting gets to baselines.
Checked the updates in VMM with Get-SCUpdate and there they are all IsApproved = False.
If i manualy add an update to a baseline i can see with get-scupdate that it changes to IsApproved = true.
What am i missing ?