SCCM/MECM WSUS Maintenance Guide

Date Created: Feb 22, 2023 (UTC)
Last Updated: Feb 22, 2023 (UTC)

WSUS maintenance is a requirement, not an option, when it comes to maintaining a healthy WSUS infrastructure. There are WSUS cleanup scripts out on the internet that decline superseded, expired and obsolete updates in WSUS. Those scripts are for Standalone WSUS environments. In a Configuration Manager environment you don't need to run those scripts because Configuration Manager will automatically do that for you, provided you enable WSUS Maintenance on the Software Update Point.

Recently I saw a post in the SCCM community on Reddit regarding WSUS in a Configuration Manager environment. More than one person told the OP that ConfigMgr clients don't directly talk to the WSUS server, only the SUP does. This is an incorrect statement. While the client downloads content from the DP's and gets policy from ConfigMgr, the update metadata the client downloads comes from WSUS. You can verify this by starting a packet capture on a workstation and then running the Software Updates Scan Cycle action from the Configuration Manager control panel applet. You'll see traffic on port 8530/8531 directly to the WSUS server.

Since clients connect to WSUS on a regular basis to get metadata, keeping the WSUS environment healthy is important.

Contents

This post is divided into the following sections

Configuration Manager Settings

Since Configuration Manager 1906, Microsoft has included options on the Software Update Point Component to manage WSUS.



  • Supersedence Rules Tab

    • These options tell Configuration Manager how long Superseded Updates should remain in the console before being expired. The default of 3 months should be sufficient for most environments. Leaving superseded updates in the console for three months allows you to not only report on the previous month's patch success/failure rate, but also allows machines to still get patched while you are testing the new update. If the new update breaks something, you can at least continue to deploy the old one.
  • WSUS Maintenance Tab

    All of these options should be enabled.

    • Decline expired updates in WSUS according to supersedence rules

      • When this option is selected, superseded updates will be declined in WSUS based on the settings configured in the Supersedence Rules tab
    • Add non-clustered indexes to WSUS database

      • When this option is selected, Configuration Manager will add non-clustered indexes to the SUSDB. This significantly improves WSUS performance and should prevent synchronization timeout errors.
    • Remove obsolete updates from the WSUS database

      • Obsolete updates are unused updates and update revisions in the WSUS database. Generally speaking, an update is considered obsolete once it's no longer in the Microsoft Update Catalog and it isn't needed by other updates as a prerequisite or dependency. When this option is selected, Configuration Manager will automatically remove obsolete updates from the WSUS database

WSUS Settings

As per Microsoft, the out of the box settings on the IIS Application Pool used by WSUS need be updated. This allows WSUS to perform better and prevents the application pool from constantly recycling, which can cause scan errors on clients.

The following settings need to be changed on the WsusPool in IIS

Setting name Value
Queue Length 2000 (up from default of 1000)
Idle Time-out (minutes) 0 (down from the default of 20)
Ping Enabled False (from default of True)
Private Memory Limit (KB) 0 (unlimited, up from the default of 1,843,200 KB)
Regular Time Interval (minutes) 0 (to prevent a recycle, and modified from the default of 1740)

Manually Declining Updates from WSUS

In most cases, manually Declining updates in WSUS is not required. But if you want to, you can, provided you are careful when doing it.

One reason you may want to manually decline updates is to shrink the amount of metadata clients have to download when running scans. For example, if you don't have any ARM64 devices in your environment, there is no reason for clients to download the metadata for those updates. The same goes for x86 updates if your environment only has x64 devices. Even if you aren't deploying those updates through ConfigMgr, the clients will still scan against them if they are not declined in WSUS. This is how the Required, Not Required and Installed columns gets populated in the All Software Updates view in the ConfigMgr console.

To manually decline updates, open the WSUS console and click on Search. In the search field type the text you want to search for, like ARM64. The text you are searching for should exist in the Title of the update. Then click Find Now. In the results window highlight every update you want to decline, then right click and select Decline. Click Yes to confirm. Refresh the WSUS console and you'll now see the updates are no longer showing and are hidden. After a sync, the Configuration Manager console will show these updates as expired and the icon on the update will be a black X.

You only need to do this on the primary WSUS server. Declined updates will sync to downstream replicas.

There are PowerShell scripts out on the internet that automate Declining updates in WSUS. I've seen some that automatically decline ARM64, x86, Itanium and unneeded languages. There are no issues running them on your primary WSUS server as long as all they do is Decline updates, not delete them.


WSUS Maintenance Activities

As per The complete guide to WSUS and Configuration Manager SUP maintenance, you do not have to manually run maintenance tasks against WSUS because Configuration Manager handles the maintenance after each synchronization. The only exception is backup and reindexing of the WSUS database. That needs to be done manually or on a schedule you set up.

While on the topic, you should also enable in Configuration Manager under Site Maintenance the Backup Site Server and Rebuild Indexes tasks.

Reindexing

It is recommended to reindex the WSUS database once per week, or at a bare minimum, once per month. You can do this manually, or automate it using a SQL Maintenance plan. Just make sure you don't schedule the reindex at the same time a sync is running. The SQL code to reindex the WSUS database can be found here. If you are using a WID database, you'll need to use task scheduler and the SQLCMD utility if you want to automate reindexing the database.

Deleting Unneeded Update Files

Configuration Manager does not download Microsoft update files to the WsusContent folder, it only downloads license agreements for updates that require them. For Microsoft updates, instead of using WsusContent, Configuration Manager downloads the source files to the file share you selected when creating the Deployment Package. This keeps the size of the WsusContent folder small since license agreements are usually only a couple of KB in size. In most cases, the WsusContent folder does not need be cleaned up on a regular basis. If you deploy third party updates, then WsusContent does need to be cleaned up on a regular basis as third party updates are fully downloaded into the WsusContent folder (more on that in the PatchMyPC section).

To cleanup the WsusContent folder, run the following PowerShell script on the WSUS server.

[reflection.assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration") | out-null
$wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::GetUpdateServer();
$cleanupScope = new-object Microsoft.UpdateServices.Administration.CleanupScope;
$cleanupScope.CleanupUnneededContentFiles = $true
$cleanupManager = $wsus.GetCleanupManager();
$cleanupManager.PerformCleanup($cleanupScope);

Note: The DiskSpaceFreed in the results window will show how many Bytes were freed.



I currently have over 2000 available updates out of 8000 total in my console, and my WsusContent folder is less than 5 MB in size. As you can see above, after removing unneeded update files, I recovered 150 KB worth of space.


Additional Maintenance (optional)

After speaking with Microsoft, a technician recommended every 3-6 months to run the below script which deletes all of the declined/hidden updates from WSUS. This is done to keep the WSUS database from growing too large. There is no official Microsoft website that provides the script, nor is there a Microsoft website that says running the script is required.

Note: Even though a Microsoft technician provided me the script, and they run it themselves, the technician stated that as per Microsoft policy, the script is not officially supported by Microsoft.

Before running the script, you should understand how WSUS works and why you may want to run the script.

When Configuration Manager runs WSUS maintenance, superseded and expired updates are automatically Declined in WSUS. This turns the update icon in the Configuration Manager console to a black X. Updates in WSUS are only deleted once they become obsolete. Declined updates will still remain, even if you directly run the WSUS Server Cleanup Wizard.

From a client perspective, this isn't an issue since Windows Clients do not scan against Declined updates. This limits the metadata the client has to download.



As you can see from the above screen shot, there are 2019 available updates out of 8271 total in WSUS. The 6252 updates that are not showing are all Declined.

Warning
As per Microsoft, In a WSUS hierarchy, it is strongly recommended that you run the cleanup process on the lower-most, downstream/replica WSUS server first, and then move up the hierarchy. Incorrectly running cleanup on any upstream server prior to running cleanup on every downstream server can cause a mismatch between the data that is present in upstream databases and downstream databases. The data mismatch can lead to synchronization failures between the upstream and downstream servers.

Updates that are deleted from the primary WSUS database do not get removed from downstream replicas during a sync. If you just run the cleanup script on the primary WSUS server, on the next sync cycle all of the previously declined updates on the downstream replicas will flip to Not Approved, instead of being deleted. This happens because during the sync cycle, the downstream replica no longer sees the update in the primary server as declined, so the downstream replica changes the update to Not Approved.

This substantially increases the amount of metadata clients have to download, causes a mismatch between databases, and creates the problem you are trying to avoid. This happened at one of my clients who was running cleanup scripts only on the primary WSUS server. They had 7,695 available updates on the primary server, and 26,981 available updates on the downstream replica.

If this happened in your environment, you have two choices to fix the problem. You can either reinstall SUP/WSUS on the downstream replica servers, or you can follow the process in this blog post to fix it. Once completed, the databases should be back in sync.

As per Microsoft, disable sync before running this script. Having a sync run during this process may cause issues.

-- To check number of hidden updates that will be deleted
SELECT * FROM tbUpdate WHERE isHidden = 1

-- To delete all of the hidden updates 
delete from tbrevisionlanguage where revisionid in (select revisionid from tbRevision where LocalUpdateId in (select LocalUpdateId from tbUpdate where ishidden=1))
delete from tbProperty where revisionid in (select revisionid from tbRevision where LocalUpdateId in (select LocalUpdateId from tbUpdate where ishidden=1))
delete from tbLocalizedPropertyForRevision where revisionid in (select revisionid from tbRevision where LocalUpdateId in (select LocalUpdateId from tbUpdate where ishidden=1 ))
delete from tbFileForRevision where revisionid in (select revisionid from tbRevision where LocalUpdateId in (select LocalUpdateId from tbUpdate where ishidden=1 ))
delete from tbInstalledUpdateSufficientForPrerequisite where prerequisiteid in (select Prerequisiteid from tbPreRequisite where revisionid in (select revisionid from tbRevision where LocalUpdateId in (select LocalUpdateId from tbUpdate where ishidden=1 )))
delete from tbPreRequisite where revisionid in (select revisionid from tbRevision where LocalUpdateId in (select LocalUpdateId from tbUpdate where ishidden=1 ))
delete from tbDeployment where revisionid in (select revisionid from tbRevision where LocalUpdateId in (select LocalUpdateId from tbUpdate where ishidden=1 ))
delete from tbXml where revisionid in (select revisionid from tbRevision where LocalUpdateId in (select LocalUpdateId from tbUpdate where ishidden=1 ))
delete from tbPreComputedLocalizedProperty where revisionid in (select revisionid from tbRevision where LocalUpdateId in (select LocalUpdateId from tbUpdate where ishidden=1 ))
delete from tbDriver where revisionid in (select revisionid from tbRevision where LocalUpdateId in (select LocalUpdateId from tbUpdate where ishidden=1))
delete from tbFlattenedRevisionInCategory where revisionid in (select revisionid from tbRevision where LocalUpdateId in (select LocalUpdateId from tbUpdate where ishidden=1))
delete from tbRevisionInCategory where revisionid in (select revisionid from tbRevision where LocalUpdateId in (select LocalUpdateId from tbUpdate where ishidden=1))
delete from tbMoreInfoURLForRevision where revisionid in (select revisionid from tbRevision where LocalUpdateId in (select LocalUpdateId from tbUpdate where ishidden=1))
delete from tbBundleAtLeastOne where bundledid in (select bundledid from tbBundleAll where revisionid in (select revisionid from tbRevision where LocalUpdateId in (select LocalUpdateId from tbUpdate where ishidden=1)))
delete from tbBundleAll where revisionid in (select revisionid from tbRevision where LocalUpdateId in (select LocalUpdateId from tbUpdate where ishidden=1))
delete from tbSecurityBulletinForRevision where revisionid in (select revisionid from tbRevision where LocalUpdateId in (select LocalUpdateId from tbUpdate where ishidden=1))
delete from tbKBArticleForRevision where revisionid in (select revisionid from tbRevision where LocalUpdateId in (select LocalUpdateId from tbUpdate where ishidden=1))
delete from tbRevisionSupersedesUpdate where revisionid in (select revisionid from tbRevision where LocalUpdateId in (select LocalUpdateId from tbUpdate where ishidden=1))
delete from tbBundleAtLeastOne where revisionid in (select revisionid from tbRevision where LocalUpdateId in (select LocalUpdateId from tbUpdate where ishidden=1))
delete from tbEulaProperty where revisionid in (select revisionid from tbRevision where LocalUpdateId in (select LocalUpdateId from tbUpdate where ishidden=1))
delete from tbRevision where LocalUpdateId in (select LocalUpdateId from tbUpdate where ishidden=1)
delete from tbUpdateSummaryForAllComputers where LocalUpdateId in (select LocalUpdateId from tbUpdate where ishidden=1)
delete from tbInstalledUpdateSufficientForPrerequisite where LocalUpdateId in (select LocalUpdateId from tbUpdate where ishidden=1)
delete from tbUpdate where ishidden = 1 

It may take a while for this script to complete.



As you can see from the above screen shot, after running the script on all of my WSUS servers, I now show 2019 updates out of a total of 2019 updates. All of the declined/hidden updates no longer exist in WSUS.

Since a lot of changes have been made to the database, each SUSDB should be reindexed using the code found here.

Don't forget to reenable sync after running the script on all of the WSUS servers. One sync is reenabled, force a full software updates synchronization from within Configuration Manager by putting a file called full.syn into the C:\Program Files\Microsoft Configuration Manager\inboxes\wsyncmgr.box folder. When the sync completes, check the wsyncmgr.log file for errors. Then on every WSUS server, load the WSUS console and make sure the number of updates are identical between the servers.


Remote Software Update Point Issues When Using a WID Database

In large environments it is common to have more than one Software Update Point. As per Microsoft, you should use a shared WSUS database when adding additional SUP's. This is not practical if your SUP's are geographically separated. In this instance you need to setup a separate WSUS database. The recommendation is to configure WSUS to use a SQL database, and not a Windows Internal Database (WID). This way Configuration Manager can add the non-clustered indexes and delete obsolete updates during normal sync cycles. It is not recommended to use SQLExpress because it has a limitation of 10 GB for the database size, is restricted to using only one CPU, and has other restrictions around memory.

If you have to use a WID database on a remote SUP, then the SMS_WSUS_SYNC_MANAGER component in Configuration Manager will show errors every time a sync runs. This is because by design, remote connections to a WID database are blocked. These errors will also show up in the wsyncmgr.log


You can prevent the index errors if you uncheck the option on the SUP that says Add non-clustered indexes to WSUS database. This doesn't mean you shouldn't add the non-clustered indexes, you should, it just means you will have to add them manually. It only needs to be done once per SUSDB, so this shouldn't be an issue. Just make sure to remember to add them if you ever create a new server, or rebuild a current one.

To check if the non-clustered indexes are installed, you can run the code found here on every SUSDB.

To manually add non-clustered indexes to the SUSDB, run the following code

-- Create custom index in tbLocalizedPropertyForRevision
USE [SUSDB]

CREATE NONCLUSTERED INDEX [nclLocalizedPropertyID] ON [dbo].[tbLocalizedPropertyForRevision]
(
     [LocalizedPropertyID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

-- Create custom index in tbRevisionSupersedesUpdate
CREATE NONCLUSTERED INDEX [nclSupercededUpdateID] ON [dbo].[tbRevisionSupersedesUpdate]
(
     [SupersededUpdateID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

Make sure you add the non-clustered indexes to every SUSDB in the environment, including downstream replicas

As for the errors on the SMS_WSUS_SYNC_MANAGER component related to deleting obsolete updates, I'd leave the option checked on the SUP. This way you can be assured they are at least being deleted on the primary WSUS server.

To manually delete obsolete updates from the remote WID database, connect to the WID database and run the following code. You should schedule this to run after every sync cycle.

DECLARE @updates TABLE (LocalUpdateID INT)
DECLARE @updateIDToDelete INT 
DECLARE @maxRunTime INT = 30
DECLARE @endtime DATETIME 
DECLARE @updateDeleted INT = 0
DECLARE @UniqueUpdateId uniqueidentifier 

SET NOCOUNT ON
SET @endtime = DATEADD(MINUTE, @maxRunTime, GETUTCDATE())

-- live with what WSUS team has

INSERT INTO @updates 
EXEC SUSDB.dbo.spGetObsoleteUpdatesToCleanup

SELECT TOP 1 @updateIDToDelete=LocalUpdateID FROM @updates

WHILE (getutcdate()<=@endtime)
BEGIN 
       SELECT TOP 1 @updateIDToDelete=LocalUpdateID FROM @updates
       IF @@ROWCOUNT = 0
       BREAK
       select @UniqueUpdateId = UpdateId from tbUpdate where LocalUpdateID = @updateIDToDelete
       Print 'Deleting update with ID: ' + CAST(@UniqueUpdateId AS VARCHAR(50))
       EXEC spDeleteUpdate @localUpdateID=@updateIDToDelete

       DELETE @updates WHERE LocalUpdateID=@updateIDToDelete
       SET @updateDeleted = @updateDeleted + 1
END 

-- return the total number of updates deleted
SELECT @updateDeleted

This is the exact code that is run during normal Configuration Manager sync cycles against non-WID databases, provided you enabled the option on the SUP to Remove obsolete updates from the WSUS database.


PatchMyPC WSUS Maintenance

While the above scripts are all you need to maintain your current WSUS environment, if you are running PatchMyPC, there are two more maintenance activities you may need to run depending on your configuration.

Before getting into the maintenance activities, you need to understand how WSUS works when it comes to Third Party Patching. There are two folder used by WSUS, WsusContent and UpdateServicesPackages. Both of these folders can be found under the root folder you selected when installing WSUS.

The WsusContent folder is used by WSUS to hold third party updates. Configuration Manager will use what is in WsusContent to populate Deployment Packages that contain third party updates. Standard Microsoft updates do not store anything other than license agreements in the WsusContent folder.

The UpdateServicesPackages folder is used by PatchMyPC to hold the source files for Third Party Updates that get put into the WsusContent folder. The UpdateServicesPackages folder does not get cleaned up during normal maintenance. This causes the folder to become bloated with obsolete updates when PatchMyPC updates are superseded and expired.

If you would like more information on this topic, you can view this video created by PatchMyPC that goes into a deep dive on the folders, and how they are used.

If you only have one WSUS server in your environment, you can have PatchMyPC automatically take care of cleaning up the UpdateServicesPackages folder by enabling the Option in their tool under WSUS Maintenance called Enable the automatic deletion and cleanup of the UpdateServicesPackages folder for declined third-party updates.

If you have one or more WSUS downstream replica servers (not sharing the primary SUSDB), it is not recommended that this option be enabled because currently the PatchMyPC tool only deletes the update from the primary WSUS database, and not from downstream replicas. Having it enabled when you have downstream replicas will introduce the same problem described earlier, meaning the databases will become out of sync and the deleted update will show as Not Approved in the downstream server. If PatchMyPC is able to get around this issue, I will update this post accordingly.

If you have automatic deletion and cleanup unchecked, you can manually delete the orphaned updates from UpdateServicesPackages by clicking the button that says 'Show unreferenced WSUS folders' in the WSUS Maintenance section of the PatchMyPC tool. When you click on the button a new window will pop up listing all of the orphaned folders in the UpdateServicesPackages folder. Click Select all then click Delete selected. Click Yes to confirm.

The tool will only show folders for updates that no longer exists in WSUS. If the updates are just declined, they will not appear. To get around this you can run the script provided by Microsoft that deletes declined/hidden updates mentioned previously in this post, then click Show unreferenced WSUS folders. At that point the orphaned updates should appear and you'll be able to cleanup the UpdateServicesPackages folder

The second maintenance activity you should run is deleting unneeded content from the WsusContent folder. To cleanup the WsusContent folder, run the following PowerShell script on the WSUS server.

[reflection.assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration") | out-null
$wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::GetUpdateServer();
$cleanupScope = new-object Microsoft.UpdateServices.Administration.CleanupScope;
$cleanupScope.CleanupUnneededContentFiles = $true
$cleanupManager = $wsus.GetCleanupManager();
$cleanupManager.PerformCleanup($cleanupScope);

Note: The DiskSpaceFreed in the results window will show how many Bytes were freed.


Conclusion

By following the recommendations in this post, your WSUS infrastructure should remain healthy.

As stated at the beginning, there are WSUS cleanup scripts out on the internet that decline superseded, expired and obsolete updates in WSUS. Those scripts are for Standalone WSUS environments. In a Configuration Manager environment you don't need to run those scripts because Configuration Manager will automatically do that for you, provided you enable WSUS Maintenance on the Software Update Point.


THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


Share this post:

Comments:

No one has commented yet. Be the first!