Invoke-DGASoftwareUpdateMaintenance
1 file(s) 60.79 KB
Invoke-DGASoftwareUpdateMaintenance
12/20/18 Update: Removed a check for WSUS cmdlets that prevented the script from working on 2008 R2 and the ReSyncUpdates from the WSUS standalone config file.
12/10/18 Update: Fixed a configuration file parsing problem and added licensing information for GPLv3.
Note: When updating you will need to update any existing plugins as well.
Despite a seemingly quiet few months I have continued to enhance my script for maintaining software updates in WSUS and ConfigMgr. In fact, I’ve silently released new versions of the script from time to time by simply updating the binary hosted here. However, now there’s enough new stuff worth talking about.
Before we get into it I’d like to thank Chad Simmons for his contributions. Chad submitted several fixes and plugins that I suspect many will enjoy. Also, he fixed my atrocious spelling and has basically shamed me into using Visual Studio Code for any future development.
The complete documentation for the script can and will always be found here: Fully Automate Software Update Maintenance in Configuration Manager
Using WhatIf will force the script to run regardless of the 24-hour timeout.
By default the script will only run once every 24 hours. This is an arbitrary amount of time and eventually I’ll probably make it configurable. However, this timeout serves two purposes. The first purpose is to avoid an infinite loop when you trigger the script from a status filter rule while using the ReSyncUpdates feature. Second, if you’re syncing multiple times a day for Defender updates it would seem overkill to run maintenance every time. When first implementing the script you should be running it a whole bunch of times using WhatIf. Therefore, it makes sense to ignore the timeout when changes are not being made.
WhatIf mode don’t check sync status when declining updates
Similar to the above, when running in WhatIf mode it doesn’t matter if your environment is syncing or not. Since it takes a non-trivial amount of time to check for sync status there’s no point doing so if no changes are going to be made anyways.
Added ExcludeByTitle, ExcludeByProduct, and IncludeByProduct options
This was a request I heard a few times so I’ve added it in. You can use these parameters to either exclude updates from being declined or to limit the scope to a certain product family. Honestly, I don’t think this feature has all that much value. To my thinking it would mean running the script multiple times to decline updates based on multiple different characteristics at the same time. That entire use case is what plugins are for. Speaking of plugins …
Updated Windows 10 Plugins
The ever sharp Justin Chalfant keyed me into the fact that although they don’t show up in ConfigMgr the Win 7 and 8.1 in-place upgrades to Win 10 are synced in WSUS. So I’ve updated the Win 10 plugins to handle those updates as well as created a plugin to remove them entirely. I’ve also updated the plugins to handle the name change from Home/Education/Pro/Enterprise/etc. to just Consumer/Business.
MOAR PLUGINS!!!!
I was super excited to see that the aforementioned Chad Simmons groked how powerful the plugin concept could be and was willing to share his work. There’s zero chance that the main script will support every use case administrators might come up with. I have zero interest in even attempting that goal. However, that’s exactly what the plugins are designed to handle. I really don’t care what convoluted logic you ‘need’ … just put it in a plugin and return a list of update IDs for the main script to decline. If you think others might benefit then by all means reach out and I’ll consider adding it in with the release. Thanks to Chad we now have plugins with more advanced logic for declining Itanium and 32-bit updates. I took the later plugin and released one that excludes Server 2008 (yea … I know). I likewise created a Windows 10 version script that excludes LTSC if you happen to be using that channel.
For Card Holding Members of the ‘Command Line is Too Damn Long’ Party
So … ok … things have gotten a little out of hand in terms of how many features and parameters this script supports. Add into that trying to pass in arrays and hash-tables as parameters and things get awkward real quick. So what I’ve done is created a new feature that will read the parameters out of a configuration INI file. Further, if you provide the script with no parameters at all it will default to use the config.ini file that is in the same folder as the script. An example default config has been provided that represents what I run in my own organization. Modify that example as you see fit and remove the WhatIf parameter from it when you are satisfied with the results. Note that relative paths are now supported to point to the configuration file itself as well as the log and output files.
Let’s Make WSUS Less … Terrible … Again
So this might be news to you but WSUS is … how can we say this … kind of long in the tooth at this point. As in, take it to the vet and do the right thing. Beyond just declining updates there’s two other things you can do.
The first thing is to make the database faster by adding indexes. A while ago Steve Thompson and Benjamin Reynolds went looking at the stored procedure for deleting obsolete updates to figure out why it took so long. The result was a great blog post you can read here: Enhancing WSUS database cleanup performance SQL script. TL;DR: The product team failed to index certain fields they rely heavily on. Fix that problem and suddenly things run hundreds of times faster.
The latest version of the script supports two new parameters: UseCustomIndexes and RemoveCustomIndexes. The first will create the indexes Steve and Ben talk about as well as some others that the community has found to be helpful. This, in theory, should solve the last mile problem for the WSUS Cleanup Wizard and make WSUS run faster in general. Keep in mind that you still need to do DB maintenance on WSUS’s database just like you would any other. I’m told the WSUS product group plans to add the indexes Steve and Ben found in the next release. Until then though, adding custom indexes isn’t exactly supported by Microsoft. For those fearful of living on the edge we call ‘unsupported’ I’ve added the RemoveCustomIndexes parameter to remove them if you so desire. Premier support will never be the wiser.
Less WSUS is Always More WSUS
The second thing you can do is to minimize the amount of updates in the database as a whole. Declining updates removes them from the catalog that WSUS generates and that clients process but it doesn’t actually remove them from the database. I recently ran the script on a client whose WSUS instance goes back nearly a decade. They had something like 40k active updates in the database. While I reduced that number dramatically they were still in the database and it was performing poorly. To remedy this I’ve added a parameter called DeleteDeclined which will actually delete updates from the database using the WSUS API calls. When used, the script will create a local data file in the script’s folder that tracks when an update was declined. It will then delete any update that was declined based on the ExclusionPeriod value. So if you decline updates after 3 months of being superseded they will be removed from the database 3 months after that for a total of 6 months after being superseded. Note that once deleted, it’s not easy to bring that update back. You may be able to manually import it from the Update Catalog or you may need to de-select the corresponding product, sync, select it again, and resync the entire product again.
What’s Next?
The next thing I want to dive into is how to orchestrate this kind of maintenance when you have multiple SUP databases and in CAS scenarios. In my research the recommendation seems to be to decline updates top-down but run the WSUS Cleanup Wizard bottom-up. I want to dive into that a bit and see how the script might be able to handle such scenarios and what it takes to configure. It seems like a dark hole of sadness so if you don’t hear from me for a while … pour one out for your fellow comrade. Until then, keep you stick on the ice.
Don’t suppose there will be an update to allow it to run on a SUP that is NOT the primary site server?
Bryan, I just wanted to give you another shout out for the script/solution. I ran into a scenario where a customer had set 3-hour maintenance windows for their servers to patch. The Windows Malicious Software Removal Tool was included in the Software Update Group and it is set to have a Maximum Run Time of 180 minutes by default (not sure if that is new) which prevented it from installing on most servers. I had already implemented the script to run daily so all I had to do was add this entry to the INI… and BAM, all done!
MaxUpdateRuntime=@{‘Windows Malicious Software Removal Tool*’=120}
Thanks again!
my understanding remote desktop and termal server was supose be remove,. guss what they not, what was big fuss, nsa told microsoft make backdoor programs, but not stop there when software when to china to be built they also keep the programs to grain access to servers, it admazes me we forgot what we learn 20 years ago about servers, throw then agian most u never touch server then, i wish i could upload two files, this year 5 month some one spent days trying pull the encription cerfitifcs off my domain, kicker was they had out dated information at time i had no domains, and i just rebuilt the pc,remote access can be used by any one with right certific or did we miss this in class, u see i have add throw i cant spell never could microsoft new how use me, i find probems in any software with no training was no leg time for software being put out me learing it i allready found the probems, i also capture the login used to run the commands ip address microsoft certific microsoft, i have the abuilty to caputure certifics in real time, so when remote access ask for i get there certifics this to meens i can reuse them, to log into any pc. wich work all to well, i trace probem back to microsoft root certifics that able do anything even read bitlocker key, but why would they need this kind access to just update a system they would not, but most telling is in windows8,1 the vpn clinents copy them to flash drive, just like they are install the flash drive in to a pc say windows 7 or server give it a day remove the drive go event viewer u find long list error codes of them files missing, but u did nothing to install them or even read them so how is it the os is now trying to run them, look programing much like a virus, the abuilt that microsoft put into computer to link one to other, is be on nuts microsoft can send commands to any pc even if not on the internet just has have lap top plug in and if u have logs set right in gpo first thing u see is them logs being disabled, microsoft has abilty to change gpo and they do upload it to there servers, no matter fbi fully knows microsoft waching each pc, throw that t his informtion going to china gives me pause as to who they working for, can u all tell me what apple microsoft hp cisco ibm all have in common any one all the software is bult and upgraded in china, what i was most consern about cisco abuily to add and remove servers at will even if they have no account on a unit, you all speak of trust yeat each these busness has taken contorl over every device, but u all sware by cloud and that no one would ever sell u data. witch world do u live in takes less 15 mins to break bitlocker key, unless u do something spichle wich makes it impossable to break, to each thing they do, we can find ways stop them but i most intrested in a scrip the can pick up remote comands and del them across remote cmd remote power shell remote firewall, remote install, with all these rmote subcomands why do we need remote desktop,ask microsoft they vr all computers out there, untill china is remove none of the systems are safe o by way that to inclueds linux microsoft made shure put out there versions so they access it as well. u know what bother me worst i have apple ph yeat microsoft new where i drove for hole month what stores i went to how fast i was going. would that not bother u as well
The error that the user reported with a solution never worked for me. If I go into and UNCHECK “Require SSL Communication to the WSUS server” it does work.
Failed to connect to the WSUS server on port 8531 with SSL.
Error: -2146233087): Exception calling “GetUpdateServer” with “3” argument(s): “Request for principal permission failed.”
At C:\Beheer\scripts\Invoke-DGASoftwareUpdateMaintenance\Invoke-DGASoftwareUpdateMaintenance.ps1:1167 char:5
+ $WSUSServer = [Microsoft.UpdateServices.Administration.AdminProxy …
So LTSB one work with the LTSC channel too for updates? We run a mix and I need to make sure that script works with both.
I’m assuming you’re referring to the LTSB plugin. That’s based on the WSUS Category name which hasn’t changed so I don’t see why it wouldn’t work. Though that’s why the script supports WhatIf and has verbose logging/output.
Yes i was sorry about that before my coffee, I guessed that much just making sure that there was not something i missed when they rolled out LTSC channel in the updates. Yeah I planned to test before hand just asking ahead of time.
New to powershell, been thrown in the deep end at school, did a fresh install of 2016 setup AD GP etc..
Now want to get updates pushed to clients PC’s.
Ran your script with everything set, how do I know its working?
It ran with no errors
By default the script will create a log file in the same directory of the script. You’ll find the results there. If it’s your first time running it I always love to see the first summary section that shows the initial reduction in active updates.
The default config.ini has a decline for ‘*security only*’ by title. What’s the rationale behind that please?
I only want to install the Cumulative updates which include the Security updates. Deploying both can do no good and only harm.
Thanks. I figured that ‘security only’ were also included in another batch.
Ive been asked a question about this after running the script. What if the ‘security only’ fix comes out 3 weeks before the cumulative update and is an urgent security patch? Arent we losing out on vital protection by declining it?
…or do security only patches come out at the same time as the cumulative update?
As a rule, yes. Patch Tuesday exists specifically so that Microsoft can release security updates on a known schedule. It’s quality updates that really don’t necessarily follow that rule.
However, there are rare out of band patches that happen from time to time. There’s such a case going on right now with IE I believe (though I don’t think they’ve been released to WSUS yet). If they name those updates in a way that the script filtering rules would catch then yes, it’s going to decline those. You can simply alter your settings or not run the script until the next CU, undecline the udpate(s) in WSUS, and resync to bring them into ConfigMgr.
Yeh – the IE one will be released within 24 hours I hear. It’d suck big time if they called it ‘security only’.
Well Ive been told not to decline the ‘Security Only’ updates.Apparently it has made the reporting of what updates being applied much more difficult for management as declined updates are not reported.
Having a few issues. Not much of a powershell user.
Have modified config_standalone as per below
;Example configuration file for standalone WSUS
;Note-You will need to remove the WhatIfPreference configuration before changes to your environment will be made.
DeclineByPlugins
DeclineByTitle=@(‘*Security Only*’,’*Preview of*’,'(Preview)*’,’*Itanium*’,’*ia64*’,’*Beta*’,’*Version Next*’)
;DeclineLastLevelOnly
DeclineSuperseded
DeleteDeclined
;ExcludeByProduct=@(‘*name*’,’*name*’)
;ExcludeByTitle=@(‘*name*’,’*name*’)
;ExclusionPeriod=3
FirstRun
Force
;IncludeByProduct=@(‘*name*’,’*name*’)
LogFile=c:\Invoke-DGASoftwareUpdateMaintenance.log
;MaxLogSize=2621440
RunCleanUpWizard
StandAloneWSUS=localhost
StandAloneWSUSPort=8530
StandAloneWSUSSSL=$True
;SyncLeadTime=5
UpdateListOutputFile
UseCustomIndexes
WhatIfPreference
Have modified the ps1 file to point to the standalone config file with this line
[CmdletBinding(SupportsShouldProcess=$True,DefaultParameterSetName=”config_wsus_standalone”)]
When i run .\Invoke-DGASoftwareUpdateMaintenance.ps1 from power shell i nothing seems to happen and i just get a new line .
PS C:\Invoke-DGASoftwareUpdateMaintenance> .\Invoke-DGASoftwareUpdateMaintenance.ps1
PS C:\Invoke-DGASoftwareUpdateMaintenance>
If any one can give me a tip where i am going wrong then that would be fantastic!
Cheers
Also should note that a log updatemaint is creeated with the following
Im running a stanalone wsus server
8530 is the non-SSL port, 8531 is the SSL port. Also, you should put the FQDN of the server in place of localhost for StandAloneWSUS. Not sure if that’s your issue, but those are my suspects.
Sorry, after playing around with the script for some time that day the config was a little messy.
Thanks for getting back to me.
Current config is
;Example configuration file for standalone WSUS
;Note-You will need to remove the WhatIfPreference configuration before changes to your environment will be made.
DeclineByPlugins
DeclineByTitle=@(‘*Security Only*’,’*Preview of*’,'(Preview)*’,’*Itanium*’,’*ia64*’,’*Beta*’,’*Version Next*’)
;DeclineLastLevelOnly
DeclineSuperseded
DeleteDeclined
;ExcludeByProduct=@(‘*name*’,’*name*’)
;ExcludeByTitle=@(‘*name*’,’*name*’)
;ExclusionPeriod=3
FirstRun
Force
;IncludeByProduct=@(‘*name*’,’*name*’)
LogFile=c:\Invoke-DGASoftwareUpdateMaintenance.log
;MaxLogSize=2621440
RunCleanUpWizard
StandAloneWSUS=wsusupdate
StandAloneWSUSPort=8530
StandAloneWSUSSSL=$False
;SyncLeadTime=5
UpdateListOutputFile
UseCustomIndexes
;WhatIfPreference
All clients are connecting on port 8530 so the port should be correct.
as of last week i am getting an output error
PS C:\Windows\system32> C:\Invoke-DGASoftwareUpdateMaintenance\Invoke-DGASoftwareUpdateMaintenance.ps1
Set-Variable : Cannot convert value “System.String” to type “System.Boolean”. Boolean parameters accept only Boolean values and numbers, such as $True, $False, 1 or 0.
At C:\Invoke-DGASoftwareUpdateMaintenance\Invoke-DGASoftwareUpdateMaintenance.ps1:894 char:21
+ … Set-Variable -Name $Data[0] -Value $Data[1] -Force -WhatI …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : MetadataError: (:) [Set-Variable], ArgumentTransformationMetadataException
+ FullyQualifiedErrorId : RuntimeException,Microsoft.PowerShell.Commands.SetVariableCommand
From what i can tell this is in relation to the config comment StandAloneWSUSSSL=$False. If i comment out that line it does not report the error.
Even with the error showing up it does seem to be doing something now. But not sure why that comment is causing an issue for me
Sorry thats a new error. not related to the comment StandAloneWSUSSSL=$False.
Okay i have gone full circle. Please ignore all messages above .
Current config
;Example configuration file for standalone WSUS
;Note-You will need to remove the WhatIfPreference configuration before changes to your environment will be made.
DeclineByPlugins
DeclineByTitle=@(‘*Security Only*’,’*Preview of*’,'(Preview)*’,’*Itanium*’,’*ia64*’,’*Beta*’,’*Version Next*’)
;DeclineLastLevelOnly
DeclineSuperseded
DeleteDeclined
;ExcludeByProduct=@(‘*name*’,’*name*’)
;ExcludeByTitle=@(‘*name*’,’*name*’)
;ExclusionPeriod=3
FirstRun
Force
;IncludeByProduct=@(‘*name*’,’*name*’)
LogFile=c:\Invoke-DGASoftwareUpdateMaintenance.log
;MaxLogSize=2621440
RunCleanUpWizard
StandAloneWSUS=WSUSupdate
StandAloneWSUSPort=8530
StandAloneWSUSSSL=$False
;SyncLeadTime=5
UpdateListOutputFile
UseCustomIndexes
WhatIfPreference
port is corrected to 8530
Standalone to $false
left in What if preference.
Ran the script and get this error.
Set-Variable : Cannot convert value “System.String” to type “System.Boolean”. Boolean parameters accept only Boolean
values and numbers, such as $True, $False, 1 or 0.
At C:\Invoke-DGASoftwareUpdateMaintenance\Invoke-DGASoftwareUpdateMaintenance.ps1:894 char:21
+ … Set-Variable -Name $Data[0] -Value $Data[1] -Force -WhatI …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : MetadataError: (:) [Set-Variable], ArgumentTransformationMetadataException
+ FullyQualifiedErrorId : RuntimeException,Microsoft.PowerShell.Commands.SetVariableCommand
This is the error that relates to the config file line StandAloneWSUSSSL=$False
If i comment this out no error shows up. Any idea what could be causing this ?
If i run the -FirstRun -Force command i have seen the progress bar show up as it goes through the routine even though the error showed up.
That just means the script is failing to convert the string ‘$False’ for the StandAloneWSUSSSL parameter. You might try removing the $ and see if that works otherwise I’ll have to test that a bit and get to the bottom of it. I’m also pretty sure that if you just comment that out the script will default to it not being SSL.
Hi,
Thanks so much for your efforts in creating this – I don’t understand why Microsoft Engineers almost seem to go out of their way to make SCCM/WSUS so notoriously difficult to use and maintain.
I’m trying to use this script in a pretty vanilla SCCM environment – A single SCCM (build 1902) Primary site server with the WSUS role and SUP all installed on the same WIN 2012 R2 server.
Unfortunately I’ a powershell noob, but fairly familiar with SCCM. I’m getting the follwoing errors when I run the script on the Primary site server with local admin permissions:
————-
Get-Item : Requested registry access is not allowed.
At D:\source\Maintenance_Scripts\Invoke-DGASoftwareUpdateMaintenance\Invoke-DGASoftwareUpdateMaintenance.ps1:565 char:20
+ $Key = Get-Item -LiteralPath $Path
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : PermissionDenied: (HKEY_LOCAL_MACH…\Identification:String) [Get-Item], SecurityException
+ FullyQualifiedErrorId : System.Security.SecurityException,Microsoft.PowerShell.Commands.GetItemCommand
You cannot call a method on a null-valued expression.
At D:\source\Maintenance_Scripts\Invoke-DGASoftwareUpdateMaintenance\Invoke-DGASoftwareUpdateMaintenance.ps1:566 char:17
+ If ($Key.GetValue($Value, $null) -ne $null) {
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
______________________________________________________________________________________________________________
I’ve tried adding the account running the script to the “WSUS admins” local SG on the SCCM/WSUS server, as per “Wims” post (29th April, 2019), but I’m still getting the same errors?
Sorry Jonathan, didn’t see this earlier. Looks like you were denied permission to reach the registry. Make sure the user/account running the script is itself an administrator on the machine.
Trying to get this working on a SCCM 1810 with ssl enabled. Here my config.ini
;Example configuration file for standalone WSUS
;Note-You will need to remove the WhatIfPreference configuration before changes to your environment will be made.
DeclineByPlugins
DeclineByTitle=@(‘*Security Only*’,’*Preview of*’,'(Preview)*’,’*Itanium*’,’*ia64*’,’*Beta*’,’*Version Next*’)
;DeclineLastLevelOnly
DeclineSuperseded
DeleteDeclined
;ExcludeByProduct=@(‘*name*’,’*name*’)
;ExcludeByTitle=@(‘*name*’,’*name*’)
;ExclusionPeriod=1
FirstRun
Force
;IncludeByProduct=@(‘*name*’,’*name*’)
LogFile=C:\Beheer\SQL\Invoke-DGASoftwareUpdateMaintenance\DGA.log
;MaxLogSize=2621440
RunCleanUpWizard
StandAloneWSUS=#REDACTED#
StandAloneWSUSPort=8531
StandAloneWSUSSSL=$True
;SyncLeadTime=5
UpdateListOutputFile
UseCustomIndexes
WhatIfPreference
Powershell output:
Set-Variable : Cannot convert value “System.String” to type “System.Boolean”. Boolean parameters accept only Boolean va
lues and numbers, such as $True, $False, 1 or 0.
At C:\Beheer\sql\Invoke-DGASoftwareUpdateMaintenance\Invoke-DGASoftwareUpdateMaintenance.ps1:894 char:21
+ … Set-Variable -Name $Data[0] -Value $Data[1] -Force -WhatI …
In the LOG file:
Failed to connect to the WSUS server localhost on port 8530 without SSL.
Error: -2146233087): Exception calling “GetUpdateServer” with “3” argument(s): “The request failed with HTTP status 403: Forbidden.”
Tried different things but keeps failing.
please advice
Is this WSUS instance part of a SUP or a standalone WSUS server? You say you’re running ConfigMgr 1810 but you’ve configured the script it to run on a StandAlone.
If it’s part of a SUP then don’t specify the StandAloneWSUS parameters. If it’s truly standalone then my guess is that you need to play around with the StandAloneWSUSSSL value somehow. I don’t have a standalone WSUS let alone one running SSL so I haven’t really been able to test that part effectively.
Thank you for the reply. This WSUS is part of a SUP. SCCM 1810 is installled with all the roles on a single Windows Server 2019.
One of the errors was a access denied on HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SMS\Identification. I added read permissions for my account, that was already local admin, to solve this.
Then i was stuck on a error:
Failed to connect to the WSUS server on port 8531 with SSL.
Error: -2146233087): Exception calling “GetUpdateServer” with “3” argument(s): “Request for principal permission failed.”
At C:\Beheer\scripts\Invoke-DGASoftwareUpdateMaintenance\Invoke-DGASoftwareUpdateMaintenance.ps1:1167 char:5
+ $WSUSServer = [Microsoft.UpdateServices.Administration.AdminProxy …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
After adding my account, that was already local admin, to the local group ‘WSUS Administrators’ on the SCCM box and rebooted, things started to work.
Thank you for the support en your hard work, Bryan
Wow – great script. Just wanted to check on streaming servers. We have this scenario (dont ask!)
ServerC (our SUP) is downstream from ServerB (where currently most get updates from) which is downstream from ServerA.
We control ServerC and ServerB but have no access to ServerA.
Do we run this stuff on ServerC first then ServerB?
If I recall correctly, the way downstreaming works is if it’s declined at a higher level, lower servers will have automatically inherited that status, so you (should) only need to do it against ServerB.
Yea, there’s a whole post right in that one question. Microsoft’s recommendation is to work bottom up because in the event that someone _does_ manually initiate a sync during your maintenance run it can break stuff. I’d argue that if you’re reasonably sure no one is going to manually kick of a sync then doing it all in parallel limits the amount of time you’re in that ‘vulnerable’ state where a sync would cause issues.
Its really all about what exactly is replicated. If you decline an update on serverC but not on serverB does the ‘not declined’ status get replicated on next sync and override the decline on ServerC? If so then Im in big trouble as I have no control over ServerA and they dont decline stuff. Ive just manually declined an update on ServerB and will wait for all the auto syncs and see what happens.
I declined an update on ServerB last week. After several syncs the same update on ServerC has not been declined. So the conclusion is that the ‘Declined’ status is not replicated. Which for me means that the WSUS maintenance scripts will have to be run at both ServerC and ServerB.
Your tool is amazing. I have a suggestion. Add plugin to decline by classification and you can specify the classification name to decline… Such as Drivers
Hi Bryan, I am trying to use the plugin “Decline-WindowsX86” to decline all Windows 10 x86 updates but it doesn’t seem to be working. Below is the line I edited in the script to do so. Thanks in advance!
$SupportedWinX86Versions = @(‘Windows 7’)
I know this is an old post, but for the sake of others that come here looking for an answer I was able to decline Windows 10 x86 updates by editing the following two lines:
$SupportedWinX86Versions = @(‘Windows 7’)
$WindowsX86Updates = ($ActiveUpdates | Where {($_.LegacyName -notlike ‘*DOTNET*-X86-TSL’) -and ($_.LegacyName -like ‘WSUS*_x86’ -or $_.LegacyName -like ‘*WINDOWS*-KB*-X86-*’ -or $_.LegacyName -like ‘KB*-*-X86-TSL’ -or $_.Title -like ‘*for x86-based Systems*’)})
I am trying to run the standalone script with WSUS and keep coming up with the following:
Failed to get updates.
If this operation timed out, try running the script with only the FirstRun parameter.
Error: -2146233087): Exception calling “GetUpdates” with “0” argument(s): “The operation has timed out”
At C:\Scripts\Invoke-DGASoftwareUpdateMaintenance\Invoke-DGASoftwareUpdateMaintenance.ps1:1391 char:6
+ $AllUpdates = $WSUSServer.GetUpdates()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Have to ask, have you ran the FirstRun and UseCustomIndex options? What that means is that the script simply failed to get the update metadata from WSUS because it timed out trying. It’s a bit of a catch-22 … WSUS is running too poorly to run maintenance.
Several times. That fails too.It finally went after about the 15th attempt. It kept timing out.
Hello,
I just tried with the latest version from november, and I’m still getting issue where it try to connect to the database and fail since it’s a WID. I tried with only the following
.\Invoke-DGASoftwareUpdateMaintenance.ps1 -UpdateListOutputFile ‘E:\sources\Invoke-DGASoftwareUpdateMaintenance\UpdateListOutputFile.csv’ -DeclineSuperseded
and it’s failing at the start (in the log file)
I tried to find in the powershell script where it try to connect but I’m a bit lost in the powershell code.
Hello,
I found the problem.
At line 1188, you have a return. Maybe that was before a function, but now, it’s straight in the root. So the Return actually exit the run instead of continuing. You must also remove the Ste-Location because it does other error.
$WSUSServerDB = Get-WSUSDB $WSUSServer
If(!$WSUSServerDB)
{
Add-TextToCMLog $LogFile “Failed to get the WSUS database configuration.” $component 3
Set-Location $OriginalLocation
Return
}
Commenting the Return and Set-Location at this point fixed the problem.
So I doubt your edits actually result in the script actually functioning. I don’t have a WID database to test with directly and it’s a bit tricky to connect to them because there’s different versions of WID with different connections strings. So by commenting out those lines you’re simply bypassing the check to make sure that the script successfully connected to the database. Unless it connects, the FirstRun and CustomIndex features aren’t going to function.
Post the whole log somewhere and I’ll take a look.
Weird, I did post the log file, can’T see it here. I think it didn’t like the tag. Since I’m using WID, it will fail to connect to database (it’s on a different server then the site server). I just ran the script and now it’s going through. What happened before is after doing this check, it exited the powershell script completly instead of just using it as a flag. Log file was simple, only saying this
https://pastebin.com/FJSmDsLQ
Invoke-DGASoftwareUpdateMaintenance started (Version 2.4.2). Invoke-DGASoftwareUpdateMaintenance 2018-12-11 13:48:21 5880 (0x16F8)
Connecting to site: XXX Invoke-DGASoftwareUpdateMaintenance 2018-12-11 13:48:21 5880 (0x16F8)
The active software update point is contoso.corpo.com. Invoke-DGASoftwareUpdateMaintenance 2018-12-11 13:48:21 5880 (0x16F8)
Trying to connect to contoso.corpo.com on Port 8531 using SSL. Invoke-DGASoftwareUpdateMaintenance 2018-12-11 13:48:21 5880 (0x16F8)
Connected to WSUS server contoso.corpo.com. Invoke-DGASoftwareUpdateMaintenance 2018-12-11 13:48:21 5880 (0x16F8)
Failed to connect to the (SUSDB) database on MICROSOFT##WID. Invoke-DGASoftwareUpdateMaintenance 2018-12-11 13:49:21 5880 (0x16F8)
Error (-2146233087): Exception calling “ConnectToDatabase” with “0” argument(s): “A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: Named Pipes Provider, error: 40 – Could not open a connection to SQL Server)” Invoke-DGASoftwareUpdateMaintenance 2018-12-11 13:49:21 5880 (0x16F8)
At E:\sources\Invoke-DGASoftwareUpdateMaintenance\Invoke-DGASoftwareUpdateMaintenance.ps1:708 char:9
+ $WSUSServerDB.ConnectToDatabase()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Invoke-DGASoftwareUpdateMaintenance 2018-12-11 13:49:21 5880 (0x16F8)
Failed to get the WSUS database configuration. Invoke-DGASoftwareUpdateMaintenance 2018-12-11 13:49:21 5880 (0x16F8)
Then nothing, script exited. Once I removed the return and set-location, the remaining of the script start running like it should. I’m neither using FirstRun or CustomIndex.
Pretty sure I know what’s happening here. Undo the changes you made and comment out line 708: $WSUSServerDB.ConnectToDatabase().
When I get the WSUS Database info the routine tries to connect to validate that the information is correct. You can’t remote into a WID database, that’s one of it’s many limitations, which is why you’re seeing the error. There’s really no reason to do that check other than it’s part of the API and super easy to do.
It does fixe it because now it doesn’t run the try-catch in that function, which resulted in a null return.
I’m unsure what exactly you want to achieve at 1183 and 1184. If you want to see if you have any information in WSUSServerDB, regardless if you can connect or not, then the problem is in the Try Catch @ 715, where you return when you fail connecting, but that doesn’t mean the information is wrong (thus get a blank WSUSServerDB returned). You could keep that for a test connection when needed, but since the function is Get-WSUSDB, it should always return the value of $WSUSServerDB, not just a plain return.
The FirstRun and CustomIndexes stuff connects directly to the DB and thus needs DB info. So the routine to get it is probably better nested inside of the features that actually require that though which is what I’ll do. Commenting out line 708 just confirms what was going on. All part of developing against WID by proxy.
Bryan,
Hope all is well. Wondering is there a way to remove specific updated out side of the expiration period the environment is set to. For example A/V definition files, there is no need to keep these around for extended periods. So wondering if there is a way to have them removed immediately after expired?
We never got to the point of using WSUS/ConfigMgr to manage definition updates so I’m a bit hazy on some of that stuff. However, when you say ‘removed’ where do you mean? When they show as Expired in ConfigMgr what is their status in the WSUS console? It’s been my understanding that MS expires the definitions pretty aggressively and in doing so solves the whole client catalog problem. In ConfigMgr there’s an unmanageable maintenance task that removes expired updates every 7 days.
I thought I meant from SCCM and WSUS but you are right that the unmanageable maintenance task removes the content every 7 days from the Software Update group but the Deployment Package does not get updated so expired content remains and causes the package to balloon. Running your script with CleanSources unfortunately does not touch theses ADRs so the packages just continue to balloon in size even though the expired updates have been removed from the update group.
So there’s another unmanageable maintenance task that run every 7 days and will remove an update’s content from deployment packages if the update is no longer deployed. Though I could see there being a timing issue there I hadn’t though of. If the first task removes the update entirely from ConfigMgr then the second won’t know that it’s not deployed (in theory) and might not touch it. You might want to dig into that a bit, prove that you have content from expired/removed updates, and file a User Voice Item. If that were the case, it might be possible to solve in the script but it’d be a hard thing for me to replicate and repeatably test at this point.
The CleanSources feature only deals with the source folder of the deployment package. It doesn’t happen often but sometimes the source files are left behind when something is removed from a deployment package. So the script compares the source folder to the deployment package and removes anything not in the package.
Hey, this is fantastic. Thank you. Is the intention to use this exclusively and not touch the WSUS clean up wizard?
I guess, yes? There’s nothing that stops you from running the clean up wizard yourself outside of the script. However, there’s a parameter to run it after the script does all of the other WSUS stuff so I don’t know why you would want or need to.
I’m somewhat confused about the error message “Currently, this script must be ran on a primary site server. When the CM 1706 reaches critical mass this requirement might be removed.”
I’m somewhat new to domains, but as far as I knew the server was its own primary site server? It’s the only Windows Server so I’m not sure what it’s looking for.
The intention is to execute the script locally on the primary site in your SCCM hierachy. Not from a remote server. Does this help?
Yeah, that’s the problem .I’m executing it on the local (and only) server. It’s a hierarchy of one .
If you think through all of the things the script does it needs pretty much god-level access. So there’s a check in the script to make sure it can read the WMI classes that exist on a primary site server. There was something released in 1706 that would theoretically make it easier to run elsewhere but even then I’m not quite sure where the value is in running it elsewhere. So what that warning means is that you’re not running it on your primary site server and you need to.
Right, that’s my question: if the only server in the domain is not a “primary site server”, then what do I do to make it so? I ran the script as a Domain Enterprise Admin on the local console (i.e. Session 0) and it spit out that error message. If that’s not God-level I’ll try impersonating SYSTEM, but I doubt that would help…
Sorry, I should have specified: the primary site server refers to your ConfigMgr primary site server sometimes simply referred to as your site server.
If you just have WSUS and don’t have ConfigMgr, try this (I only have WSUS)
The bug I found was in the config file parsing section. \d will find an integer in hostnames that have numbers in them, which I imagine a whole lot of folks do. This causes the config parse to fail for any string with a number in it that’s in the config file and you won’t get the config to realize you only have a single WSUS server.
change line (around) 860 to } ElseIf ($Data[1] -match “^\d+$”) {
Ah, does that cause this problem somehow? It didn’t register that the two would be related.
Yes, basically the regex is broken in that if you have a number anywhere in your server name, that elseif breaks the StandAloneWSUS config option.
Someone, maybe you, already called that out so I have that fixed in the code but didn’t see the urgency in releasing just that. I’ll try and get that out this week yet.
Yep, was me further down in these comments. Never saw a reply or update so I just mentioned it again as it was driving me crazy when I first got ahold of your script 🙂
but, thanks for the script. The “other” one that was on the web for cleaning WSUS is now a pay-to-use script, so your script is the only free game in town that I’m aware of.
Ah …..I don’t think I have such a program installed.
then try to change that line in the script and see if it works. if you want to use the config.ini, you also need to not pass any other command line options to the script or it will ignore it from what I remember.
Ah, I do remember fiddling the script so it ignores that check and subsequently fails loading up the config manager thing .
I’ll just use the stand alone option.. .
Yep, remove that check and it’s going to assume you’re running ConfigMgr and try to call into it to get all sort of info. You need the WSUSStandAlone feature to work. In theory, if you simply specify everything on the command line instead of the config file it should work as-is. But I’ll try and get the fix out soon.
Hello Bryan,
I have enabled switch DeleteDeclined in config.ini.
This is log from updatemaint.log:
Deleted update ‘Upgrade to Windows 10 Education, version 1511, 10586 – nl-nl, Volume’ (ID: 8ec3e4af-1b8e-4238-8c66-dde0ebd34527). Source: Declined
What does it mean the last bit at log entry “Source: Declined”? Does it mean that updated wasn’t deleted?
Is it normal that when DeleteDeclined is enabled script runs very very slow?
Thanks,
Tomas
Define slow. The log is pretty verbose; where’s it sucking up time? If you have a lot of declined updates … sure … it’ll take more time to run the script.
The source field is really just my attempt to indicate why something is deleted or declined and goes into the output file. In theory there might be some other reason to delete an update other than because it was declined.
Hello Bryan,
$SupportedUpdateLanguages=@(“en”,”all”) – Will decline all languages except en-us and en-gb
How to decline en-us as well and leave only en-gb?
I have tried like this $SupportedUpdateLanguages=@(“en-gb”,”all”) but no luck.
Thanks,
Tomas
So the problem here is that the language configuration for ConfigMgr/WSUS doesn’t quite match the language field on the actual updates. The script looks at the config and does the best it can while erring on the side of safety. If you want to get really specific it would be pretty simply to write a plugin that looks for specific language codes that you define. Just be sure to also keep updates labelled as ‘all’ since that’s the majority.
Would it work if I run like this: DeclineByTitle=@(‘*, en-us*’) ?
Maybe? That’s what WhatIf is for.
I have tested and can confirm that does work:-)
Bryan, first thank you for the amazing job you’re doing with this script, which I found just recently.
However, I can confirm that neither of the plugins are working as they should – they always return zero updates to decline to the main script. I tried to look into it and here are my thoughts:
In each of the plugins you’re referring to a variable $Updates – I searched through the whole source of the main script and the only place where that variable is defined is inside the IF that sets the maximum runtime for updates (line 1900). I suppose that’s not enough, isn’t it? Could this be the reason plugins don’t work?
Alright, I found another variable that was defined and looked exactly what was needed – $AllUpdates
Then I changed $Updates to $AllUpdates in a plugin’s source and it did the trick!
You are correct, I had some time to look at this yesterday and I once again screwed up a Git upload/merge. I apparently suck at those and plan to migrate to some real tooling before starting any new serious development. I should have another release out today with the correct plugins.
There’s also a mistype in all the Windows10 plugins (haven’t checked others): Test-Exclusions is missing a “c”.
Just uploaded the correct plugins. Please test them out and let me know. All my environments are already clean so I can’t really test these properly until next month’s updates are synced.
Thanks for this – This worked for me too.
Also, there appears to be a Typo in a few of the plugins – I’m getting errors about ‘Test-Exlusions’ and I’m figuring that should be ‘Test-Exclusions’.
Yep, as I mentioned above I uploaded the wrong version of the plugins. Hope to have that fixed today yet.
Just uploaded the correct plugins. Please test them out and let me know. All my environments are already clean so I can’t really test these properly until next month’s updates are synced.
Hello Bryan, first of all, thank you for your script.
Same problem here for using plugin, I set up Decline-Windows10Versions.ps1 as required and nothing happened. I use the script (version 2.4) only for WSUS with config.ini
It’s not an important problem, it was just to warn you.
config.ini:
DeclineByPlugins
DeclineByTitle=@(‘*Preview of*’,'(Preview)*’,’*Itanium*’,’*ia64*’,’*Beta*’,’*Version Next*’,’*ARM64-based*’,’*ARM-based*’)
;DeclineLastLevelOnly
DeclineSuperseded
DeleteDeclined
;ExcludeByProduct=@(‘*name*’,’*name*’)
;ExcludeByTitle=@(‘*name*’,’*name*’)
ExclusionPeriod=1
;FirstRun
Force
;IncludeByProduct=@(‘*name*’,’*name*’)
LogFile=C:\Scripts\Invoke-DGASoftwareUpdateMaintenance\Invoke-DGASoftwareUpdateMaintenance.log
;MaxLogSize=2621440
RunCleanUpWizard
StandAloneWSUS=****************
;StandAloneWSUSPort=8530
;StandAloneWSUSSSL=$False
;SyncLeadTime=5
UpdateListOutputFile
UseCustomIndexes
;WhatIfPreference
All Updates = 27256 Invoke-DGASoftwareUpdateMaintenance 22/10/2018 15:23:38 4796 (0x12BC)
All Declined Updates = 13852 Invoke-DGASoftwareUpdateMaintenance 22/10/2018 15:23:38 4796 (0x12BC)
All Updates Except Declined = 13404 Invoke-DGASoftwareUpdateMaintenance 22/10/2018 15:23:38 4796 (0x12BC)
All Superseded Updates = 681 Invoke-DGASoftwareUpdateMaintenance 22/10/2018 15:23:38 4796 (0x12BC)
Superseded Updates Declined = 859 Invoke-DGASoftwareUpdateMaintenance 22/10/2018 15:23:38 4796 (0x12BC)
Updates Declined by Title for *Version Next* = 0 Invoke-DGASoftwareUpdateMaintenance 22/10/2018 15:23:38 4796 (0x12BC)
Updates Declined by Title for *ARM64-based* = 0 Invoke-DGASoftwareUpdateMaintenance 22/10/2018 15:23:38 4796 (0x12BC)
Updates Declined by Title for *ARM-based* = 0 Invoke-DGASoftwareUpdateMaintenance 22/10/2018 15:23:38 4796 (0x12BC)
Updates Declined by Title for *Preview of* = 0 Invoke-DGASoftwareUpdateMaintenance 22/10/2018 15:23:38 4796 (0x12BC)
Updates Declined by Title for *Itanium* = 0 Invoke-DGASoftwareUpdateMaintenance 22/10/2018 15:23:38 4796 (0x12BC)
Updates Declined by Title for *ia64* = 0 Invoke-DGASoftwareUpdateMaintenance 22/10/2018 15:23:38 4796 (0x12BC)
Updates Declined by Title for *Beta* = 0 Invoke-DGASoftwareUpdateMaintenance 22/10/2018 15:23:38 4796 (0x12BC)
Updates Declined by Title for (Preview)* = 0 Invoke-DGASoftwareUpdateMaintenance 22/10/2018 15:23:38 4796 (0x12BC)
Updates Declined by Title (Total) = 0 Invoke-DGASoftwareUpdateMaintenance 22/10/2018 15:23:38 4796 (0x12BC)
Updates Declined by Plugin for Decline-Windows10Versions = 0 Invoke-DGASoftwareUpdateMaintenance 22/10/2018 15:23:38 4796 (0x12BC)
Updates Declined by Plugin (Total Unique) = 0 Invoke-DGASoftwareUpdateMaintenance 22/10/2018 15:23:38 4796 (0x12BC)
Total Newly Declined Updates = 498 Invoke-DGASoftwareUpdateMaintenance 22/10/2018 15:23:38 4796 (0x12BC)
Total Newly Deleted Updates = 0 Invoke-DGASoftwareUpdateMaintenance 22/10/2018 15:23:38 4796 (0x12BC)
Total Active Updates = 12906 Invoke-DGASoftwareUpdateMaintenance 22/10/2018 15:23:38 4796 (0x12BC)
Total Updates = 27256 Invoke-DGASoftwareUpdateMaintenance 22/10/2018 15:23:38 4796 (0x12BC)
my modification in Decline-Windows10Versions.ps1 :
$UnsupportedVersions = @(“1507″,”1511″,”1607″,”1703”)
Just uploaded the correct plugins. Please test them out and let me know. All my environments are already clean so I can’t really test these properly until next month’s updates are synced.
So, I downloaded your new files but now I’m stuck with this :
the script seems to stop using stand alone wsus parameter.
With the 2.4.0 version, it’s ok but … I manually cleaned my environnment but I will try the plugin as soon as possible and I keep you posted.
Invoke-DGASoftwareUpdateMaintenance started (Version 2.4.1). Invoke-DGASoftwareUpdateMaintenance 30/10/2018 10:13:26 3200 (0x0C80)
Currently, this script must be ran on a primary site server. When the CM 1706 reaches critical mass this requirement might be removed. Invoke-DGASoftwareUpdateMaintenance 30/10/2018 10:13:26 3200 (0x0C80)
THe bug I found was in the config file parsing section. \d will find an integer in hostnames that have numbers in them, which I imagine a whole lot of folks do. This causes the config parse to fail for any string with a number in it that’s in the config file.
change line (around) 860 to } ElseIf ($Data[1] -match “^\d+$”) {
Have you considered porting this over to GitHub? It would provide several advantages, as well as making easier for the community to get involved (if desirable)
Oh, it’s on GitHub in my ‘super secret’ public repository. I haven’t really advertised though for reasons that probably aren’t good reasons. Mostly that I don’t want people running versions that are WIP in their prod environments.
Brian is there anything specific you have to do remove the inplace upgrade to Windows 10? I copied the plugin from disabled folder to the plugins root and executed but it does not detect any IPU titles even though they appear in my WSUS.
I’m assuming you’re talking about the new Decline-Windows7IPUs plugin? That one should work without any need for modification. In your WSUS console do you see ‘Windows 7 and 8.1 upgrade to Windows 10’ updates?
Yes they do. When I ran the script it comes back with 0 items but the count is about 50. I also have the Office 365 plugin running and set to leave only the current release but all the releases are still listed in the console.
Just to clarify, when you say the script comes back with 0 items you mean that the log lists the specific plugins you’re talking about in the summary and that they each found 0 updates?
Correct. Here is an exert from the log:
Updates Declined by Plugin for Decline-Windows7IPUs = 0
Updates Declined by Plugin for Decline-Windows10Languages = 0
Updates Declined by Plugin for Decline-Windows10Versions = 0
Updates Declined by Plugin for Decline-Windows10Editions = 0
Updates Declined by Plugin for Decline-Office365Editions = 0
When I execute query for the Windows 7 and 8.1 upgrade to Windows 10* updated I have a count of 378. Same goes for Office 365 I have it configured for the following supported editions:
“Office 365 Client Update – Monthly Channel Version”,
“Office 365 Client Update – Monthly Channel \(Targeted\) Version”,
“Office 365 Client Update – Semi-annual Channel Version”,
“Office 365 Client Update – Semi-annual Channel \(Targeted\) Version”
I also have $LatestVersionOnly set to $True.
Yet my console still shows all the deferred channel, first channel releases and multiple versions. I extract some of the code just to get $updates and then executed both the Decline-Office365Editions and the Decline-Windows7IPUs. Both $Office365Updates and $WindowsIPUUpdates returned values. It seems to me the information collected is not being passed back to the main script when it executes the Plugin.
Just uploaded the correct plugins. Please test them out and let me know. All my environments are already clean so I can’t really test these properly until next month’s updates are synced.
Dude, you’re too kind.