SCCM Client Cache Auto-Cleanup

How to create a Compliance Baseline that will automatically keep your SCCM client cache folders purged of unnecessary data.

The Problem

It's simple. The SCCM client has no built-in method for automatically removing data from the cache folder once it's no longer needed. By default, the SCCM cache folder is 5GB, and you can customize that by passing arguments to the client installer and/or using the options on the Client Cache Settings page of the Client Settings properties.

But, no matter how big you allow that cache folder to be, it's going to fill up eventually. And when it does, you'll start running into issues with your larger deployments. Also, when a hard drive is nearing full capacity, lots of other things start to go wrong, not the least of which are Windows Updates. So, removing 10GB of stale data from the SCCM cache folder can make all the difference in such cases.

We just need that to happen automatically, since we can't rely on end users to monitor that and manually trigger the cleanup using the Configuration Manager Properties from the Control Panel. And that manual trigger requires local admin rights, which your users shouldn't have anyways.

Also, when this automated clean-up happens, we don't want to simply purge the entire contents of the ccmcache folder. This is an important distinction, because if we're automatically deleting all content from the cache folder, we'll probably end up deleting content that has been pre-staged for an upcoming required deployment. That data would then have to be downloaded again by the client, creating unneccessary network traffic.

The Solution

Using PowerShell, we can not only automate the clean-up of the cache folder, we can perform an intelligent clean-up that only removes data which is no longer needed for a future deployment. As previously mentioned, modifying the contents of the ccmcache folder requires local admin rights. And your users don't have those permissions, right? RIGHT? So, an SCCM compliance policy is a perfect way to deploy this. We can ensure it runs on a routine schedule, and we can have it run under System context.

If you don't care about the details and just want the code, then feel free to skip ahead. If, on the other hand, you're a responsible sysadmin and would never deploy a script in your environment without understanding exactly what it's doing, read on.

When we clean the ccmcache folder, we're looking to purge 2 types of data:

  1. Data associated with an active SCCM application/package, as long as
    a. that data is not needed for a currently running or future scheduled deployment, and
    b. that data hasn't been called upon by the SCCM client for a set number of days (our specified retention period).

  2. Orphaned data. This is data that is not associated with an active SCCM app or package (because that app has been removed from the SCCM library, for instance).

Let's look at these two cases individually. First, the unnecessary active data. This code will build an array of cache entries that are OK to be purged. The code comments explain each step.

# Specify Max Days For CCM Cache Entries
$MaxRetention = 3

# Connect To Resource Manager Com Object
$SCCMClient = New-Object -ComObject UIResource.UIResourceMgr

# List All Applications Due In The Future Or Currently Running
$PendingApps = @($SCCMClient.GetAvailableApplications() | ?{
    ($_.StartTime -gt (Get-Date)) -or ($_.IsCurrentlyRunning -eq 1)
})

# Create List Of Applications To Purge From Cache
$PurgeApps = @($SCCMClient.GetCacheInfo().GetCacheElements() | ?{
    # Choose apps that meet these criteria:
    ## we don't need it in the future
    ## it hasn't already been deleted
    ## we haven't used it for our retention duration
    
    ($_.ContentID -notin $PendingApps.PackageID) `
    -and ((Test-Path -Path $_.Location) -eq $true) `
    -and ($_.LastReferenceTime -lt (Get-Date).AddDays(- $MaxRetention))
})

Now, to find the orphaned data.

# Connect To Resource Manager Com Object
$SCCMClient = New-Object -ComObject UIResource.UIResourceMgr

# Get SCCM Client Cache Directory Location
$SCCMCacheDir = $SCCMClient.GetCacheInfo().Location

# Get all cache directories with an active association
$ActiveDirs = @($SCCMClient.GetCacheInfo().GetCacheElements() | %{ 
    Write-Output $_.Location
})

# Build an array of folders in ccmcache that don't have an active association
# by getting all child items of ccmcache and comparing to the list of
# ActiveDirs
$MiscDirs = @(Get-ChildItem -Path $SCCMCacheDir | ?{
    (($_.PsIsContainer -eq $true) -and ($_.FullName -notin $ActiveDirs)) 
})

And finally, we'll need a method to instruct the SCCM client to delete a cache item. For active associations, we need to use the DeleteCacheElement method of the SCCM client object. For orphaned data, we can simply delete it from the file system.

# Clean up Active cache items
foreach ($App in $PurgeApps) {
   $SCCMClient.GetCacheInfo().DeleteCacheElement($App.CacheElementID)
}

# Clean Up Orphaned cache items
foreach ($dir in $MiscDirs) {
    $dir  | Remove-Item -Recurse -Force
}

That covers everything we'll need to construct our compliance policy scripts.

Full Scripts

Putting these pieces together, we can build the scripts we need to define a compliance policy configuration item. Specifically, we'll want a discovery script and a remediation script. This is what I use in my environment:

Discovery script:

# Specify Max Days For CCM Cache Entries
$MaxRetention = 3

# Connect To Resource Manager Com Object
$SCCMClient = New-Object -ComObject UIResource.UIResourceMgr

# Get SCCM Client Cache Directory Location
$SCCMCacheDir = ($SCCMClient.GetCacheInfo().Location)

# List All Applications Due In The Future Or Currently Running
$PendingApps = @($SCCMClient.GetAvailableApplications() | ?{
    ($_.StartTime -gt (Get-Date)) -or ($_.IsCurrentlyRunning -eq 1)
})

# Create List Of Applications To Purge From Cache
$PurgeApps = @($SCCMClient.GetCacheInfo().GetCacheElements() | ?{
    ($_.ContentID -notin $PendingApps.PackageID) `
    -and ((Test-Path -Path $_.Location) -eq $true) `
    -and ($_.LastReferenceTime -lt (Get-Date).AddDays(- $MaxRetention))
})

# Get all cache directories with an active association
$ActiveDirs = @($SCCMClient.GetCacheInfo().GetCacheElements() | %{ 
    Write-Output $_.Location
})

# Build an array of folders in ccmcache that don't have an active association
$MiscDirs = @(Get-ChildItem -Path $SCCMCacheDir | ?{
    (($_.PsIsContainer -eq $true) -and ($_.FullName -notin $ActiveDirs)) 
})

# Add Old App & Misc Directories
$PurgeCount = $PurgeApps.Count + $MiscDirs.Count

# Return Number Of Applications To Purge
return $PurgeCount

This script returns the number of items that need to be purged. So, returning 0 would indicate compliance. Any number greater than zero should trigger automatic remediation.

For the remediation script, I use:

# Specify Max Days For CCM Cache Entries
$MaxRetention = 3

# Connect To Resource Manager Com Object
$SCCMClient = New-Object -ComObject UIResource.UIResourceMgr

# Get SCCM Client Cache Directory Location
$SCCMCacheDir = ($SCCMClient.GetCacheInfo().Location)

# List All Applications Due In The Future Or Currently Running
$PendingApps = @($SCCMClient.GetAvailableApplications() | ?{
    ($_.StartTime -gt (Get-Date)) -or ($_.IsCurrentlyRunning -eq 1)
})

# Create List Of Applications To Purge From Cache
$PurgeApps = @($SCCMClient.GetCacheInfo().GetCacheElements() | ?{
    ($_.ContentID -notin $PendingApps.PackageID) `
    -and ((Test-Path -Path $_.Location) -eq $true) `
    -and ($_.LastReferenceTime -lt (Get-Date).AddDays(- $MaxRetention))
})

# Get all cache directories with an active association
$ActiveDirs = @($SCCMClient.GetCacheInfo().GetCacheElements() | %{ 
    Write-Output $_.Location
})

# Build an array of folders in ccmcache that don't have an active association
$MiscDirs = @(Get-ChildItem -Path $SCCMCacheDir | ?{
    (($_.PsIsContainer -eq $true) -and ($_.FullName -notin $ActiveDirs)) 
})

# Track the number of failures encountered
$failures = 0

# Purge Apps No Longer Required
foreach ($App in $PurgeApps) {
    try {
        $SCCMClient.GetCacheInfo().DeleteCacheElement($App.CacheElementID)
    } catch {
        $failures++
    }
}

# Purge Orphaned Data
foreach ($dir in $MiscDirs) {
    try {
        $dir  | Remove-Item -Recurse -Force
    }
    catch {
        $failures++
    }
}

# Return Number Of Cleanup Failures
return $failures

This script returns the number of failures encountered while performing clean-up. Obviously, 0 is what we're looking for. If it returns anything higher, then that means an issue was encountered.

Deploying

To start running this configuration item, you'll need to add it to a configuration baseline and then deploy that baseline. Make sure you set the baseline to allow remediation! If you need detailed instructions for creating configuration items or baselines, start by reading through the Microsoft documentation.


I hope this was helpful. If you have any comments or questions, or if you have an idea about how to further improve this approach, you can connect with me via the comments below or via Twitter.

Show Comments