Public/Get-MsiPatch.ps1

function Get-MsiPatch {
    <#
    .SYNOPSIS
        Scans the "C:\Windows\Installer" directory for all installed and orpaned msp files.
    .DESCRIPTION
        Office installations can leave behind a large amount of orphaned patches which can take up many GBs of disk space.
        Using the "Get-MSIPatchInfo" cmdlet from the "MSI" module we can determine which msp files are currently installed.
        From there we can calculate the amount and size of the orpaned msp files.
    .EXAMPLE
        Get-MsiPatch
         
        This will display a PsCustomObject of all msp files and size, which are installed and size, and which are
        orpaned and their total size.
    .EXAMPLE
        Get-MsiPatch -Verbose
         
        Same as above only verbose information is displayed for each msp detailing whether they are installed or orphaned.
    .EXAMPLE
        Get-MsiPatch | FT
 
        Simply changes the format to a table view of the object.
    .NOTES
        Author: Mark Kerry
        Date: 08/01/2018
    #>
 

    [CmdletBinding()] 
    Param()

    # Begin by checking the user in running the function from elevated priviledges
    if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
        Write-Warning "You need to run this script from an elevated PowerShell prompt!`nPlease start PowerShell as an Administrator..."
        break
    }

    if ($PSVersionTable.PSEdition -eq 'Core') {
        Write-Warning "This function is not compatible with PowerShell Core as it reiles on the MSI module binaries."
        break
    }

    # Get each .msp in the Installer directory and calculate the size.
    $allMsp = Get-Item -Path C:\Windows\Installer\*.msp | Measure-Object -Property Length -Sum
    $allMspSum = "{0:N2}" -f ($allMsp.sum / 1GB) + " GB"

    # List all msp files in %windir%\Installer and convert to string
    $strAllMsp = Get-ChildItem -Path C:\Windows\Installer\*.msp | Select-Object Name -ExpandProperty Name

    # List all currently installed msp files and convert to string
    $strInstalledMsps = Get-MSIPatchInfo | Select-Object LocalPackage -ExpandProperty LocalPackage

    # Create a new array of all the installed msp files but run a regex query to strip the path from the string so the format is the same as the strAllMsp variable
    $array = @()
    foreach ($Msp in $strInstalledMsps) {
       $a = $Msp -creplace '(?s)^.*\\', ''
       $array += $a
    }
    # Remove any duplicate values from the array
    $array = $array | Select-Object -uniq

    # Now list both installed and Obsolete msps
    $inCount=0
    $obCount=0
    $inst = @()
    $obinst = @()
    foreach ($x in $strAllMsp) {
        if ($array.Contains($x)) {
            Write-Verbose "$x is installed."
            $inst += $x
            $inCount++
        }
        else {
            Write-Verbose "$x is obsolete and can be moved/deleted."
            $obinst += $x
            $obCount++
        }
    }
    
    # Scan for each installed msp and calculate the total size of them all.
    Write-Progress -Activity 'Retrieving installed msp files...' -Status '33% Complete.' -PercentComplete 33

    $size = $null
    foreach ($i in $inst) {
        $instSize = Get-ChildItem -Path C:\Windows\Installer | Where-Object {$_.Name -like $i} | Select-Object Length -ExpandProperty Length
        $size += $instSize
    }
    $TotalInst = "{0:N2}" -f ($size / 1GB) + " GB"

    # Scan for each obsolete msp and calculate the total size of them all.
    Write-Progress -Activity 'Retrieving orphaned msp files...' -Status '66% Complete.' -PercentComplete 66

    $obsize = $null
    foreach ($o in $obinst) {
        $obinstSize = Get-ChildItem -Path C:\Windows\Installer | Where-Object {$_.Name -like $o} | Select-Object Length -ExpandProperty Length
        $obsize += $obinstSize
    }
    $TotalOb = "{0:N2}" -f ($obsize / 1GB) + " GB"
    
    # Display them all in a PsCustomObject
    [PsCustomObject]@{
        TotalPatchCount = $allMsp.Count
        TotalPatchSize = $allMspSum
        InstalledPatchCount = $inCount
        InstalledPatchSize = $TotalInst
        OrphanedPatchCount = $obCount
        OrphanedPatchSize = $TotalOb
    }
}