Easily Find Unused Application and Package in MECM

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

Git Link : GitHub – codaamok/Get-CMUnusedSources: A PowerShell script that will tell you what folders are not used by Microsoft Endpoint Manager Configuration Manager.

*** Keep us updated with your feedback and suggestions ***

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: