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 since 1999 and was awarded his first MVP Enterprise Mobility award in 2017. Technology focus includes Active Directory, Group Policy, Hyper-V, Windows Deployment (SCCM & MDT) and Office 365.

(1812)

comments
  • Peter Fitch
    Posted at 18:25 August 14, 2017
    Peter Fitch
    Reply
    Author

    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

    • Maurice Daly
      Posted at 23:32 August 14, 2017
      Maurice Daly
      Reply
      Author

      Hi Peter,

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

      Maurice

  • Jose Camacaro Latouche
    Posted at 20:15 August 24, 2017
    Jose Camacaro Latouche
    Reply
    Author

    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’.

    • Maurice Daly
      Posted at 17:43 August 25, 2017
      Maurice Daly
      Reply
      Author

      Hi Jose.

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

      Maurice

  • Leave a Reply