Automating Content Library Content Clean Up

So earlier this year the great ConfigMgr team released CB1702 and in turn a couple of tools including the Content Library Cleanup Tool. I previously blogged about using the tool ( and the possible storage space savings that could be achieved.

So it occurred to me that this new tool might not be utilised as effectively as it could be when it comes to providing a holistic approach to content maintenance across your ConfigMgr estate, the reason being that command line tools are great but can often be a single use tool that people forget about. So let’s talk about automating this tool..

Automating The Command-Line Tool

So this got me thinking that a PowerShell script which wrapped the functions of the new tool could assist in providing ConfigMgr administrators with an easier means of automating / managing the process. If you are familiar with PowerShell you could of course draft up your own script to make use of the tool in much the same manner that I am going to talk about, but I am catering for those who also are not (yet) comfortable about setting out down this path.

GUI or PS Script – Your Choice

So wanting to cater for both those who prefer a GUI and those who prefer a PS script,  here we have the SCConfigMgr Content Cleaner Tool.

Technet Download Link 

The tool can be used as a GUI to run on demand, or via the schedule job button it schedules a PS script to take care of the maintenance of your distribution points.

On launch it will check the following prerequisites are discovered;

  • ConfigMgr PS commandlets
  • Content Library Cleanup

Note: It should be used on your site server with an account with full rights to your ConfigMgr environment.

After the prerequisites are met you have two options, you can either analyse all discovered distribution points for left over packages or you can schedule a clean up job to run on a daily basis.

Analyse Job

The analyse job does exactly what is says on the tin, it looks at all of your distribution points within the same site for orphaned packages. Each of the server names and their associated values are read out of the logs and displayed in a data grid view within the GUI. The total potential savings figure provides you with an indication of the savings, of course these might vary due to conditions including data deduplication.

Clean Up Job

After the distribution points have been analysed, you will have the option to click on the “Clean Content Libraries Now” button which launches the delete data process. At this point any distribution points which had been identified as transferring data in the analyse job will be skipped, as will any distribution points with a null saving figure. All remaining distribution points will be processed in sequence.

Schedule Job

To schedule the clean up of distribution points, all we need to do is select a time from the drop down list, select a folder for the script to copy a clean up script into (for the purpose of a scheduled task) and enter credentials which have both log on as a batch service and ConfigMgr full access rights.

The credentials will be validated with Active Directory when setting the schedule and error messages relating to this and the path will be displayed within the form;

Upon providing user authentication being verified, the ContentCleanupRemoval.ps1 script is copied down to the path specified and a scheduled task set up;

The schedule calls the PowerShell script to scan and automatically take care of your orphaned content. Below you have the full script;

   Created with: 	SAPIEN Technologies, Inc., PowerShell Studio 2017 v5.4.139
   Created on:   	05/05/2017 15:11
   Created by:   	Maurice.Daly
   Filename:     	ContentCleanupRemoval.ps1
    This script works in conjuction with the content library clean up tool 
    in SCCM CB1702 onwards to automate the function of deleting orphaned 
    content on your distribution ponts

    Use : This script is provided as it and I accept no responsibility for any issues arising from its use.
    Twitter : @modaly_it
    Blog :


function Get-ScriptDirectory
  param ()
  if ($null -ne $hostinvocation)
    Split-Path $hostinvocation.MyCommand.path
    Split-Path $script:MyInvocation.MyCommand.Path

[string]$ScriptDirectory = Get-ScriptDirectory

# Logging Function
function Write-CMLogEntry
  param (

[parameter(Mandatory = $true, HelpMessage = “Value added to the log file.”)]

[ValidateNotNullOrEmpty()] [string]$Value,

[parameter(Mandatory = $true, HelpMessage = “Severity for the log entry. 1 for Informational, 2 for Warning and 3 for Error.”)]

[ValidateNotNullOrEmpty()] [ValidateSet(“1”, “2”, “3”)] [string]$Severity,

[parameter(Mandatory = $false, HelpMessage = “Name of the log file that the entry will written to.”)]

[ValidateNotNullOrEmpty()] [string]$FileName = “ScheduledContentLibraryCleanup-$(Get-Date -Format dd-MM-yyyy).log” ) # Determine log file location $LogFilePath = Join-Path -Path $ScriptDirectory -ChildPath $FileName # Construct time stamp for log entry $Time = -join @((Get-Date -Format “HH:mm:ss.fff”), “+”, (Get-WmiObject -Class Win32_TimeZone | Select-Object -ExpandProperty Bias)) # Construct date for log entry $Date = (Get-Date -Format “MM-dd-yyyy”) # Construct context for log entry $Context = $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name) # Construct final log entry $LogText = “<![LOG[$($Value)]LOG]!><time=””$($Time)”” date=””$($Date)”” component=””ScheduledContentLibraryCleanup”” context=””$($Context)”” type=””$($Severity)”” thread=””$($PID)”” file=””””>” # Add value to log file try { Add-Content -Value $LogText -LiteralPath $LogFilePath -ErrorAction Stop } catch [System.Exception] { Write-Warning -Message “Unable to append log entry to ScheduledContentLibraryCleanup.log file. Error message: $($_.Exception.Message)” } } # Import SCCM PowerShell Module $ModuleName = (Get-Item $env:SMS_ADMIN_UI_PATH).parent.FullName + “\ConfigurationManager.psd1” Write-CMLogEntry -Value “RUNNING: ConfigMgr PS commandlets path set to $ModuleName” -Severity 1 # Specify SCCM Site Vairables $SiteServer = %SITESERVER% Write-CMLogEntry -Value “RUNNING: Site server identified as $SiteServer” -Severity 1 $SiteCode = %SITECODE% Write-CMLogEntry -Value “RUNNING: SMS site code identified as $SiteCode” -Severity 1 # Define Content Library Path Location $ContentLibraryExe = (“$($env:SMS_LOG_PATH | Split-Path -Parent)” + “\cd.latest\SMSSETUP\TOOLS\ContentLibraryCleanup\ContentLibraryCleanup.exe”) # Define Arrays $DistributionPoints = New-Object -TypeName System.Collections.ArrayList # Import SCCM Module Import-Module $ModuleName # Connect to Site Code PS Drive Set-Location -Path ($SiteCode + “:”) # List Distribution Points $DistributionPoints = @(((Get-CMDistributionPoint | Select-Object NetworkOSPath).NetworkOSPath).TrimStart(“\\”)) # Specify Temp Log Location $LogDir = ($ScriptDirectory + “\ContentLibraryCleanerLogs”) function CleanContent { param (

[parameter(Mandatory = $true)]


[parameter(Mandatory = $true)]


[parameter(Mandatory = $true)]

[String]$SiteCode ) try { Write-CMLogEntry -Value “CLEANING: Setting location to $ScriptDirectory” -Severity 1 Set-Location -Path $ScriptDirectory # Content Library Cleanup switches Write-CMLogEntry -Value “CLEANING: Setting execution switches to $ContentCleanupString” -Severity 1 $ContentCleanupStrings = “/q /ps $SiteServer /dp $DistributionPoint /sc $SiteCode /delete” Write-Host “Running process for $DistributionPoint” Write-CMLogEntry -Value “CLEANING: Starting clean up process for server $DistributionPoint” -Severity 1 $RunningProcess = Start-Process -FilePath $ContentLibraryExe -ArgumentList $ContentCleanupStrings -NoNewWindow -RedirectStandardOutput $($LogDir + “\$DistributionPoint-ScheduledDeletionJob.log”) -PassThru # Wait for Process completion While ((Get-Process | Where-Object { $_.Id -eq $RunningProcess.Id }).Id -ne $null) { Write-CMLogEntry -Value “CLEANING: Waiting for process PID:$($RunningProcess.Id) to finish” -Severity 1 sleep -Seconds 1 } # Get most recent log file generated $ContentCleanUpLog = Get-ChildItem -Path $LogDir -Filter *.log | Where-Object { ($_.Name -match “$DistributionPoint-ScheduledDeletionJob”) } | select -First 1 # Exception for reports with active distributions preventing the content rule from estimating space if ((Get-Content -Path $ContentCleanUpLog.FullName | Where-Object { $_ -match “This content library cannot be cleaned up right now” }) -ne $null) { Write-CMLogEntry -Value “CLEANING: $DistributionPoint currently has active transfers. Skipping.” -Severity 2 $RowData = @($DistributionPoint, “N/A – Active Transfers”) } else { Write-CMLogEntry -Value “CLEANING: Process completed” -Severity 1 } } catch [System.Exception] { Write-CMLogEntry -Value “ERROR: $($_.Exception.Message)” -Severity 3 Write-Warning -Message “ERROR: $($_.Exception.Message)” } Write-CMLogEntry -Value “Finished: Job Complete” -Severity 1 } foreach ($DistributionPoint in $DistributionPoints) { Write-CMLogEntry -Value “RUNNING: Starting clean up process for server $DistributionPoint” -Severity 1 CleanContent $DistributionPoint $SiteServer $SiteCode } # // ————- Clean Up Log Files ———– // # # Define Maximum Log File Age $MaxLogAge = 30 # Remove Logs Write-CMLogEntry -Value “CLEANING: Removing log files older than $MaxLogAge days old” -Severity 2 Get-ChildItem -Path $ScriptDirectory -Filter “ScheduledContentLibrary*.log” -File | Where-Object {$_.LastWriteTime -lt ((Get-Date).AddDays(-$MaxLogAge))} | Remove-Item


A full verbose log is created during every step of the process, this is located in a “ContentLibraryCleanerLogs” sub-folder of the folder from which you ran the tool. The logs can be viewed using CMTrace and have both warning and error outputs for troubleshooting purposes.

The contentlibrarytool logs are written to the %temp% folder as per the default, however the output from the process is again written into the “ContentLibraryCleanerLogs” sub-folder, however note that all bar the ContentLibraryCleanerTool main log is removed on each subsequent run.

For those scheduling the process, the logs are written into the directory you specified when scheduling the job.

Maurice Daly

Maurice has been working in the IT industry for the past 20 years and currently working in the role of Senior Cloud Architect with CloudWay. With a focus on OS deployment through SCCM/MDT, group policies, active directory, virtualisation and office 365, Maurice has been a Windows Server MCSE since 2008 and was awarded Enterprise Mobility MVP in March 2017. Most recently his focus has been on automation of deployment tasks, creating and sharing PowerShell scripts and other content to help others streamline their deployment processes.


  • I guess this does not solve the Problem that this tool cannot be used on a Primary site Server with a DP on it?

    Is there any clean way to remove unneeded objects from the Content library in this Scenario?

  • Hi Maurice,

    I like this tool, however i was hoping you able to help me find out what change i need to do in the script to use the ContentLibraryCleaner.exe that i have locally saved on each Distribution Point instead of using the site servers location?

    We have many DPs with different WLAN link speeds and this will help us to speed up the process.



  • Hi Maurice,

    I like the idea of this tool, but when I start it I stopped after checking of first DP with error message like:

    ERROR: Cannot convert value “8.451.030.916” to type “System.Int32”. Error: “Input string was not in a correct format.” ContentLibraryCleanerTool 19.09.2017 15:17:17 6740 (0x1A54)

    Can you help me with this issue?



Categories use cookies to ensure that we give you the best experience on our website.