Configuration Manager (SCCM) | Application and Package Clean-up
Is it that time of year, when you are getting asked to make room on the MECM Distribution Points and perform some housing keeping in the Configuration Manager environment as you edge or reach full capacity and the IT budget just has no money to expand or replace the hardware?
Or you are like me and like to keep a tidy house, keep everything in order and clean up!
Whatever the case, then these scripts and steps could be for you.
It will help to identify potential unused applications and packages that could be removed or deleted from your estate to free up disk space on not only your Distribution Points but also your File Server where the source files reside
There are three scripts
CM-Cleanup.ps1 – Exports .csv of potential unused Applications and Packages
CM-RemoveFromDistribution.ps1 – Parses the exported .csv and removes content from distribution points
CM-DeleteObject.ps1 – Parses the exported .csv and deletes the object from Configuration Manager
The original ‘unused Applications and Packages script author was Matt Bobke Finding Unused SCCM Applications and Packages – Scripting Secures Success (mattbobke.com), but have made a few enhancements and added the extra scripts to remove from Distribution Points or to delete from MECM if you choose too.
<#
.SYNOPSIS
This script will export a .csv file showing all applications and packages that are not in use within Configuration Manage
.DESCRIPTION
Connect to your Configuration Manger PowerShell Console and execute. A .csv file of all applications and packages that are not in use
will be written to C:\Windows\Temp\CM_Apps.csv and C:\Windows\Temp\CM_Packages.csv
.NOTES
The original author was Matt Bobke : https://mattbobke.com/2018/05/06/finding-unused-sccm-applications-and-packages/ and enhancements by Skatterbrainz
and has since been updated by S.P.Drake
Full functionality requires Configuration Manager Release 2010 or later
File Name : CM - Cleanup.ps1
Author : S.P.Drake
Version : 1.1 : Added content distribution count, package type translation and ConfigurationManager compatibility
Version : 1.0 : Enhanced package filter, exclude predefined packages, Configuration Manager Client Piloting Package and DefaultImages and dependant programs
#>
# Assign report folder
$ReportFolder = "$env:systemdrive\Windows\Temp"
# Suppress Fast check not in use warning message
$CMPSSuppressFastNotUsedCheck = $true
Write-Verbose "Get - distribution points and package content" -Verbose
# Initialise $packageContentCount array
$packageContentCount = @()
# Get all distribution points servers
$allDPs = Get-CMDistributionPointInfo | Select-Object -ExpandProperty ServerName
# Get the content on each distribution pint
foreach ($dp in $allDPs){
$packageContentCount += Get-CMDeploymentPackage -DistributionPointName $dp | Select-Object -ExpandProperty PackageID }
# Group the content found on each distribution point to get a package distributed total count
$groupedPackageContentCount = $packageContentCount | Sort-Object $_.Name | Group-Object $_.Name
Write-Verbose "Get - applications" -Verbose
# Get all Applications that are not deployed, have no dependant task sequences, no deployment types that depend on this application and not superseded - (can't filter packages like applications)
$AllApplications = Get-CMApplication | Where-Object {($_.IsDeployed -eq $False) -and ($_.NumberofDependentTS -eq 0) -and ($_.NumberofDependentDTs -eq 0)}
Write-Verbose "Get - packages" -Verbose
# If running ConfigurationManager version 2010 or later
If ((Get-Module -Name ConfigurationManager).Version.Major -ge 5 -and (Get-Module -Name ConfigurationManager).Version.Minor -ge 2010){
# Get all Regular Packages that are not predefined packages and package name not 'Configuration Manager Client Piloting Package'
$RegularPackage = Get-CMPackage -Fast -PackageType RegularPackage | Where-Object {($_.IsPredefinedPackage -eq $false) -and ($_.Name -ne 'Configuration Manager Client Piloting Package')}
# Get all Driver Packages
$DriverPackage = Get-CMPackage -Fast -PackageType Driver
# Get all Operating System Image packages
$ImageDeploymentPackage = Get-CMPackage -Fast -PackageType ImageDeployment
# Get all Operating System Upgrade packages
$OSInstallPackagePackage = Get-CMPackage -Fast -PackageType OSInstallPackage
# Get all Boot Image packages, DefaultImage=False
$BootImagePackage = Get-CMPackage -Fast -PackageType BootImage | Where-Object {$_.DefaultImage -eq $false}
# Combine all packages lists together
$AllPackages = ($RegularPackage + $DriverPackage + $ImageDeploymentPackage + $OSInstallPackagePackage + $BootImagePackage)}
else{
# Get all Regular Packages that are not predefined packages and package name not 'Configuration Manager Client Piloting Package'
$AllPackages = Get-CMPackage -Fast | Where-Object {($_.IsPredefinedPackage -eq $false) -or ($_.Name -ne 'Configuration Manager Client Piloting Package')}
}
Write-Verbose "Get - deployments" -Verbose
# Get all deployments, filter to just a list of their package IDs
$DeploymentPackageIDs = Get-CMDeployment | Select-Object PackageID | Sort-Object | Get-Unique -AsString
Write-Verbose "Get - task sequences" -Verbose
# Get all task sequences that have references and not disabled (cannot use -Fast)
$FilteredTaskSequences = Get-CMTaskSequence | Where-Object { ($_.References -ne $null) -and ($_.TsEnabled -ne $false) }
# If filtered task Sequence found
if ($FilteredTaskSequences.Count -ne 0) {
Write-Verbose "Filter - task sequence references only" -Verbose
# Filter task sequences to just a list of their references (cannot use -Fast)
$TSReferences = ( $FilteredTaskSequences | Select-Object References).References.Package | Sort-Object | Get-Unique -AsString
Write-Verbose "Filter - task sequence dependant programs only " -Verbose
# Filter task Sequence’s dependant programs, filter to just a list of their references (cannot use -Fast)
$TSDependentProgram = $FilteredTaskSequences | Select-Object DependentProgram | Foreach-Object {$_.DependentProgram.Split(';;')[0]} | Sort-Object | Get-Unique -AsString
}
Write-Verbose "Filter - applications and packages that are not active" -Verbose
# Initialise FinalApplications
$FinalApplications = New-Object -TypeName 'System.Collections.ArrayList'
# Initialise FinalPackage
$FinalPackages = New-Object -TypeName 'System.Collections.ArrayList'
# Append content distribution count to the application list
foreach ($App in $AllApplications) {
$App | Add-Member -MemberType NoteProperty DPCount -Value ($groupedPackageContentCount | Where-Object {$_.Name -eq $App.PackageID} | Select-Object -ExpandProperty count)
$FinalApplications.Add($App) | Out-Null
}
# Filter packages to only those that do not have their PackageID in the list of references and append content distribution count to the package list
foreach ($Package in $AllPackages) {
if (($Package.PackageID -notin $TSReferences) -and ($Package.PackageID -notin $DeploymentPackageIDs.PackageID) -and ($Package.PackageID -notin $TSDependentProgram)) {
$Package | Add-Member -MemberType NoteProperty DPCount -Value ($groupedPackageContentCount | Where-Object {$_.Name -eq $Package.PackageID} | Select-Object -ExpandProperty count)
$FinalPackages.Add($Package) | Out-Null
}
}
Write-Verbose "Export - applications and packages that are not active" -Verbose
# Export application list to .csv
$FinalApplications `
| Select-Object -Property LocalizedDisplayName, PackageID, DateCreated, DateLastModified, IsDeployable, IsEnabled, IsExpired, IsHidden, IsSuperseded, DPCount `
| Sort-Object -Property LocalizedDisplayName `
| Export-Csv -Path "$ReportFolder\CM_Applications.csv" -NoTypeInformation
# Export package list to .csv
$FinalPackages `
| Select-Object Name, @{Name = "PackageType";Expression = {$_.PackageType -replace '258','BootImage' -replace '3','Driver' -replace '257','ImageDeployment' -replace '259','OSInstallPackage' -replace '0','RegularPackage'}},PackageID, SourceDate, LastRefreshTime, PkgSourcePath, DPCount `
| Sort-Object -Property PackageType, Name `
| Export-Csv -Path "$ReportFolder\CM_Packages.csv" -NoTypeInformation
Write-Verbose "Done - CSVs stored in $ReportFolder" -Verbose
# Future releases will have the option for Report Only, Prompt on Delete, or Auto Delete
Connect to CM Environment and launch – Connect via Windows PowerShell ISE

Run default CM Environment script that is loaded when open Windows PowerShell ISE

Open the attached script – (CM-Cleanup.ps1) in a new tab or copy the script in a new tab and run

When the script is complete you will find two .csv in C:\Windows\Temp , CM-Appications.csv and CM-Packages.csv , this will be a list of inactive applications and packages.
Personally, I find it helpful then to import into Microsoft Excel, so you can filter and sort to view the data easily and make informed decisions.
NOTE: A task sequence that has been disabled, will be treated as it does not exist.
Therefore, if an application or package is ‘only’ associated with this task sequence and the application or package is ‘not’ independently deployed it will be counted as unused.
This functionality can be changed by changing line 70 to below syntak
$FilteredTaskSequences = Get-CMTaskSequence | Where-Object { $_.References -ne $null }
What does the script actually do
It searches for all
– Applications
– Packages
– Driver Packages
– Operating System Images
– Operating System Upgrade Packages
– Boot Images
It then identifies if the below statements are True
– Not Deployed
– Not associated with a Task Sequence, or Task Sequence Disabled
– Not a dependant to another application and not a dependant
– Not a predefined MECM Package
Then for each identified Application, Package, Driver Package, Operating System Image, Operating System Upgrade Package, Boot Image it will output to .csv with the Package Name, ID, and the Number of DPs it has been distributed to.
How the results in the script can be interpreted
It shows all Packages, Driver Packages, Operating System Images, Operating System Upgrade Packages, Boot Image that potentially can be deleted from MECM or removed from DPs
The word potentially has been used, as some side cases could be that the Package, Application etc fits the search criteria, but it could be a Package, Application etc that is work-in-progress, and is in the process of being created but not yet deployed or attached to a Task Sequence
What the script will not detect
All the Packages, Driver Packages, Operating System Images, Operating System Upgrade Packages, Boot Image that has been deployed to an empty or unused collection, or attached to an unused Task Sequence that is not disabled.
For example, an application that is not needed by the estate anymore, but is still deployed to an empty, unused collection, or attached to an unused Task Sequence that is not disabled, will not be identified as a package that can be safely removed.
Get the script from the below link
GitHub : https://github.com/Drakey2000/CommunityHelper/tree/master/CM-Cleanup
So to run a full end to end cleanup of your MECM environment a typical approach could be to run a Bi-Annual or Annual clean up with the following steps
1. Perform an inventory of all your Applications, Packages, and Task sequences and either remove the deployments, retire or disable those that are no longer required.
(if anyone has ideas to make this automated would be great to hear, although thinking this will have to be a manual step)
2. Run CM-Cleanup.ps1 to confirm and report on all the identified Applications, Packages, Driver Packages, Operating System Images, Operating System Upgrade Packages, Boot Images that can be deleted
3. From the exported list remove the content from All your DPs with the script – CM-RemoveFromDistribution.ps1
4. Wait a few weeks to see if anyone shouts, to ensure that they are not needed
5. Delete the identified Applications, Packages, Driver Packages, Operating System Images, Operating System Upgrade Packages, Boot Images from MECM with the script – CM-DeleteObject.ps1
6. Run Adam Cook Script to identify unused source folders, in case the same source folder has been used by multiple Applications or Packages
7. Delete all unused source folders
8. Cleanup complete……..until next time!
Excellent….you now have a reporting tool to identify potential unused applications and packages in Microsoft Endpoint Configuration Manager, which can be removed to free up disk space on your Distribution Points, but I hear you…..
How do you remove the source contents on our file servers that are now redundant?
Check this guy out Adam Cook….not going to say too much more, but he has the answers. It is a backwards search, searching the Source Content File Storage and then determining if its in use by any Microsoft Endpoint Configuration Manager objects
*** Keep us updated with your feedback and suggestions ***
Awesome script, worked great. Thanks
LikeLike