MSEndpointMgr

Using PowerShell for cleaning up Packages and Applications

I  recently spotted a request where someone was looking for a means of reporting on Applications and Packages which were not deployed or used in task sequences in their ConfigMgr environment. The reason of course was for clean up purposes, as it is often the case that software gets added in for test or other purposes and never makes it through to being deployed or is withdrawn but not removed from the database.

How to report on these items?

There are several ways to achieve this but I thought wouldn’t it be fun to use PowerShell. So here is a link to a script that will display either a list of applications or packages, along with their current deployment state / number of deployments and a count of the number of task sequences they are referenced in – https://gallery.technet.microsoft.com/scriptcenter/Application-Package-Usage-3ed3e14a

Source code:

<#
.SYNOPSIS
    SCConfigMgr Application & Package reporting

.DESCRIPTION
  This script allows you to report on applications and packages in your environment for the
  purpose of clean up tasks.

.INPUTS
  The script has two switches for either Applications or Packages and requires you to enter
  the site server name.

.EXAMPLE
  .\Get-AppPackageCleanUp.ps1 -PackageType Packages -SiteServer YOURSITESERVER

  .\Get-AppPackageCleanUp.ps1 -PackageType Packages -SiteServer YOURSITESERVER -ExportCSV True

.NOTES
    FileName:    Get-AppPackgeCleanUp.ps1
    Authors:     Maurice Daly
    Contact:     @modaly_it
    Created:     2017-08-11
    Updated:     2017-08-21
    
    Version history:
    1.0.0 - (2017-08-11) Script created (Maurice Daly)
  1.0.1 - (2017-08-21) Added task sequence names as requested (Maurice Daly)
#>

[CmdletBinding(SupportsShouldProcess = $true)]
param (
  

[parameter(Position = 0, HelpMessage = “Please specify whether you want to report on applications or packages”)]

[ValidateSet(“Applications”, “Packages”)] [string]$PackageType,

[parameter(Position = 0, HelpMessage = “Please specify your SCCM site server”)]

[ValidateNotNullOrEmpty()] [string]$SiteServer,

[parameter(Position = 0, HelpMessage = “Generated CSV output option”)]

[ValidateSet($false, $true)] [string]$ExportCSV = $false ) function DeploymentReport ($PackageType) { # Set required variables $PackageReport = @() $Packages = Get-CMPackage | Select-Object Name, PackageID $Applications = Get-CMApplication | Select-Object LocalizedDisplayName, PackageID, IsDeployed, ModelName $TaskSequences = Get-CMTaskSequence | Select-Object Name, PackageID, References | Where-Object { $_.References -ne $null } # Run package report if ($PackageType -eq “Packages”) { Foreach ($Package in $Packages) { $TaskSequenceCount = 0 $TaskSequenceList = $null $Deployments = (Get-CMDeployment | Select-Object -Property * | Where-Object { $_.PackageID -match $Package.PackageID }).count foreach ($TaskSequence in $TaskSequences) { if (($TaskSequence | Select-Object -ExpandProperty References | Where-Object { $_.Package -contains $Package.PackageID }) -ne $null) { $TaskSequenceCount++ $TaskSequenceList = $TaskSequenceList + $TaskSequence.Name + “;” } } $TaskSequenceMatch = New-Object PSObject $TaskSequenceMatch | Add-Member -type NoteProperty -Name ‘Package Name’ -Value $Package.Name $TaskSequenceMatch | Add-Member -type NoteProperty -Name ‘Package ID’ -Value $Package.PackageID $TaskSequenceMatch | Add-Member -type NoteProperty -Name ‘Deployment References’ -Value $Deployments $TaskSequenceMatch | Add-Member -type NoteProperty -Name ‘Task Sequence References’ -Value $TaskSequenceCount $TaskSequenceMatch | Add-Member -type NoteProperty -Name ‘Task Sequences’ -Value $TaskSequenceList $PackageReport += $TaskSequenceMatch } Return $PackageReport } # Run application report if ($PackageType -eq “Applications”) { foreach ($Application in $Applications) { $TaskSequenceCount = 0 $TaskSequenceList = $null foreach ($TaskSequence in $TaskSequences) { If ($($TaskSequence.References.Package) -contains $Application.ModelName) { $TaskSequenceCount++ $TaskSequenceList = $TaskSequenceList + $TaskSequence.Name + “;” } } $TaskSequenceMatch = New-Object PSObject $TaskSequenceMatch | Add-Member -type NoteProperty -Name ‘Application Name’ -Value $Application.LocalizedDisplayName $TaskSequenceMatch | Add-Member -type NoteProperty -Name ‘Package ID’ -Value $Application.PackageID $TaskSequenceMatch | Add-Member -type NoteProperty -Name ‘Application Deployed’ -Value $Application.IsDeployed $TaskSequenceMatch | Add-Member -type NoteProperty -Name ‘Task Sequence References’ -Value $TaskSequenceCount $TaskSequenceMatch | Add-Member -type NoteProperty -Name ‘Task Sequences’ -Value $TaskSequenceList $PackageReport += $TaskSequenceMatch } Return $PackageReport } } function ConnectSCCM ($SiteServer) { if ((Test-WSMan -ComputerName $SiteServer).wsmid -ne $null) { # Import SCCM PowerShell Module $ModuleName = (Get-Item $env:SMS_ADMIN_UI_PATH).parent.FullName + “\ConfigurationManager.psd1” if ($ModuleName -ne $null) { Import-Module $ModuleName $SiteCode = QuerySiteCode -SiteServer $SiteServer Return $SiteCode } else { Write-Error “Error: ConfigMgr PowerShell Module Not Found” -Severity 3 } } else { Write-Error “Error: ConfigMgr Server Specified Not Found – $SiteServer” -Severity 3 } } function QuerySiteCode ($SiteServer) { try { $SiteCodeObjects = Get-WmiObject -Namespace “root\SMS” -Class SMS_ProviderLocation -ComputerName $SiteServer -ErrorAction Stop $SiteCodeError = $false } Catch { $SiteCodeError = $true } if (($SiteCodeObjects -ne $null) -and ($SiteCodeError -ne $true)) { foreach ($SiteCodeObject in $SiteCodeObjects) { if ($SiteCodeObject.ProviderForLocalSite -eq $true) { $SiteCode = $SiteCodeObject.SiteCode } } Return $SiteCode } } function Get-ScriptDirectory { [OutputType([string])] param () if ($null -ne $hostinvocation) { Split-Path $hostinvocation.MyCommand.path } else { Split-Path $script:MyInvocation.MyCommand.Path } } # Get current directory [string]$CurrentDirectory = (Get-ScriptDirectory) # Connect to the SCCM environment and discover the site code $SiteCode = ConnectSCCM ($SiteServer) # Set the location to the site code Set-Location -Path ($SiteCode + “:”) # Start deployment report process $Report = DeploymentReport ($PackageType) Set-Location -Path $CurrentDirectory if ($ExportCSV -eq $true) { if ($PackageType -eq “Applications”) { $Report[1 .. ($Report.Count – 1)] | Export-CSV -Path .\Application-CleanUpReport.csv -NoTypeInformation -Append } else { $Report[1 .. ($Report.Count – 1)] | Export-CSV -Path .\Package-CleanUpReport.csv -NoTypeInformation -Append } } else { $Report | Out-GridView }

Running the script

To run the script launch an elevated PowerShell session and type in the following;

 .\Get-AppPackageCleanUp.ps1 -PackageType Applications -SiteServer YOURSITESERVER
In this mode the script will present a list of Applications in a data grid

 .\Get-AppPackageCleanUp.ps1 -PackageType Packages -SiteServer YOURSITESERVER -ExportCSV True
In this mode the script will output a list of packages to a CSV file 

Grid View Output


Applications Report Grid View

Packages Report Grid View

CSV Export

The output is displayed in a grid view by default, however if you use the -ExportCSV True switch it will export the contents out to a CSV where you can interrogate the data further.

You could go a step further by reading in an updated CSV containing only apps you wish to remove and use the Remove-CMPackage / Remove-CMApplication commands for a quick spring clean of your environment.

Sample PowerShell application clean up command
$Applications = Import-CSV -Path .\ApplicationCleanUpReport.csv
$AppsToClear = $Applications | Where-object {($_."Application Deployed" -eq $false) -and ($_."Task Sequence References" -eq 0)}
foreach ($App in $AppsToClear)
{
  Remove-CMApplication -Name $App."Application Name" -Force
}

In the above few lines of code (run from a ConfigMgr PS consle) you would remove all applications not referenced in task sequences or deployed.

Conclusion

Using PowerShell is a very powerful means of drilling down into data, allowing you to generate ad hoc reports. Be careful however when using the Remove command as it will not be forgiving.

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.

7 comments

  • To make this run faster you should move the variable for get-cmapplication and get-cmpackage under the sections where you would actually use them. Why gather all the applications when you are running a packaging report?

  • It’s OK, worked it out – just needed to amend this line:

    $Applications = Get-CMApplication | Select-Object LocalizedDisplayName, PackageID, IsDeployed, ModelName
    to
    $Applications = Get-CMApplication | Select-Object LocalizedDisplayName, PackageID, IsDeployed, CreatedBy, ModelName

    And added this line:
    $TaskSequenceMatch | Add-Member -type NoteProperty -Name ‘Application Owner’ -Value $Application.CreatedBy

    Brian

  • Awesome script!

    If you are open to feedback for improvements: would it be difficult or impossible for you to also check for “dependency” relationship? i.e. App ‘A’ not deployed nor referenced in any TS, but it is a dependency of App ‘B’ which is deployed and/or referenced in TS. Therefore, we would not want to remove App ‘A’.

    • Hi Jose.

      I have answered you on Technet but the answer is yes I can look at adding this in for you.

      Maurice

      • Hi Maurice,

        Do you have the script with the dependency added in also available? We would love this script.

  • Maurice,

    Thank you for this post. It’s exactly what I’m looking for to help cleanup some of my older updates. I’m having trouble getting it to run though. I’ve unblocked the script so that it can run but I cannot set the execution policy to unrestricted in the SCCM powershell module. Any suggestions as to how I can easily do this?

    PF

    • Hi Peter,

      Launch a PowerShell session using the -ExecutionPolicy Bypass switch, that should allow you to run the script.

      Maurice

Sponsors