Introduction

Lately, I am testing installations on virtual computers with limited storage. So, as a result, the CCM Cache is filled quickly. So time for a tool to clear the CCM Cache properly. Of course, you can achieve this goal by using Client Center for Configuration Manager by Roger Zander remotely, but in my case, WinRM was disabled and could not be enabled easily. That is why I decided to create my own version. And my own version has a GUI so you can easily choose which item to remove. The only disadvantage is that you have to use this application locally. 

Some challenges

I used the script from Ioan Popovici Clean-CMClientCache as a starting and learning point. Also, I created some small scripts to test the functions.

Query the Applications

You find the application Firefox (in this example) - and its status - via a WMI Query:

Clear-Host
Get-CimInstance -Namespace "Root\ccm\ClientSDK" -ClassName 'CCM_Application' | Where-Object {$_.Name -like "*Firefox*"}

WMI Query for Firefox

But that gives only the information about all the applications. But the deployment information is in the deployment types. So additional information is needed. This case for Firefox:

Clear-Host
ForEach ($Application in (Get-CimInstance -Namespace "Root\ccm\ClientSDK" -ClassName 'CCM_Application' | Where-Object {$_.Name -like "*Firefox*"}))
 {
  $Application.Name
  $AppDTs = ($Application | Get-CimInstance).AppDTs
  ForEach ($AppDT in $AppDTs)
   {
    $AppDT
   }
 }

Firefox and the deployment types

And now, the contentid. That information is stored in CCM_AppDeliveryType. So, an additional query is needed:

Clear-Host
ForEach ($Application in (Get-CimInstance -Namespace "Root\ccm\ClientSDK" -ClassName 'CCM_Application' | Where-Object {$_.Name -like "*Firefox*"}))
 {
  $AppDTs = ($Application | Get-CimInstance).AppDTs
  ForEach ($AppDT in $AppDTs)
   {
    ForEach ($ActionType in ($AppDT.AllowedActions))
     {
      $Arguments = [hashtable]@{
              'AppDeliveryTypeID' = [string]$($AppDT.ID)
              'Revision'          = [uint32]$($AppDT.Revision)
              'ActionType'        = [string]$($ActionType)}
      $AppContentID = (Invoke-CimMethod -Namespace 'Root\ccm\cimodels' -ClassName 'CCM_AppDeliveryType' -MethodName 'GetContentInfo' -Arguments $Arguments -Verbose:$false).ContentID
      Write-Host "Application: $($Application.Name)"
      Write-Host "ContentID:   $($AppContentID)"
     }
   }
 }

ContentID for a deployment type

You can read the content location (like C:\WINDOWS\CCMCache\8) for all the cached elements with the following commands:

Clear-Host

$CCMComObject = New-Object -ComObject 'UIResource.UIResourceMgr'
$CacheInfo    = $($CCMComObject.GetCacheInfo().GetCacheElements())
$CacheInfo

Get Cached Elements

And finally, the location where the content is stored. 

Clear-Host

$CCMComObject = New-Object -ComObject 'UIResource.UIResourceMgr'
$CacheInfo    = $($CCMComObject.GetCacheInfo().GetCacheElements())

ForEach ($Application in (Get-CimInstance -Namespace "Root\ccm\ClientSDK" -ClassName 'CCM_Application' | Where-Object {$_.Name -like "*Firefox*"}))
 {
  $AppDTs = ($Application | Get-CimInstance).AppDTs
  ForEach ($AppDT in $AppDTs)
   {
    ForEach ($ActionType in ($AppDT.AllowedActions))
     {
      $Arguments = [hashtable]@{
              'AppDeliveryTypeID' = [string]$($AppDT.ID)
              'Revision'          = [uint32]$($AppDT.Revision)
              'ActionType'        = [string]$($ActionType)}
      $AppContentID = (Invoke-CimMethod -Namespace 'Root\ccm\cimodels' -ClassName 'CCM_AppDeliveryType' -MethodName 'GetContentInfo' -Arguments $Arguments -Verbose:$false).ContentID
      Write-Host "Application:      $($Application.Name)"
      Write-Host "Action:           $($ActionType)"
      Write-Host "ContentID:        $($AppContentID)"
      $AppCacheInfo = $CacheInfo | Where-Object { $($_.ContentID) -eq $AppContentID }
      Write-Host "Content Location: $($AppCacheInfo.Location)"
      Write-Host "Content Size:     $($AppCacheInfo.ContentSize)"
      Write-Host "---------------------------------------------------------------------------------------"
     }
   }
 }

GetCacheElements for Firefox

You can get all the information for all the applications with the following code:

Clear-Host

$CCMComObject = New-Object -ComObject 'UIResource.UIResourceMgr'
$CacheInfo    = $($CCMComObject.GetCacheInfo().GetCacheElements())

$EnforcePreference = @{
0 = "Immediate"
1 = "NonBusinessHours"
2 = "AdminSchedule"
}

$EvaluationSate = @{
0 = "No state information is available."
1 = "Application is enforced to desired/resolved state."
2 = "Application is not required on the client."
3 = "Application is available for enforcement (install or uninstall based on resolved state). Content may/may not have been downloaded."
4 = "Application last failed to enforce (install/uninstall)."
5 = "Application is currently waiting for content download to complete."
6 = "Application is currently waiting for content download to complete."
7 = "Application is currently waiting for its dependencies to download."
8 = "Application is currently waiting for a service (maintenance) window."
9 = "Application is currently waiting for a previously pending reboot."
10 = "Application is currently waiting for serialized enforcement."
11 = "Application is currently enforcing dependencies."
12 = "Application is currently enforcing."
13 = "Application install/uninstall enforced and soft reboot is pending."
14 = "Application installed/uninstalled and hard reboot is pending."
15 = "Update is available but pending installation."
16 = "Application failed to evaluate."
17 = "Application is currently waiting for an active user session to enforce."
18 = "Application is currently waiting for all users to logoff."
19 = "Application is currently waiting for a user logon."
20 = "Application in progress, waiting for retry."
21 = "Application is waiting for presentation mode to be switched off."
22 = "Application is pre-downloading content (downloading outside of install job)."
23 = "Application is pre-downloading dependent content (downloading outside of install job)."
24 = "Application download failed (downloading during install job)."
25 = "Application pre-downloading failed (downloading outside of install job)."
26 = "Download success (downloading during install job)."
27 = "Post-enforce evaluation."
28 = "Waiting for network connectivity."
}

ForEach ($Application in (Get-CimInstance -Namespace "Root\ccm\ClientSDK" -ClassName 'CCM_Application'))
 {
  $AppDTS = ($Application | Get-CimInstance).AppDTs
  ForEach ($AppDT in $AppDTs)
   {
     ForEach ($ActionType in $AppDT.AllowedActions)
      {
                            $Arguments = [hashtable]@{
                            'AppDeliveryTypeID' = [string]$($AppDT.ID)
                            'Revision'          = [uint32]$($AppDT.Revision)
                            'ActionType'        = [string]$($ActionType)}
        $AppContentID = (Invoke-CimMethod -Namespace 'Root\ccm\cimodels' -ClassName 'CCM_AppDeliveryType' -MethodName 'GetContentInfo' -Arguments $Arguments -Verbose:$false).ContentID
        Write-Host "Application: Name:                 $($Application.Name)"
        Write-Host "Application: Enforce preference:   $($EnforcePreference.Item([int]$($Application.EnforcePreference)))"
        Write-Host "Application: Evaluation:           $($EvaluationSate.Item([int]$($Application.EvaluationState)))"
        Write-Host "Application: Deployment Type name: $($AppDT.Name)"
        Write-Host "Application: Action:               $($ActionType)"
        $AppCacheInfo = $CacheInfo | Where-Object { $($_.ContentID) -eq $AppContentID }
        if($AppCacheInfo)
         {
          Write-Host "Application: Content ID:           $($AppCacheInfo.ContentID)"
          Write-Host "Application: Location:             $($AppCacheInfo.Location)"
          Write-Host "Application: Content size:         $('{0:N2}' -f $($AppCacheInfo.ContentSize / 1KB))Mb"
          Write-Host "Application: Last Reference time:  $($AppCacheInfo.LastReferenceTime)"
         }
        Write-Host "---------------------------------------------------------------------------------------"
      }
   }
 }

Get Cached Elements for all applications

Query the Updates

And the same for all the installed updates:

Clear-Host
$CCMComObject = New-Object -ComObject 'UIResource.UIResourceMgr'
$CacheInfo    = $($CCMComObject.GetCacheInfo().GetCacheElements())
$Updates      = (Get-CimInstance -Namespace 'Root\ccm\SoftwareUpdates\UpdatesStore' -ClassName 'CCM_UpdateStatus' -Verbose:$False) | Sort-Object -Property Title

$ClassificationType = @{
"5C9376AB-8CE6-464A-B136-22113DD69801" = "Application"
"434DE588-ED14-48F5-8EED-A15E09A991F6" = "Connectors"
"E6CF1350-C01B-414D-A61F-263D14D133B4" = "CriticalUpdates"
"E0789628-CE08-4437-BE74-2495B842F43B" = "DefinitionUpdates"
"E140075D-8433-45C3-AD87-E72345B36078" = "DeveloperKits"
"B54E7D24-7ADD-428F-8B75-90A396FA584F" = "FeaturePacks"
"9511D615-35B2-47BB-927F-F73D8E9260BB" = "Guidance"
"0FA1201D-4330-4FA8-8AE9-B877473B6441" = "SecurityUpdates"
"68C5B0A3-D1A6-4553-AE49-01D3A7827828" = "ServicePacks"
"B4832BD8-E735-4761-8DAF-37F882276DAB" = "Tools"
"28BC880E-0592-4CBF-8F95-C79B17911D5F" = "UpdateRollups"
"CD5FFD1E-E932-4E3A-BF74-18BF0B1BBD83" = "Updates"} 

ForEach ($Update in $Updates)
 {
   Write-Host "Updates: Bulletin ID:                $($Update.Article)"
   Write-Host "Updates: Title:                      $($Update.Title)"
   Write-Host "Updates: Status:                     $($Update.Status)"
   Write-Host "Updates: ProductID:                  $($Update.ProductID)"
   Write-Host "Updates: Revision number:            $($Update.RevisionNumber)"
   Write-Host "Updates: Unqiue ID:                  $($Update.UniqueID)"
   Write-Host "Updates: Classification:             $($ClassificationType.Item($($Update.UpdateClassification)))"
   $UpdateCacheInfo = $CacheInfo | Where-Object { $_.ContentID -eq $Update.UniqueID}
   if( $UpdateCacheInfo)
    {
     Write-Host "Updates: Content ID:                 $($UpdateCacheInfo.ContentID)"
     Write-Host "Updates: Location:                   $($UpdateCacheInfo.Location)"
     Write-Host "Updates: Content size:               $('{0:N2}' -f $($UpdateCacheInfo.ContentSize / 1KB))"
     Write-Host "Updates: Last Reference Time:        $($UpdateCacheInfo.LastReferenceTime)"
     }
    Write-Host "--------------------"

 }

All the updates

Query the Packages

The packages:

Clear-Host
$CCMComObject = New-Object -ComObject 'UIResource.UIResourceMgr'
$CacheInfo    = $($CCMComObject.GetCacheInfo().GetCacheElements())
$Packages     = Get-CimInstance -Namespace 'ROOT\ccm\Policy\Machine\RequestedConfig' -ClassName 'CCM_SoftwareDistribution' -Verbose:$false

ForEach ($Package in $Packages)
 {
  Write-Host "Package: Name:                 $($Package.PKG_MIFName)"
  Write-Host "Package: Progam:               $($Package.PRG_ProgramID)"
  $PkgCacheInfo = $CacheInfo | Where-Object { $_.ContentID -eq $Package.PKG_PackageID }

  if(($($PkgCacheInfo | Measure-Object).Count) -gt 1){$PkgCacheInfo1 = $PkgCacheInfo[-1]} else {$PkgCacheInfo1 = $PkgCacheInfo}

  ForEach ($CacheItem in $PkgCacheInfo1)
   {
    If ($CacheItem) 
     {
      #  Set content size to 0 if null to avoid division by 0
     If ($CacheItem.ContentSize -eq 0) { [int]$PkgContentSize = 0 } Else { [int]$PkgContentSize = $($CacheItem.ContentSize) }
        Write-Host "Package: Content ID:           $($CacheItem.ContentID)"
        Write-Host "Package: Location:             $($CacheItem.Location)"
        Write-Host "Package: Content size          $('{0:N2}' -f $($PkgContentSize / 1KB))Mb"
        Write-Host "Package: Last reference time:  $($CacheItem.LastReferenceTime)"
     }
    }
    Write-Host "------------------"
    
   }

All packages

Orphaned folders

There are folders in the CCMCache that are no longer needed. These folders need to be cleaned up.
See this example:

Orphaned Folder Name  ContentId
--------------------  ---------
C:\WINDOWS\ccmcache\t
C:\WINDOWS\ccmcache\u d156b905-7ef5-408e-8fb0-005973ca9364

With the code:

Clear-Host
$CCMComObject                      = New-Object -ComObject 'UIResource.UIResourceMgr'
$CCMCacheFolderUsedByDeployments   = $CCMComObject.GetCacheInfo().GetCacheElements()
$CacheLocation                     = $CCMComObject.GetCacheInfo().Location
$AllFoldersInCache                 = (Get-ChildItem $CacheLocation -Directory).FullName

ForEach ($FolderInCache in $AllFoldersInCache)
 {
  If (($CCMCacheFolderUsedByDeployments | ForEach-Object {$_.Location}) -notcontains $FolderInCache)
   {
    if (-not $FolderInCache.Contains("."))
     {
      Write-Host "$FolderInCache is not used by any application, program or update."
     }
   }
 }

Orphaned folders

And now, the other way around. You can run an inventory of all the ContentID's that are used by all applications, programs and updates. That information is stored in the global array $Global:arrTable. Also, you have all the contentID's that are collected via $CCMComObject.GetCacheInfo().GetCacheElements() and put in the variable $CCMCacheFolderUsedByDeployments. So, all the contentID's that are in $CCMCacheFolderUsedByDeployments and not in the $Global:arrTable are orphaned as well. In the example above that is the folder C:\WINDOWS\ccmcache\u. 

The folder C:\WINDOWS\ccmcache\t can be removed via a Remove-Item command.  
The folder C:\WINDOWS\ccmcache\u is removed by a special command that is described later on.  

The script

Working with the script

You see the following screen if you start the script with elevated rights:

Starting the script

The greyed-out lines do not have any cache information, so these entries cannot be removed. The 'normal' lines with a blank background and black letters have cache information. So, these entries can be removed. If you only want to see the entries that are in the CCM Cache, select the option 'Show only items that are in the CCM Cache.'.

Only the items that are in the CCM Cache

If you select 'Select or unselect all items' then all the items that are in the CCM Cache are selected:

All items are selected

And this is how to remove the items from the CCM Cache:

Select the item(s) you want to remove and click on the button 'Remove selected items from Software Center cache':

Remove from cache

And an expected failure, as the package has been installed recently:

Remove from cache (failure)

But there is a workaround: select 'Delete the item immediately and ignore ReferenceDays':

Remove from cache (success)

And install fonts is no longer in the CCM Cache anymore:

Install fonts no longer in the CCM Cache

If you rerun the installation, then the content is downloaded first:

Rerun from Software Center

Command-line options

There are a few command-line options:

  • DetailedLogging:
    for a log file in %TEMP% folder. So you know what is going on.

  • Verbose:
    logging on the screen.

  • OnlyItemsInCCMCache:
    starts the application and only the items in the CCM Cache are shown.

  • LanguageOverride:
    by default, the language of the computer is used. You can override that. New in version 1.1: you can find all the supported languages with the switch OverviewSupportedLanguages. If you need more languages, use my PowerShell script Translate content of a JSON file with DeepL, Google and Microsoft Azure Cognitive Services Translator to add the needed languages.
<#
.SYNOPSIS
    Removes selected items from the CCMCache.

.DESCRIPTION
    Removes selected items from the CCMCache.

.EXAMPLE
     Start the application and show the GUI.
     ."ClearSoftwareCenterCache_v12.ps1" 

.EXAMPLE
     Start the application and show the GUI with verbose logging on the screen.
     ."ClearSoftwareCenterCache_v13.ps1" -Verbose

.EXAMPLE
     Start the application and show the GUI with detailed logging to a log file in %TEMP%
     ."ClearSoftwareCenterCache_v13.ps1" -DetailedLogging

.EXAMPLE
     Start the application and show the GUI with detailed logging to a log file in %TEMP% 
     and show only the items that are in the CCM Cache.
     ."ClearSoftwareCenterCache_v13.ps1" -DetailedLogging -OnlyItemsInCCMCache

.EXAMPLE
     Start the application, show the GUI in the Italian GUI language. Enable detailed logging.
     ."ClearSoftwareCenterCache_v13.ps1" -LanguageOverride it -DetailedLogging
     Use the switch OverviewSupportedLanguages the find the supported languages.

.NOTES
    Author:  Willem-Jan Vroom
    Website: 
    Twitter: @TheStingPilot

v0.1 Beta 1:
   * Initial version. 

v0.1 Beta 2:
   * Corrected the translation of the sentence 'You need elevated (admin) rights to run this script.'
   * Modified the function CCM-FindOrphanedCCMFolders.
   * The datagrid has the correct layout for the rows that can be selected.

v0.1 RC1
   * More help added.

v1.0
   * Added the command line option OnlyItemsInCCMCache
   * Added the option to ignore the ReferencedDays, so you can delete the item straight away.

v1.1
   * Better error handling if the removal failed if persistent in cache.
   * Introduction of the 'OverviewSupportedLanguages' switch

v1.2:
   * Changed the date notation of the logfile to yyyy-MM-dd HH-mm-ss, for example '2022-11-14 22-23-09'.
   * The function Userdetaisl has been modified.

v1.3:
   * The title of the error box has been changed and includes the application name.

Resources:
 - https://SCCM.Zone/Clean-CMClientCache
 - https://stackoverflow.com/questions/5648931/test-if-registry-value-exists
 - https://stackoverflow.com/questions/72535521/powershell-datagridview-avoid-selection-of-rows-based-on-a-value-in-cell
 - https://docs.microsoft.com/en-us/mem/configmgr/develop/reference/core/clients/sdk/ccm_application-client-wmi-class
 - https://docs.microsoft.com/en-us/mem/configmgr/develop/reference/core/clients/sdk/ccm_evaluationstate-client-wmi-class
 - https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ff357803(v=vs.85)
 - https://ss64.com/ps/psboundparameters.html
 - https://community.idera.com/database-tools/powershell/powertips/b/tips/posts/using-solid-alternatives-for-myinvocation

#>

[CmdletBinding()]

Param
  (
   [Parameter(HelpMessage='Enable detailed logging to a log file.')]
   [Switch]   $DetailedLogging,

   [Parameter(HelpMessage='Show only the items that are available in the CCM Cache.')]
   [Switch]   $OnlyItemsInCCMCache,

   [Parameter(HelpMessage='Force a per machine install or uninstall.')]
   [Switch]   $OverviewSupportedLanguages,

   [Parameter(HelpMessage = 'Override the language.')]
   [ValidateScript({
     $JSONFile                    = $PSCommandPath -replace ".ps1",".json"  
     if(Test-Path($JSONFile))
      {
       $JSONObject                  = Get-Content -Path $JSONFile -Raw -Encoding UTF8 | ConvertFrom-Json    
       $Languages                   = @(($JSONObject | Get-Member -type NoteProperty).Name)
       $Languages                   = $Languages | Sort-Object
       if($Languages -contains $_)
        {
         $True
        }
         else
        {
         $SupportedLanguages = ""
         ForEach ($Language in $Languages)
          {
           $LanguageName = ((([CultureInfo]::GetCultures([System.Globalization.CultureTypes]::SpecificCultures) | Where {$_.Name -like "$Language*"})[0]).DisplayName).Split(" ")[0]
           if($SupportedLanguages.Length -eq 0)
            {
             $SupportedLanguages = "$Language ($LanguageName)"
            }
             else
            {
             $SupportedLanguages += ", $Language ($LanguageName)"
            } 
          }
         if($Languages.Count -gt 1)
          {
           $Lastcomma          = $SupportedLanguages.LastIndexOf(", ")
           $Firstpart          = $SupportedLanguages.Substring(0,$Lastcomma)
           $Lastpart           = $SupportedLanguages.SubString($Lastcomma+2,($SupportedLanguages.Length) - $Lastcomma -2)
           Clear-Host
           Throw "$_ is no supported language. The following $($Languages.Count) languages are supported: $Firstpart and $Lastpart."
          }
           else
          {
           Throw "$_ is no supported language. The following language is supported: $SupportedLanguages."
          }
        }
      }
       else
      {
       Write-Host "The JSON File '$JSONFile' is not found."
       Exit 99
      }
     })]
   [String]   $LanguageOverride
  )

  Clear-Host

# =============================================================================================================================================
# Function block
# =============================================================================================================================================

  Function Display-MessageBox
   {

    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Created on:       24-May-2021 / 24-Apr-2024 / 09-June-2022
    Created by:       Willem-Jan Vroom
    Organization:     
    Functionname:     Display-MessageBox
    =============================================================================================================================================
    .SYNOPSIS

    This function displays a message box.

    The return value is:

    None (no result) 	0
    OK 	                1
    Cancel 	            2
    Yes 	            6
    No. 	            7

    #>

    param
     (
      [Parameter(Mandatory=$True)] [ValidateNotNullOrEmpty()][String] $Text,
      [Parameter(Mandatory=$False)][String] $Title,
      [Parameter(Mandatory=$False)][Switch] $GUI,
      [Parameter(Mandatory=$False)][Switch] $Error,
      [Parameter(Mandatory=$False)][ValidateSet('Ok','AbortRetryIgnore','OkCancel','YesNoCancel','YesNo')][String] $Button = "Ok"
     )
         
    If ($GUI)
     {
      If ($Error)
       {
        $Return = [System.Windows.Forms.MessageBox]::Show($this,$Text.Trim(),$($CCMCacheActions.$Language.Error) + " " + $Title,$Button,"Error")
       }
        else
       {
        $Return = [System.Windows.Forms.MessageBox]::Show($this,$Text.Trim(),$($CCMCacheActions.$Language.Information) + " " + $Title,$Button,"Asterisk")
       }
     }
      else
     {
      Write-Host "$Text`n"
      Return 0
     }

     Return $($Return.value__)
   }

  Function Add-EntryToLogFile
   {

    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Created on:       17-May-2020 / Modified 09-May-2022: Includes the function name
    Created by:       Willem-Jan Vroom
    Organization:     
    Functionname:     Add-EntryToLogFile
    =============================================================================================================================================
    .SYNOPSIS

    This function adds a line to a log file

    #>

    Param
     (
      [Parameter(Mandatory=$True)]  [string] $Entry,
      [Parameter(Mandatory=$False)] [String] $FunctionName

     )
      
     Write-Verbose "[Function: $FunctionName] - $Entry"
     If ($Global:gblDetailedLogging -and $Global:gblLogFile)
      {
       $Timestamp = (Get-Date -UFormat "%a %e %b %Y %X").ToString()
       Add-Content $Global:gblLogFile -Value $($Timestamp + "[Function: $FunctionName] - $Entry") -Force -ErrorAction SilentlyContinue
      }
   }

  Function Add-EntryToResultsFile
   {

    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Created on:       03-August-2018
    Created by:       Willem-Jan Vroom
    Organization:     
    Functionname:     Add-EntryToResultsFile
    =============================================================================================================================================
    .SYNOPSIS

    This function adds the success or failure information to the array that contains the log
    information.

    #>

    Param
     (
      [String]   $Name                  = "",
      [String]   $DeploymentType        = "",
      [String]   $Program               = "",
      [String]   $Evaluation            = "",
      [String]   $UpdateClassification  = "",
      [String]   $ContentID             = "",
      [String]   $ContentLocation       = "",
      [String]   $ContentSize           = "",
      [String]   $LastReferenceTime     = ""
     )

    $ThisFunctionName = $MyInvocation.MyCommand.Name

    $Record  = [ordered] @{"Name"                        = "";
                           "Deployment Type"             = "";
                           "Program"                     = "";
                           "Evaluation"                  = "";
                           "Update Classification"       = "";
                           "Content ID"                  = "";
                           "Content Location"            = "";
                           "Content Size"                = "";
                           "Content Last Reference Time" = ""}

    $Record."Name"                        = $Name
    $Record."Deployment Type"             = $DeploymentType
    $Record."Program"                     = $Program
    $Record."Evaluation"                  = $Evaluation
    $Record."Update Classification"       = $UpdateClassification
    $Record."Content ID"                  = $ContentID
    $Record."Content Location"            = $ContentLocation
    $Record."Content Size"                = $ContentSize
    $Record."Content Last Reference Time" = $LastReferenceTime

    $Global:arrTable                     += New-Object PSObject -Property $Record
    
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Discovered application, program or update:"
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "  * Name:                        $Name"
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "  * Deployment Type:             $DeploymentType"
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "  * Program:                     $Program"
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "  * Evaluation:                  $Evaluation"
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "  * Update Classification:       $UpdateClassification"
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "  * Content ID:                  $ContentID"
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "  * Content Location:            $ContentLocation"
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "  * Content Size:                $ContentSize"
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "  * Content Last Reference Time: $LastReferenceTime"
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "#######################################################################"
   }


  Function Create-Folder
   {

    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Created on:       03-August-2018
    Created by:       Willem-Jan Vroom
    Organization:     
    Functionname:     Create-Folder
    =============================================================================================================================================
    .SYNOPSIS

    This function creates the given folder.
    The function returns two results:
     1. A message.
     2. True or false (success or failure)

    #>
    
    param
     (
      [Parameter(Mandatory=$True)] [ValidateNotNullOrEmpty()][String] $FolderName
     )

   $bolResult     = $True
   $ResultMessage = ""

   If (-not(Test-Path $('FileSystem::' + $FolderName)))
    {
     New-Item -Path $FolderName -ItemType Directory | Out-Null
     Sleep 1
     If (test-path $('FileSystem::' + $FolderName))
      {
       $ResultMessage = $($CCMCacheActions.$Language.FolderCreated).Replace("<FolderName>", $FolderName)
      }
       else
      {
       $ResultMessage = $($CCMCacheActions.$Language.ErrorCreatingFolder).Replace("<FolderName>", $FolderName)
       $bolResult     = $False
      }
    }
     else
    {
     $ResultMessage = $($CCMCacheActions.$Language.FolderAlreadyExists).Replace("<FolderName>", $FolderName)
    }

    Return $ResultMessage,$bolResult 

   }

  Function Check-HasElevatedRights
   {

    <#
    .NOTES
    ========================================================================================================================
    Created with:     Windows PowerShell ISE
    Created on:       11-January-2019 / Modified 22-Apr-2022 / Modified 05-May-2022 / Modified on 28-Nov-2022.
    Created by:       Willem-Jan Vroom
    Organization:     
    Functionname:     Check-HasElevatedRights
    ========================================================================================================================
    .SYNOPSIS

    This function checks if an user has admin rights. The function returns $True or $False

    Initially, the function was ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]'544')
    But that command throws up an error due to the PowerShell ConstrainedMode.
    "S-1-5-32-544" -in (whoami /groups /FO csv | convertfrom-csv).sid does not work either as it will always return 'true', even if not elevated but
    the logged on user is an admin
    So the only good test: check if you can create a file in c:\windows\fonts. If yes, then admin / elevated rights otherwise not. 

    #>

    $RandomFile = ""
    For($a=1; $a -le 8; $a++)
     {
      $RandomFile += [char](Random(97..122))
     }

    $RandomFile = "$($env:windir)\Fonts\$RandomFile.txt"
    
    Try
     {
      New-Item $RandomFile -Force -ErrorAction SilentlyContinue | Out-Null
      If (test-path($RandomFile))
       {
        Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "The random file $RandomFile has been created, so there are elevated rights."
        Remove-Item $RandomFile -Force
        Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "The random file $RandomFile has been removed."
        Return $True
       }
        else
       {
        Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "The random file $RandomFile has not been created, so there are normal user rights."
        Return $False
       }
     }
      Catch
     {  
      Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "The random file $RandomFile has not been created, so there are normal user rights."
      Return $False
     } 
   }

  Function Test-RegistryKeyValue
   {
    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Created on:       30-Dec-20
    Created by:       (C) Aaron Jensen 
                      https://stackoverflow.com/questions/5648931/test-if-registry-value-exists
    Organization:     Carbon Module
    Functionname:     Test-RegistryKeyValue
    =============================================================================================================================================
    .SYNOPSIS

    #>

    [CmdletBinding()]
    param 
     (
      [Parameter(Mandatory = $True)][string]  $Path,
      [Parameter(Mandatory = $True)][string]  $Name
     )

    if ( -not(Test-Path -Path $Path -PathType Container))
     {
      return $False
     }

    $properties = Get-ItemProperty -Path $Path
    if (-not $properties)
     {
      return $False
     } 

    $member = Get-Member -InputObject $properties -Name $Name
    if ($member)
     {
      return $True
     }
      else
     {
      return $False
     }
   }

  Function UserDetails
   {
    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Created on:       03-Jan-21 / Modified on 14-Jan-21 / Modified on 22-Apr-22 / Modified on 01-May-2022 / Modified on 17-May-2022 /  
	                  Modified 21-Nov-2022 / Modified 08-Dec-2022 / Modified 17-Mar-2023
    Created by:       Willem-Jan Vroom
    Functionname:     UserDetails
    =============================================================================================================================================
    .SYNOPSIS

    This function returns 4 details of the Current Logged In Usser
    1. The username of the current logged in user
    2. User\Domain of the current logged in user
    3. User SID fo the User\Domain
    4. Account name that is using the script 

    Initially, the scriptAccount was found with the command 
    [System.Security.Principal.WindowsIdentity]::GetCurrent().Name

    But that command throws up an error due to the PowerShell ConstrainedMode.

    Also, the output of whoami /user depends on the language. In the German language the output is different.
    The header information is in the German language, so the header should be read from the CSV file.
    That can be done with PSObject.Properties.Name

    C:\Users\Test>whoami /user

    BENUTZERINFORMATIONEN
     ---------------------

    Benutzername         SID
    ==================== ==============================================
    windows10wkg_02\test S-1-5-21-1753037473-1212035439-1379364385-1001

    On 21-Nov-2022 the function has been changed.

    There are 6 situations that should be handled:
    1. Find the logged on user sid when there is one user logged on on a 'normal' Windows computer
    2. Find the logged on user sid when there are multiple users logged on in a RDS / Citrix environment where the users are no local admin.
    3. Find the logged on user sid when there are multiple users logged on in a RDS / Citrix environment where the users are local admin.
    4. Find the logged on user sid when there is a SYSTEM account and there is a user logged on on a 'normal' Windows computer. This is the
       case during a Software Center installation performed under the SYSTEM account.
    5. Find the logged on user sid when there is a SYSTEM account and there is a user logged on on a RDS / Citrix environment.
    6. Take care of the PowerShell Language Mode.

    While testing I found that the MainWindowHandle is not 0 for the user that is running the explorer process. That is true for all the
    above mentioned situations.

    You can check while running this command with elevated rights:

    get-process -IncludeUserName | Select-Object -Property Username, Id, Name,MainWindowHandle | Where {($_.Name -eq "explorer" -or $_.Name -eq "pfwsmgr")} | Format-Table

    The output will be something like this:

    UserName             Id Name     MainWindowHandle
    --------             -- ----     ----------------
    DEMO\user1         1380 explorer                0
    DEMO\adminuser2    6556 explorer           131134

    So, you have to continue with PID ID 6556.

    So, I used the method of finding the PID, and use that PID as a condition for the Get-WMIObject to search for the explorer or pfwsmgr
    process with that PID.

    #>

  # =============================================================================================================================================
  # Find the current logged on user by checking the rights on the explore.exe (of pfwsmgr.exe with Ivanti Workspace Control) process.
  # If the language mode is FullLanguage then use MainWindowHandle. That is needed when this script is run from SYSTEM context. I assume that
  # the SYSTEM account is not effected by the PowerShell Language Mode.
  # If the language mode is not FullLanguage then use a check on username and the owner of the explorer or pfwsmgr process. 
  # =============================================================================================================================================

    if($Global:gblFullLanguage)
     {
      $PIDID              = (get-process | Select-Object -Property Id, Name, MainWindowHandle | Where {(($_.Name -eq "explorer" -or $_.Name -eq "pfwsmgr") -and $($_.MainWindowHandle).ToInt32() -gt 0)}).Id[0]
      Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "The process 'explorer' or 'pfwsmgr' has the PID $PIDID."
      $Explorer           = (Get-WMIObject -Query "Select * From Win32_Process Where (ProcessId=$PIDID)")
      Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "The process with PID $PIDID has the name $($Explorer.Name)."

      $UserName           = $Explorer.GetOwner()
      $SID                = ($Explorer.GetOwnerSID()).SID
     }
      else
     {
      $Explorer      = @(Get-WMIObject -Query "Select * From Win32_Process Where (Name='explorer.exe' or Name='pfwsmgr.exe')")[0]
      $EnvUSERNAME   = $Env:USERNAME

      Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "The process 'explorer.exe' or 'pfwsmgr.exe' is running $($Explorer.Count) times."
         
      For ($a=0; $a -lt ($Explorer.Count); $a++)
       {
        $UserName      = ($Explorer[$a]).GetOwner()
        If ($($UserName.User) -eq $EnvUSERNAME)
         {
          $SID           = (($Explorer[$a]).GetOwnerSID()).SID
          Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "A valid SID '$SID' has been found for user '$EnvUSERNAME'." 
          Break
         }
       }
     }
    Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "Found domain:        $($UserName.Domain)."
    Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "Found username:      $($UserName.User)."
    Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "Found SID:           $SID."
        
    $UserAndDomain      = "$($Username.Domain )\$($Username.User)".ToUpper()
    $tmpScriptAccount   = (whoami /user /FO csv | convertfrom-csv)
    $TranslatedUserName = $tmpScriptAccount.PSObject.Properties.Name[0]
    $ScriptAccount      = $($tmpScriptAccount.$TranslatedUserName).ToUpper()

    Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "Found scriptaccount: $ScriptAccount"
    
    Return $($Username.User),$UserAndDomain,$SID,$ScriptAccount
   }

  Function Find-Language
   {
    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Created on:       30-Dec-20
    Created by:       Willem-Jan Vroom 
    Organisation:                      
    Functionname:     Find-Language
    =============================================================================================================================================
    .SYNOPSIS

    This function works the best, even running under system context.
    Get-UiCulture returns the UICulture details from the account that is used. So when the system account is used, incorrect
    details might be shown.

    #>

    [CmdletBinding()]
    Param
     (
      [Parameter(Mandatory = $True)][ValidateNotNullOrEmpty()][String] $CurrentUserSID
     )

    $Result           = "en-US"
    $ThisFunctionName = $MyInvocation.MyCommand.Name

    $RegKey = "REGISTRY::HKEY_USERS\$CurrentUserSID\Control Panel\Desktop"
    $Value  = "PreferredUILanguages"
    if (Test-RegistryKeyValue -Path $RegKey -Name $Value)
     {
      $Result = (get-itemproperty $RegKey | Select -ExpandProperty $Value).Split()[0]
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Regkey '$RegKey' value '$Value' exists. The data is '$Result'."
      Return $Result
     }
    else
     {
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Regkey '$RegKey' value '$Value' does not exists. Skipping to machine settings."
     }

    $RegKey = "REGISTRY::HKEY_USERS\.DEFAULT\Control Panel\Desktop\MuiCached"
    $Value  = "MachinePreferredUILanguages"
    if (Test-RegistryKeyValue -Path $RegKey -Name $Value)
     {
      $Result = (get-itemproperty $RegKey | Select -ExpandProperty $Value).Split()[0]
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Regkey '$RegKey' value '$Value' exists. The data is '$Result'."
      Return $Result
     }

    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "There was a problem reading the registry..."
    Return $Result
   }

  Function Check-SCCMClient
   {
    
    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Created on:       14-Jan-21
    Created by:       Willem-Jan Vroom 
    Organisation:                      
    Functionname:     Check-SCCMClient
    =============================================================================================================================================
    .SYNOPSIS

    This function checks if the SCCM client on the machine is operational. It returns either nothing or an error message.

    #>

    $ErrorMessage             = ""
    $ThisFunctionName         = $MyInvocation.MyCommand.Name

    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $CCMCacheActions.$Language.StartCheckSCCMClient

    Try
     {
      $class = Get-WmiObject -Class 'SMS_CLIENT' -List -Namespace 'root\ccm' -ErrorAction Stop
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $CCMCacheActions.$Language.SCCMClientOk
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $CCMCacheActions.$Language.EndCheckSCCMClient
      Return $True,$ErrorMessage
     }
      Catch
     {
      $ErrorMessage = $($_.Exception.Message).Trim()
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $($CCMCacheActions.$Language.SCCMClientError1)
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $($CCMCacheActions.$Language.SCCMClientError2).Replace("<ErrorMessage>",$ErrorMessage)
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $CCMCacheActions.$Language.EndCheckSCCMClient
      Return $False,$ErrorMessage
     }
   }

  Function CCM-FindApplications
   {

    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Created on:       06-June-2022
    Created by:       Willem-Jan Vroom 
    Organisation:                      
    Functionname:     CCM-FindApplications
    =============================================================================================================================================
    .SYNOPSIS

    This function finds all the via SCCM (installed) applications.

    The EnforcePreference is explained here:
    https://docs.microsoft.com/en-us/mem/configmgr/develop/reference/core/clients/sdk/ccm_application-client-wmi-class

    The EvaluationState is explained here:
    https://docs.microsoft.com/en-us/mem/configmgr/develop/reference/core/clients/sdk/ccm_evaluationstate-client-wmi-class

    #>

    $Applications             = Get-CimInstance -Namespace "Root\ccm\ClientSDK" -ClassName 'CCM_Application' -Verbose:$False
    $CCMComObject             = New-Object -ComObject 'UIResource.UIResourceMgr'
    $CacheInfo                = $($CCMComObject.GetCacheInfo().GetCacheElements())
    $ThisFunctionName         = $MyInvocation.MyCommand.Name

    $EnforcePreference = @{
    0 = "Immediate"
    1 = "NonBusinessHours"
    2 = "AdminSchedule"}

    $EvaluationSate = @{
    0 = "No state information is available."
    1 = "Application is enforced to desired/resolved state."
    2 = "Application is not required on the client."
    3 = "Application is available for enforcement (install or uninstall based on resolved state). Content may/may not have been downloaded."
    4 = "Application last failed to enforce (install/uninstall)."
    5 = "Application is currently waiting for content download to complete."
    6 = "Application is currently waiting for content download to complete."
    7 = "Application is currently waiting for its dependencies to download."
    8 = "Application is currently waiting for a service (maintenance) window."
    9 = "Application is currently waiting for a previously pending reboot."
    10 = "Application is currently waiting for serialized enforcement."
    11 = "Application is currently enforcing dependencies."
    12 = "Application is currently enforcing."
    13 = "Application install/uninstall enforced and soft reboot is pending."
    14 = "Application installed/uninstalled and hard reboot is pending."
    15 = "Update is available but pending installation."
    16 = "Application failed to evaluate."
    17 = "Application is currently waiting for an active user session to enforce."
    18 = "Application is currently waiting for all users to logoff."
    19 = "Application is currently waiting for a user logon."
    20 = "Application in progress, waiting for retry."
    21 = "Application is waiting for presentation mode to be switched off."
    22 = "Application is pre-downloading content (downloading outside of install job)."
    23 = "Application is pre-downloading dependent content (downloading outside of install job)."
    24 = "Application download failed (downloading during install job)."
    25 = "Application pre-downloading failed (downloading outside of install job)."
    26 = "Download success (downloading during install job)."
    27 = "Post-enforce evaluation."
    28 = "Waiting for network connectivity."}

    ForEach ($Application in $Applications)
     {
      $tmpName                 = ""
      $tmpDeploymentType       = ""
      $tmpEvaluation           = ""
      $tmpContentID            = ""
      $tmpContentLocation      = ""
      $tmpContentSize          = ""
      $tmpLastReferenceTime    = ""

      $AppDTS = ($Application | Get-CimInstance).AppDTs
      ForEach ($AppDT in $AppDTS)
       {
        ForEach ($ActionType in $AppDT.AllowedActions)
         {
          $Arguments = [hashtable]@{
          'AppDeliveryTypeID' = [string]$($AppDT.ID)
          'Revision'          = [uint32]$($AppDT.Revision)
          'ActionType'        = [string]$($ActionType)}
          $AppContentID = (Invoke-CimMethod -Namespace 'Root\ccm\cimodels' -ClassName 'CCM_AppDeliveryType' -MethodName 'GetContentInfo' -Arguments $Arguments -Verbose:$False).ContentID

          $tmpName            = $Application.Name
          $tmpDeploymentType  = "$($AppDT.Name) -> Action: $ActionType"
          $tmpEvaluation      = $($EvaluationSate.Item([int]$($Application.EvaluationState)))
           

          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Application: Name:                 $tmpName"
          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Application: Enforce preference:   $($EnforcePreference.Item([int]$($Application.EnforcePreference)))"
          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Application: Evaluation:           $tmpEvaluation"
          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Application: Deployment Type name: $($AppDT.Name)"
          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Application: Action:               $($ActionType)"
 
          $AppCacheInfo = $CacheInfo | Where-Object { $($_.ContentID) -eq $AppContentID }
          If ($AppCacheInfo)
           {
            $tmpContentID         = $AppCacheInfo.ContentID
            $tmpContentLocation   = $AppCacheInfo.Location
            $tmpContentSize       = $AppCacheInfo.ContentSize
            $tmpLastReferenceTime = $AppCacheInfo.LastReferenceTime
         
            Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Application: Content ID:           $tmpContentID"
            Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Application: Location:             $tmpContentLocation"
            Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Application: Content size:         $('{0:N2}' -f $($tmpContentSize / 1KB))Mb"
            Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Application: Last Reference time:  $tmpLastReferenceTime"
           }
          Add-EntryToResultsFile -Name $tmpName -DeploymentType $tmpDeploymentType -Evaluation $tmpEvaluation -ContentID $tmpContentID -ContentLocation $tmpContentLocation -LastReferenceTime $tmpLastReferenceTime -ContentSize $tmpContentSize
         }
       }
     }
   }

  Function CCM-FindPrograms
   {

    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Created on:       06-June-2022
    Created by:       Willem-Jan Vroom 
    Organisation:                      
    Functionname:     CCM-FindPrograms
    =============================================================================================================================================
    .SYNOPSIS

    This function finds all the via SCCM (installed) programs.

    #>

    $Packages                 = Get-CimInstance -Namespace 'ROOT\ccm\Policy\Machine\RequestedConfig' -ClassName 'CCM_SoftwareDistribution' -Verbose:$False
    $CCMComObject             = New-Object -ComObject 'UIResource.UIResourceMgr'
    $CacheInfo                = $($CCMComObject.GetCacheInfo().GetCacheElements())
    $ThisFunctionName         = $MyInvocation.MyCommand.Name

    ForEach ($Package in $Packages)
     {
      $tmpName                 = $Package.PKG_MIFName
      $tmpProgram              = $Package.PRG_ProgramID
      $tmpContentID            = ""
      $tmpContentLocation      = ""
      $tmpContentSize          = ""
      $tmpLastReferenceTime    = ""

      $PkgCacheInfo = $CacheInfo | Where-Object { $_.ContentID -eq $Package.PKG_PackageID }

      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Program: Name:                 $tmpName"
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Program: Program name:         $tmpProgram"

      # Only take the last items from the cache information. 
      If (($($PkgCacheInfo | Measure-Object).Count) -gt 1){$PkgCacheInfo1 = $PkgCacheInfo[-1]} else {$PkgCacheInfo1 = $PkgCacheInfo}

      ForEach ($CacheItem in $PkgCacheInfo1)
       {
        If ($CacheItem) 
         {
          #  Set content size to 0 if null to avoid division by 0
          If ($CacheItem.ContentSize -eq 0) { [int]$PkgContentSize = 0 } Else { [int]$PkgContentSize = $($CacheItem.ContentSize) }
          $tmpContentID         = $CacheItem.ContentID
          $tmpContentLocation   = $CacheItem.Location
          $tmpContentSize       = $PkgContentSize
          $tmpLastReferenceTime = $CacheItem.LastReferenceTime

          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Package: Content ID:           $tmpContentID"
          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Package: Location:             $tmpContentLocation"
          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Package: Content size:         $('{0:N2}' -f $($PkgContentSize / 1KB))Mb"
          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Package: Last reference time:  $tmpLastReferenceTime"   
         }
       }
      Add-EntryToResultsFile -Name $tmpName -Program $tmpProgram -ContentID $tmpContentID -ContentLocation $tmpContentLocation -LastReferenceTime $tmpLastReferenceTime -ContentSize $tmpContentSize
     }
   }

  Function CCM-FindUpdates
   {

    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Created on:       06-June-2022
    Created by:       Willem-Jan Vroom 
    Organisation:                      
    Functionname:     CCM-FindUpdates
    =============================================================================================================================================
    .SYNOPSIS

    This function finds all the via SCCM installed updates.
    The Classification Type is explained here:
    https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ff357803(v=vs.85)

    #>

    $CCMComObject             = New-Object -ComObject 'UIResource.UIResourceMgr'
    $CacheInfo                = $($CCMComObject.GetCacheInfo().GetCacheElements())
    $Updates                  = Get-CimInstance -Namespace 'Root\ccm\SoftwareUpdates\UpdatesStore' -ClassName 'CCM_UpdateStatus' -Verbose:$False
    $ThisFunctionName         = $MyInvocation.MyCommand.Name

    $ClassificationType = @{
    "5C9376AB-8CE6-464A-B136-22113DD69801" = "Application"
    "434DE588-ED14-48F5-8EED-A15E09A991F6" = "Connectors"
    "E6CF1350-C01B-414D-A61F-263D14D133B4" = "CriticalUpdates"
    "E0789628-CE08-4437-BE74-2495B842F43B" = "DefinitionUpdates"
    "E140075D-8433-45C3-AD87-E72345B36078" = "DeveloperKits"
    "B54E7D24-7ADD-428F-8B75-90A396FA584F" = "FeaturePacks"
    "9511D615-35B2-47BB-927F-F73D8E9260BB" = "Guidance"
    "0FA1201D-4330-4FA8-8AE9-B877473B6441" = "SecurityUpdates"
    "68C5B0A3-D1A6-4553-AE49-01D3A7827828" = "ServicePacks"
    "B4832BD8-E735-4761-8DAF-37F882276DAB" = "Tools"
    "28BC880E-0592-4CBF-8F95-C79B17911D5F" = "UpdateRollups"
    "CD5FFD1E-E932-4E3A-BF74-18BF0B1BBD83" = "Updates"} 

    ForEach ($Update in $Updates)
     {
      $tmpName                 = "$($Update.Title) Bulletin ID: $($Update.Article)"
      $tmpEvaluation           = $Update.Status
      $tmpUpdateClassification = $($ClassificationType.Item($($Update.UpdateClassification)))
      $tmpContentID            = ""
      $tmpContentLocation      = ""
      $tmpContentSize          = ""
      $tmpLastReferenceTime    = ""

      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Update: Title and  Bulletin ID:   $tmpName"
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Update: Evaluation:               $tmpEvaluation"

      $UpdateCacheInfo = $CacheInfo | Where-Object { $_.ContentID -eq $Update.UniqueID}
      If ($UpdateCacheInfo)
       {
        ForEach ($Update in $UpdateCacheInfo)
         {
          $tmpContentID            = $Update.ContentID
          $tmpContentLocation      = $Update.Location
          $tmpContentSize          = $Update.ContentSize
          $tmpLastReferenceTime    = $Update.LastReferenceTime

          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Update: Content ID:               $tmpContentID"
          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Update: Location:                 $tmpContentLocation"
          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Update: Content size:             $('{0:N2}' -f $($tmpContentSize / 1KB))Mb"
          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Update: Last reference time:      $tmpLastReferenceTime"
         }   

       }
      Add-EntryToResultsFile -Name $tmpName -Evaluation $tmpEvaluation -UpdateClassification $tmpUpdateClassification -ContentID $tmpContentID -ContentLocation $tmpContentLocation -LastReferenceTime $tmpLastReferenceTime  -ContentSize $tmpContentSize  
     }
   }

  Function CCM-FindOrphanedCCMFolders
   {

    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Created on:       06-June-2022
    Created by:       Willem-Jan Vroom 
    Organisation:                      
    Functionname:     CCM-FindOrphanedCCMFolders
    =============================================================================================================================================
    .SYNOPSIS

    This function finds all the folders in the CCMCache that are no longer needed or used. Also, there is no write action in that folder for
    at least one hour.

    Also, there might be folders that are still in the CCMCache, have details like ContentID but that content is no longer used by any application,
    program or update.

    Skip Content Locations that contains a dot. 
        
    #>

    $CCMComObject                    = New-Object -ComObject 'UIResource.UIResourceMgr'
    $CCMCacheFolderUsedByDeployments = $CCMComObject.GetCacheInfo().GetCacheElements()
    $CacheLocation                   = $CCMComObject.GetCacheInfo().Location
    $AllFoldersInCache               = (Get-ChildItem $CacheLocation -Directory).FullName
    $ThisFunctionName                = $MyInvocation.MyCommand.Name
    
    $FoundOrhanedFolders             = @()

    $Record  = [ordered] @{"Orphaned Folder Name"  = "";
                           "ContentId"             = ""}

  # =============================================================================================================================================
  # Step 1: Check for folders in the CacheLocation (for example C:\windows\ccmcache) that are not used by any application, program or update.
  #         Add these folders to the array FoundOrhanedFolders
  # =============================================================================================================================================
  
    ForEach ($FolderInCache in $AllFoldersInCache)
     {
      If (($CCMCacheFolderUsedByDeployments | ForEach-Object {$_.Location}) -notcontains $FolderInCache)
       {
        if ((Get-ItemProperty $FolderInCache).LastWriteTime -le (get-date).AddHours(-1) -and -not $FolderInCache.Contains("."))
         {
          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $($CCMCacheActions.$Language.OrphanedFolder).Replace("<FolderInCache>",$FolderInCache)
          $Record."Orphaned Folder Name" = $FolderInCache
          $objRecord                      = New-Object PSObject -Property $Record
          $FoundOrhanedFolders           += $objRecord
         }
       }
     }

  # =============================================================================================================================================
  # Step 2: The array Global:arrTable contains all the found applications, programs and updates, including the content location.
  #         Now, we check if there are folders in the CacheLocation (for example C:\windows\ccmcache) that are not in the array Global:arrTable. 
  #         Add these folders to the array FoundOrhanedFolders.
  # =============================================================================================================================================
  
    ForEach ($FolderInCache in ($CCMCacheFolderUsedByDeployments | ForEach-Object {$_.Location} | Where-Object {$_ -notin $Global:arrTable."Content Location"}))
     {
      $ContentID = $CCMCacheFolderUsedByDeployments | Where-Object {$_.Location -eq $FolderInCache} | ForEach-Object {$_.ContentId}
      $Record."Orphaned Folder Name" = $FolderInCache
      $Record."ContentId"            = $ContentID
      $objRecord                      = New-Object PSObject -Property $Record
      $FoundOrhanedFolders           += $objRecord
     }

    $FoundOrhanedFolders = $FoundOrhanedFolders | Sort-Object "Orphaned Folder Name"

    Return $FoundOrhanedFolders
   }

  Function CCM-DeleteItemFromCache
   {

    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Created on:       09-June-2022 / Modified on 29_nov-2022: check on an empty CacheElementID and an empty $CacheInfoArray.
    Created by:       Willem-Jan Vroom 
    Organisation:                      
    Functionname:     CCM-DeleteItemFromCache
    =============================================================================================================================================
    .SYNOPSIS

    This function removes the specified item from the cache.
        
    #>

    [CmdletBinding()]

    param
    (
     [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [String]  $ContentID,
     [Parameter(Mandatory = $False)]                           [Boolean] $RemoveIfPersistentInCache = $False,
     [Parameter(Mandatory = $False)][ValidateNotNullorEmpty()] [int16]   $ReferencedThreshold = 2,
     [Parameter(Mandatory = $True)]                            [String]  $ContentLocation
    )
  
    $CCMComObject                    = New-Object -ComObject 'UIResource.UIResourceMgr'
    $ThisFunctionName                = $MyInvocation.MyCommand.Name
    $CacheInfoArray                  = @($CCMComObject.GetCacheInfo().GetCacheElements() | Where-Object { ($_.ContentID -eq $ContentID) -and ($_.Location -eq $ContentLocation) })
    $ReturnValue                     = $True
    $ReturnReason                    = ""
    [datetime]$OlderThan             = (Get-Date).ToUniversalTime().AddDays(-$ReferencedThreshold)
    $ReturnText                      = @{
    1 = $($CCMCacheActions.$Language.CCMDeleted_1)                                                           # The item has been removed successfully.
    2 = $($CCMCacheActions.$Language.CCMDeleted_2)                                                           # This item is used by other items, therefore not removed.
    3 = $($CCMCacheActions.$Language.CCMDeleted_3).Replace("<ReferencedThreshold>",$ReferencedThreshold)     # The content has been used in the last <ReferencedThreshold> days.
    4 = $($CCMCacheActions.$Language.CCMDeleted_4)                                                           # The item has not been removed successfully.
    5 = $($CCMCacheActions.$Language.CCMDeleted_5)                                                           # The item has no valid Last Reference Time, therefore skipped.
    6 = $($CCMCacheActions.$Language.CCMDeleted_6)                                                           # The item must remain in cache, therefore not removed.
	7 = $($CCMCacheActions.$Language.CCMDeleted_7)                                                           # The CacheElementID is empty, nothing is done.
    8 = $($CCMCacheActions.$Language.CCMDeleted_8)                                                           # Skipped as this item has already been removed.
    }
   
    If ($($CacheInfoArray.Count) -eq 0)
     {
      $ReturnNumber = 8
      $ReturnValue  = $True
     }
      else
     {
      ForEach ($CacheInfo in $CacheInfoArray)
       {

      # =============================================================================================================================================
      # Only delete this entry if not referenced by other items and not used in the last number of <ReferencedThreshold> days.
      # =============================================================================================================================================

        If ($([string]$($CacheInfo.CacheElementID)))
         {
          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "----> Processing $([string]$($CacheInfo.CacheElementID))"
          If ($CacheInfo.LastReferenceTime)
           {
            Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "----> The last reference time is $($CacheInfo.LastReferenceTime)."
            If ( (($CacheInfo.ReferenceCount) -lt 1) -and ([datetime]($CacheInfo.LastReferenceTime) -le $OlderThan) )
             {
              $Result    = $CCMComObject.GetCacheInfo().DeleteCacheElementEx([string]$($CacheInfo.CacheElementID), [bool]$RemoveIfPersistentInCache)

            # =============================================================================================================================================
            # Check after the deletion of the item.
            # =============================================================================================================================================

              $CacheInfo = $CCMComObject.GetCacheInfo().GetCacheElements() | Where-Object { ($_.ContentID -eq $ContentID) -and ($_.Location -eq $ContentLocation) }

            # =============================================================================================================================================
            # Remove the object and check if the item has been deleted successfully.
            # The variable $Result is always empty. So you have the check by quering the CCM Object.
            # =============================================================================================================================================
      
              If ( -not($CacheInfo))    
                {
                 $ReturnNumber = 1
                 $ReturnValue  = $True
                } 
                 else 
                {

                 If ($RemoveIfPersistentInCache)
                  {
                   $ReturnNumber = 6
                   $ReturnValue  = $False
                  }
                   else
                  {
                   $ReturnNumber = 4
                   $ReturnValue  = $False
                  }
                }
             }
              else
             {
        
            # =============================================================================================================================================
            # The content is used by other items or the content is used within the number of <ReferencedThreshold> days. 
            # So, checking. 
            # =============================================================================================================================================
        
              If (-not($CacheInfo.ReferenceCount) -lt 1)  
               {
                $ReturnNumber = 2
                $ReturnValue  = $False
               }
                else
               {
                $ReturnNumber = 3
                $ReturnValue  = $False
               } 
             }
           }
            else
           {
      
          # =============================================================================================================================================
          # No valid reference time.
          # =============================================================================================================================================
      
            $ReturnNumber = 5
            $ReturnValue  = $False 
           }
         }
          else
         {
          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "----> The CacheElementID appears to be empty..."
		  $ReturnNumber = 7
         }
       }

     }
    
    Return $ReturnValue, $($ReturnText.Item($ReturnNumber))
   }

# =============================================================================================================================================
# End function block
# =============================================================================================================================================

# =============================================================================================================================================
# Functions, used in the forms blocks
# =============================================================================================================================================

  Function SelectOrUnselectAllItemsInDGV
   {

    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Functionname:     SelectOrUnselectAllItemsInDGV
    =============================================================================================================================================
    .SYNOPSIS

    Selects only the items that are in the local CCMCache. So these items can be cleared from the CCMCache.
        
    #>

    $ThisFunctionName                = $MyInvocation.MyCommand.Name
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $($CCMCacheActions.$Language.PressedButtonName).Replace("<ButtonName>",$CCMCacheActions.$Language.chkAllItems)
    If ($chkAllItems.Checked)
     {
      $dgvCacheItems.SelectAll()
      ForEach ($Row in $dgvCacheItems.SelectedRows)
       {
        If ( -not($dgvCacheItems.Rows[$Row.Index].Cells['Content ID'].Value))
         {
          $dgvCacheItems.Rows[$Row.Index].Selected = $False
         }
       }
     }
      else
     {
      $dgvCacheItems.ClearSelection()
     }
   }

  Function FindAppsProgramsAndUpdates
   {
    
    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Functionname:     FindAppsProgramsAndUpdates
    =============================================================================================================================================
    .SYNOPSIS

    Calls the functions to find all the installed applications, programs and updates and fills the datagrid dgvCacheItems. 
        
    #>
    
    $ThisFunctionName                      = $MyInvocation.MyCommand.Name
    $chkOnlyCachedItems.Enabled            = $False
    $chkDeleteImmediately.Enabled          = $False
    $btnClearCache.Enabled                 = $False
    $chkAllItems.Enabled                   = $False 
    
    If ($OnlyItemsInCCMCache) {$chkOnlyCachedItems.Checked = $True}   
        
    $frmProcessing                         = New-Object 'System.Windows.Forms.Form'
    $txtMessage                            = New-Object 'System.Windows.Forms.TextBox'
        
    $frmProcessing.Controls.Add($txtMessage)
    $frmProcessing.AutoScaleDimensions     = New-Object System.Drawing.SizeF(6, 13)
    $frmProcessing.AutoScaleMode           = 'Font'
    $frmProcessing.BackColor               = [System.Drawing.SystemColors]::ControlDark 
    $frmProcessing.ClientSize              = New-Object System.Drawing.Size(430, 44)
    $frmProcessing.FormBorderStyle         = 'None'
    $frmProcessing.Name                    = 'frmProcessing'
    $frmProcessing.ShowIcon                = $False
    $frmProcessing.StartPosition           = 'CenterParent'
    $frmProcessing.Text = "Form"
    $frmProcessing.add_Shown({$Global:arrTable = @()
                              $tmpTable        = @()
                              CCM-FindApplications
                              CCM-FindPrograms
                              CCM-FindUpdates            
                              [void]$frmProcessing.Close()
                              [void]$frmProcessing.Dispose()
                              })
    #
    # txtMessage
    #
    $txtMessage.ReadOnly                   = $True
    $txtMessage.Location                   = New-Object System.Drawing.Point(12, 12)
    $txtMessage.MaxLength                  = 50
    $txtMessage.Name                       = 'txtMessage'
    $txtMessage.Size                       = New-Object System.Drawing.Size(400, 20)
    $txtMessage.TabIndex                   = 0
    $txtMessage.Text                       = $CCMCacheActions.$Language.txtMessage
    $txtMessage.TextAlign                  = 'Center'
    $txtMessage.WordWrap                   = $False

    [void]$frmProcessing.ShowDialog()
    $dgvCacheItems.DataSource = $null
    $dgvCacheItems.Rows.Clear()

    if ($Global:arrTable.Count -gt 0)
     {
      $chkOnlyCachedItems.Enabled       = $True
      $btnClearCache.Enabled            = $True
      If (@($Global:arrTable | Where-Object {$_."Content ID"}).Count -gt 0)  {$chkDeleteImmediately.Enabled     = $True; $chkAllItems.Enabled = $true}
      $Global:arrTable = $Global:arrTable | Sort-Object -Property "Name","Deployment Type","Program","Evaluation","Update Classification","Content ID","Content Location","Content Size","Content Last Reference Time" -Unique
      If ($chkOnlyCachedItems.Checked)
       {
        $tmpTable = @($Global:arrTable | Where-Object {$_."Content ID"})
       }
        else
       {
        $tmpTable = @($Global:arrTable)
       }
     }
        
    if ($tmpTable.Count -gt 0)
     {
      $dgvCacheItems.DataSource = [System.Collections.ArrayList]$tmpTable
      $dgvCacheItems.Update()
      $dgvCacheItems.SelectAll()
      ForEach ($Row in $dgvCacheItems.SelectedRows)
       {
        If (($dgvCacheItems.Rows[$Row.Index].Cells['Content ID'].Value).Length -eq 0)
         {
          $dgvCacheItems.Rows[$Row.Index].DefaultCellStyle.BackColor = [System.Drawing.Color]::FromArgb(255, 249, 249, 249)
          $dgvCacheItems.Rows[$Row.Index].DefaultCellStyle.ForeColor = [System.Drawing.Color]::FromArgb(255, 160, 160, 160)
         }
       }
      $dgvCacheItems.ClearSelection()
     }
   }

   Function ShowOnlyCachedItems
    {

     <#
     .NOTES
     =============================================================================================================================================
     Created with:     Windows PowerShell ISE
     Functionname:     ShowOnlyCachedItems
     =============================================================================================================================================
     .SYNOPSIS

     Displays only the items that are in the local CCMCache. So these items can be cleared from the CCMCache.
        
     #>

     $ThisFunctionName                = $MyInvocation.MyCommand.Name
     Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $($CCMCacheActions.$Language.PressedButtonName).Replace("<ButtonName>",$CCMCacheActions.$Language.chkOnlyCachedItems).Replace("&","")

     $dgvCacheItems.DataSource = $null
     $dgvCacheItems.Rows.Clear()
     If ($chkOnlyCachedItems.Checked)
      {
       $tmpTable = @($Global:arrTable | Where-Object {$_."Content ID"})
       Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $($CCMCacheActions.$Language.NumberOfItemsInCache).Replace("<Number>",$tmpTable.Count)
       if ($tmpTable.Count -gt 0)
        {
         $dgvCacheItems.DataSource = $Null
         $dgvCacheItems.DataSource = [System.Collections.ArrayList]$tmpTable
         $dgvCacheItems.Update()
        }
      }
       else
      {
       if ($Global:arrTable.Count -gt 0)
        {
         $dgvCacheItems.DataSource = $Null
         $dgvCacheItems.DataSource = [System.Collections.ArrayList]$Global:arrTable
         $dgvCacheItems.Update()
         $dgvCacheItems.SelectAll()
         ForEach ($Row in $dgvCacheItems.SelectedRows)
          {
           If (($dgvCacheItems.Rows[$Row.Index].Cells['Content ID'].Value).Length -eq 0)
            {
             $dgvCacheItems.Rows[$Row.Index].DefaultCellStyle.BackColor = [System.Drawing.Color]::FromArgb(255, 249, 249, 249)
             $dgvCacheItems.Rows[$Row.Index].DefaultCellStyle.ForeColor = [System.Drawing.Color]::FromArgb(255, 160, 160, 160)
            }
          }
         $dgvCacheItems.ClearSelection()
        }
      }
    }
  
  Function DeleteOrphanedFolders
   {

    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Functionname:     DeleteOrphanedFolders
    =============================================================================================================================================
    .SYNOPSIS

    Deletes the non used folders from the CCMCache.
        
    #>

    $ThisFunctionName                = $MyInvocation.MyCommand.Name
    $ArrayOrphanedFolders            = @(CCM-FindOrphanedCCMFolders)
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $($CCMCacheActions.$Language.PressedButtonName).Replace("<ButtonName>",$CCMCacheActions.$Language.btnDeleteOrphanedFolders).Replace("&","")
    If ($ArrayOrphanedFolders.Count -gt 0)
     {
      $OrphanedFolders                 = $ArrayOrphanedFolders | ForEach-Object {$_."Orphaned Folder Name"}  | Out-String
      $Text                            = "$($CCMCacheActions.$Language.ConfirmRemovalFolders)`n`n$OrphanedFolders"
      $Result                          = Display-MessageBox -Text $Text -GUI -Button OkCancel -Title "$ApplicationName $ApplicationVersion"
      If ($Result -eq 1)
       {
        ForEach ($OrphanedFolder in $ArrayOrphanedFolders)
         {
          if (-not $OrphanedFolder."ContentID")
           {
            Add-EntryToLogFile -Entry $($CCMCacheActions.$Language.DeletingFolder).Replace("<FolderName>",$($OrphanedFolder."Orphaned Folder Name")) -FunctionName $ThisFunctionName
            Remove-Item -Path $($OrphanedFolder."Orphaned Folder Name") -Recurse -Force
           }
            else
           {
            $Result,$ResultText = CCM-DeleteItemFromCache -ContentID $($OrphanedFolder."ContentID") -ContentLocation $($OrphanedFolder."Orphaned Folder Name") -RemoveIfPersistentInCache:$True -ReferencedThreshold 0
            Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "$($OrphanedFolder."Orphaned Folder Name") --> $ResultText"
           }
         }
        $btnDeleteOrphanedFolders.Enabled = $False
       }
     }
      else
     {
      $Text       = $CCMCacheActions.$Language.NoOrphanedFolders
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $Text 
      $Result     = Display-MessageBox -Text $Text -GUI -Title "$ApplicationName $ApplicationVersion"
     }
   }

  Function RemoveItemsFromCache
   {

    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Functionname:     RemoveItemsFromCache
    =============================================================================================================================================
    .SYNOPSIS

    Removes an item from the CCMCache.
        
    #>

    $ThisFunctionName                = $MyInvocation.MyCommand.Name
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $($CCMCacheActions.$Language.PressedButtonName).Replace("<ButtonName>",$CCMCacheActions.$Language.btnClearCache).Replace("&","")
    $Text = "$($CCMCacheActions.$Language.RemovedFromCache)`n"
    $Rerun_FindAppsProgramsAndUpdates = $null
    If ($chkDeleteImmediately.Checked) {$ReferenceDays = 0} else {$ReferenceDays = 2}
    ForEach($Row in $dgvCacheItems.SelectedRows)
     {
      $SelectedContentID       = $dgvCacheItems.Rows[$Row.Index].Cells['Content ID'].Value
      $SelectedContentLocation = $dgvCacheItems.Rows[$Row.Index].Cells['Content Location'].Value
      $AppName                 = $dgvCacheItems.Rows[$Row.Index].Cells['Name'].Value
      $ProgName                = $dgvCacheItems.Rows[$Row.Index].Cells['Program'].Value
      $DTName                  = $dgvCacheItems.Rows[$Row.Index].Cells['Deployment Type'].Value
      If ($ProgName)           {$AppName += " (Program: $ProgName)"}
      If ($DTName)             {$AppName += " (Deployemnt type: $DTName)"}
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Removing from cache: $AppName"
      $Result,$ResultText = CCM-DeleteItemFromCache -ContentID $SelectedContentID -ContentLocation $SelectedContentLocation -ReferencedThreshold $ReferenceDays
      If ($Result)
       {
        $Line = $($CCMCacheActions.$Language.RemovedFromCacheSuccess).Replace("<Cell>",$AppName)
        Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $Line
        $Rerun_FindAppsProgramsAndUpdates = $True
       }
        else
       {
        $Line = $($CCMCacheActions.$Language.RemovedFromCacheFailure).Replace("<Cell>",$AppName).Replace("<ResultText>",$ResultText)
        Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $Line
        If (-not($Rerun_FindAppsProgramsAndUpdates)) {$Rerun_FindAppsProgramsAndUpdates = $False}
       }
      $Text +="`n$Line`n"
     }
    $Result = Display-MessageBox -Text $Text -GUI -Button Ok -Title "$ApplicationName $ApplicationVersion"
    If ($Rerun_FindAppsProgramsAndUpdates)
     {
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $CCMCacheActions.$Language.txtMessage
      $Global:arrTable = @()
      FindAppsProgramsAndUpdates
     }
   }

# =============================================================================================================================================
# End functions, used in the forms blocks.
# =============================================================================================================================================

# =============================================================================================================================================
# Show the supported languages
# =============================================================================================================================================

  If ($OverviewSupportedLanguages)
   {
    Clear-Host
    $PSFile                      = $PSCommandPath
    $JSONFile                    = $PSFile.Replace(".ps1",".json")
    If (Test-Path($JSONFile))
      {
       $JSONObject                  = Get-Content -Path $JSONFile -Raw -Encoding UTF8 | ConvertFrom-Json    
       $Languages                   = @(($JSONObject | Get-Member -type NoteProperty).Name)
       $Languages                   = $Languages | Sort-Object
       Write-Host "$($Languages.Count) Supported languages:"
       ForEach ($Language in $Languages)
        {
         $LanguageName = ((([CultureInfo]::GetCultures([System.Globalization.CultureTypes]::SpecificCultures) | Where {$_.Name -like "$Language*"})[0]).DisplayName).Split(" ")[0]
         Write-Host " - Use '$Language' for the $LanguageName language." 
        }
       
       Write-Host "Use '$([char](34))$PSFile$([char](34)) -LanguageOverride <language>' to override the language."
      }
       else
      {
       Write-Host "The file '$JSONFile' does not exists."
      }
    Exit 0
   }

# =============================================================================================================================================
# Declares the variables.
# =============================================================================================================================================

  $Global:gblDetailedLogging                     = $DetailedLogging
  $strCurrentDir                                 = Split-Path -parent $MyInvocation.MyCommand.Definition
  $ApplicationName                               = "Clear Software Center Cache"
  $ApplicationVersion                            = "v1.3"
  $PowerShellLanguageMode                        = $ExecutionContext.SessionState.LanguageMode
  $Global:gblFullLanguage                        = $PowerShellLanguageMode -eq "FullLanguage"
  $Global:gblElevatedRights                      = Check-HasElevatedRights
  $MinimumWidth                                  = 1000
  $MinimumHeight                                 = 700
  $ErrorNumber                                   = 0
  $ErrorText                                     = ""
  
# =============================================================================================================================================
# Find the logpath.
# It is the key 'HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders' with the value 'Local AppData'.
# And then '\temp' is added. 
# =============================================================================================================================================

  $OnlyUserName,               `
  $LoggedOnUserInDomainFormat, `
  $UseridSID,                  `
  $InstallAccount                 = UserDetails
  $RegKey                         = "REGISTRY::HKEY_USERS\$UseridSID\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
  $ValueName                      = "Local AppData"
  $Value                          = (Get-ItemProperty $RegKey).$ValueName
  $LogPath                        = $Value + "\temp"    
  $Global:gblLogPath              = $LogPath

# =============================================================================================================================================
# Define the results file. This file contains all the results.
# =============================================================================================================================================
 
  $strLastPartOfFileName                         = " ($((Get-Date -format "yyyy-MM-dd HH-mm-ss").ToString()))"
  $PreFixLogFile                                 = $ApplicationName -replace(" ","")
  $ThisFunctionName                              = "[Main - Resultsfile]"
   
  If ($Global:gblDetailedLogging)
   {
    $Global:gblLogFile = $Global:gblLogPath + "\"+ $PreFixLogFile + $($strLastPartOfFileName + ".log")
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Logfile: $Global:gblLogFile"
   }

# =============================================================================================================================================
# Find all the arguments and put them in the log file
# Source: https://ss64.com/ps/psboundparameters.html
# =============================================================================================================================================

  $ThisFunctionName                              = "[Main - Arguments]"
  If ($Global:gblFullLanguage)
   {
    $TableWithParameters  = @()
    $RecordWithParameters = [ordered] @{"Key"     = "";
                                        "Value"   = ""}
  
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "***** Parameters part           *****"
  
    ForEach($boundparam in $PSBoundParameters.GetEnumerator()) 
     {
      $tmpValue                     = $($boundparam.Value)
      $Value                        = ""
      If ($tmpValue -is [array])
       {
        ForEach ($object in $tmpValue)
         {
          If (-not($value))
           {
            $Value = $object
           }
            else
           {
            $Value +=",$($object)"
           }
         }
       }
        else
       {
        $Value = $tmpValue
       }
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Key: $($boundparam.Key) Value: $Value" 
     }
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "***** End Parameters part       *****`r`n" 
   }
    else
   {
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "The parameters are not written to the logfile due to PowerShell ConstrainedMode."
   }

# =============================================================================================================================================
# Write the logged in user details to the log file. 
# =============================================================================================================================================
  
  $ThisFunctionName                              = "[Main - User Details]"
  Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "***** User details part         *****" 
  Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Logged on user:                  $LoggedOnUserInDomainFormat"
  Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Logged on user (SID):            $UseridSID"
  Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Has (elevated) admin rights:     $Global:gblElevatedRights"
  Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Installation account:            $InstallAccount"
  If ($Global:gblDetailedLogging)
   {
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Logfile:                         $Global:gblLogFile"
   }
  Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "***** End User details part     *****`r`n"

# =============================================================================================================================================
# Read the JSON file with the translations.
# If the JSON file does not contain the detected language, then fallback to English.
# =============================================================================================================================================

  $ThisFunctionName                              = "[Main - Language]"
  Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "***** Language part             *****"

  if ($LanguageOverride)
   {
    $Language = $LanguageOverride
    Add-EntryToLogFile -FunctionName $ThisFunctionName  -Entry "The parameter -LanguageOverride is used. The language is '$Language'."
   }
    else
   {
    $Language = (Find-Language -CurrentUserSID $UseridSID).SubString(0, 2)
   }

  $JSONFile = $strCurrentDir + "\"+ $($MyInvocation.MyCommand.Name -replace ".ps1",".json")

  if (Test-Path($JSONFile))
   {
    $CCMCacheActions = Get-Content $JSONFile -Encoding UTF8 | ConvertFrom-Json

    if (-not($CCMCacheActions.$Language))
     {
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "The language '$Language' is not found in the json file '$JSONFile'."
      $Language = "en"
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Falling back to the default language '$Language'."
     }

    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "The language '$Language' is used."

    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "***** End language part         *****`r`n" 

  # =============================================================================================================================================
  # Check if there is a (working) SCCM Client installed.
  # =============================================================================================================================================

    $BoleanSCCMClient,$ErrorMessageRegardingSCCMClient = Check-SCCMClient

    $Error02 = $($CCMCacheActions.$Language.Error02Text)
    $Error04 = $($CCMCacheActions.$Language.Error04Text).Replace("<ErrorSCCMClient>",$ErrorMessageRegardingSCCMClient).Trim()
    $Error08 = $($CCMCacheActions.$Language.Error08Text)

    If (-not $Global:gblFullLanguage)   {$ErrorNumber = $ErrorNumber +  2}
    if (-not $BoleanSCCMClient)         {$ErrorNumber = $ErrorNumber +  4}
    if (-not $Global:gblElevatedRights) {$ErrorNumber = $ErrorNumber +  8}
   } 
   else
   {
    $Error01 = "The '$JSONFile' file with the translations does not exists."
    $ErrorNumber = $ErrorNumber +  1
   }

# =============================================================================================================================================
# End reading the JSON file with the translations.
# =============================================================================================================================================

# =============================================================================================================================================
# Add assembly (only FullLanguage)
# =============================================================================================================================================
  
  If ($Global:gblFullLanguage)
   {    
    Add-Type -AssemblyName System.Windows.Forms
    Add-Type -AssemblyName System.Drawing
    Add-Type -AssemblyName System.Runtime
    Add-Type -AssemblyName PresentationFramework 
    [System.Windows.Forms.Application]::EnableVisualStyles()
    $DetectedWidth  = ([System.Windows.Forms.SystemInformation]::PrimaryMonitorSize).Width
    $DetectedHeight = ([System.Windows.Forms.SystemInformation]::PrimaryMonitorSize).Height
    If ($DetectedWidth -le $MinimumWidth)   
     {
      $ErrorNumber = $ErrorNumber + 16
      $Error16 = $($CCMCacheActions.$Language.Error16Text).Replace("<DetectedWidth>",$DetectedWidth).Replace("<AllowedWidth>",$MinimumWidth)
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $Error16
     }
    If ($DetectedHeight -le $MinimumHeight) 
     {
      $ErrorNumber = $ErrorNumber + 32
      $Error32 = $($CCMCacheActions.$Language.Error32Text).Replace("<DetectedHeight>",$DetectedHeight).Replace("<AllowedHeight>",$MinimumHeight)
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $Error32
     }
   }

# =============================================================================================================================================
# Error handling.
# =============================================================================================================================================

  If (($ErrorNumber -band 1) -eq 1)      {If ($ErrorText) {$ErrorText += "`n$Error01"}  else {$ErrorText = $Error01}}
  If (($ErrorNumber -band 2) -eq 2)      {If ($ErrorText) {$ErrorText += "`n$Error02"}  else {$ErrorText = $Error02}}
  If (($ErrorNumber -band 4) -eq 4)      {If ($ErrorText) {$ErrorText += "`n$Error04"}  else {$ErrorText = $Error04}}
  If (($ErrorNumber -band 8) -eq 8)      {If ($ErrorText) {$ErrorText += "`n$Error08"}  else {$ErrorText = $Error08}}
  If (($ErrorNumber -band 16) -eq 16)    {If ($ErrorText) {$ErrorText += "`n$Error16"}  else {$ErrorText = $Error16}}
  If (($ErrorNumber -band 32) -eq 32)    {If ($ErrorText) {$ErrorText += "`n$Error32"}  else {$ErrorText = $Error32}}
  
  If ($ErrorNumber -gt 0)
   {
    $DummyValue = Display-MessageBox -Text $ErrorText -GUI:$Global:gblFullLanguage -Error -Title "$ApplicationName $ApplicationVersion" 
    Exit $ErrorNumber
   }

# =============================================================================================================================================
# End Error handling.
# =============================================================================================================================================

  $frmClearSCCache                                           = New-Object 'System.Windows.Forms.Form'
  $btnDeleteOrphanedFolders                                  = New-Object 'System.Windows.Forms.Button'
  $chkAllItems                                               = New-Object 'System.Windows.Forms.CheckBox'
  $btnCancel                                                 = New-Object 'System.Windows.Forms.Button'
  $btnClearCache                                             = New-Object 'System.Windows.Forms.Button'
  $chkOnlyCachedItems                                        = New-Object 'System.Windows.Forms.CheckBox'
  $dgvCacheItems                                             = New-Object 'System.Windows.Forms.DataGridView'
  $lblDiscoveredItems                                        = New-Object 'System.Windows.Forms.Label'
  $chkDeleteImmediately                                      = New-Object 'System.Windows.Forms.CheckBox'

  #
  # frmClearSCCache
  #
  $frmClearSCCache.Controls.Add($btnDeleteOrphanedFolders)
  $frmClearSCCache.Controls.Add($chkAllItems)
  $frmClearSCCache.Controls.Add($btnCancel)
  $frmClearSCCache.Controls.Add($btnClearCache)
  $frmClearSCCache.Controls.Add($chkOnlyCachedItems)
  $frmClearSCCache.Controls.Add($dgvCacheItems)
  $frmClearSCCache.Controls.Add($lblDiscoveredItems)
  $frmClearSCCache.Controls.Add($chkDeleteImmediately)
  $frmClearSCCache.AutoScaleDimensions                       = New-Object System.Drawing.SizeF(6, 13)
  $frmClearSCCache.AutoScaleMode                             = 'Font'
  $frmClearSCCache.ClientSize                                = New-Object System.Drawing.Size(984, 676)
  $frmClearSCCache.FormBorderStyle                           = 'FixedSingle'
  $frmClearSCCache.Name                                      = 'frmClearSCCache'
  $frmClearSCCache.StartPosition                             = 'CenterScreen'
  $frmClearSCCache.Text                                      = "$($CCMCacheActions.$Language.frmClearSCCache) $ApplicationVersion"
  $frmClearSCCache.add_Load($frmClearSCCache_Load)
  $frmClearSCCache.MaximizeBox                               = $False
  $frmClearSCCache.MinimizeBox                               = $False
  #
  # chkDeleteImmediately
  #
  $chkDeleteImmediately.Location                             = New-Object System.Drawing.Point(13, 645)
  $chkDeleteImmediately.Name                                 = 'chkDeleteImmediately'
  $chkDeleteImmediately.Size                                 = New-Object System.Drawing.Size(400, 25)
  $chkDeleteImmediately.TabIndex                             = 7
  $chkDeleteImmediately.Text                                 = $CCMCacheActions.$Language.chkDeleteImmediately
  $chkDeleteImmediately.UseVisualStyleBackColor              = $True
  # 
  # btnDeleteOrphanedFolders
  #
  $btnDeleteOrphanedFolders.Location                         = New-Object System.Drawing.Point(540, 625)
  $btnDeleteOrphanedFolders.Name                             = 'btnDeleteOrphanedFolders'
  $btnDeleteOrphanedFolders.Size                             = New-Object System.Drawing.Size(140, 45)
  $btnDeleteOrphanedFolders.TabIndex                         = 6
  $btnDeleteOrphanedFolders.Text                             = $CCMCacheActions.$Language.btnDeleteOrphanedFolders
  $btnDeleteOrphanedFolders.UseVisualStyleBackColor          = $True
  #
  # chkAllItems
  #
  $chkAllItems.Location                                      = New-Object System.Drawing.Point(13, 625)
  $chkAllItems.Name                                          = 'chkAllItems'
  $chkAllItems.Size                                          = New-Object System.Drawing.Size(400, 25)
  $chkAllItems.TabIndex                                      = 5
  $chkAllItems.Text                                          = $CCMCacheActions.$Language.chkAllItems
  $chkAllItems.UseVisualStyleBackColor                       = $True
  #
  # btnCancel
  #
  $btnCancel.Location                                        = New-Object System.Drawing.Point(832, 625)
  $btnCancel.Name                                            = 'btnCancel'
  $btnCancel.Size                                            = New-Object System.Drawing.Size(140, 45)
  $btnCancel.TabIndex                                        = 4
  $btnCancel.Text                                            = $CCMCacheActions.$Language.btnCancel
  $btnCancel.UseVisualStyleBackColor                         = $True
  #
  # btnClearCache
  #
  $btnClearCache.Location                                    = New-Object System.Drawing.Point(686, 625)
  $btnClearCache.Name                                        = 'btnClearCache'
  $btnClearCache.Size                                        = New-Object System.Drawing.Size(140, 45)
  $btnClearCache.TabIndex                                    = 3
  $btnClearCache.Text                                        = $CCMCacheActions.$Language.btnClearCache
  $btnClearCache.UseVisualStyleBackColor                     = $True
  #
  # chkOnlyCachedItems
  #
  $chkOnlyCachedItems.Location                               = New-Object System.Drawing.Point(13, 605)
  $chkOnlyCachedItems.Name                                   = 'chkOnlyCachedItems'
  $chkOnlyCachedItems.Size                                   = New-Object System.Drawing.Size(400, 25)
  $chkOnlyCachedItems.TabIndex                               = 2
  $chkOnlyCachedItems.Text                                   = $CCMCacheActions.$Language.chkOnlyCachedItems
  $chkOnlyCachedItems.UseVisualStyleBackColor                = $True
  #
  # dgvCacheItems
  #
  $dgvCacheItems.AllowUserToAddRows                          = $False
  $dgvCacheItems.AllowUserToDeleteRows                       = $False
  $dgvCacheItems.AllowUserToResizeRows                       = $False
  $dgvCacheItems.AutoSizeColumnsMode                         = 'AllCells'
  $dgvCacheItems.AutoSizeRowsMode                            = 'AllCells'
  $dgvCacheItems.ClipboardCopyMode                           = 'Disable'
  $dgvCacheItems.ColumnHeadersHeightSizeMode                 = 'AutoSize'
  $dgvCacheItems.Location                                    = New-Object System.Drawing.Point(13, 36)
  $dgvCacheItems.Name                                        = 'dgvCacheItems'
  $dgvCacheItems.RowTemplate.Resizable                       = 'True'
  $dgvCacheItems.SelectionMode                               = 'FullRowSelect'
  $dgvCacheItems.ShowEditingIcon                             = $False
  $dgvCacheItems.Size                                        = New-Object System.Drawing.Size(959, 562)
  $dgvCacheItems.TabIndex                                    = 1
  $dgvCacheItems.ReadOnly                                    = $True
  #
  # lblDiscoveredItems
  #
  $lblDiscoveredItems.AutoSize                               = $True
  $lblDiscoveredItems.Location                               = New-Object System.Drawing.Point(12, 9)
  $lblDiscoveredItems.Name                                   = 'lblDiscoveredItems'
  $lblDiscoveredItems.Size                                   = New-Object System.Drawing.Size(235, 13)
  $lblDiscoveredItems.TabIndex                               = 0
  $lblDiscoveredItems.Text                                   = $CCMCacheActions.$Language.lblDiscoveredItems
  
# =============================================================================================================================================
# All kind of actions
# =============================================================================================================================================
  
  $btnDeleteOrphanedFolders.Enabled                          = $False

  
  $frmClearSCCache.add_Shown({FindAppsProgramsAndUpdates                              
                              If (@(CCM-FindOrphanedCCMFolders).Count -gt 0) {$btnDeleteOrphanedFolders.Enabled = $True}
                             })


  $chkAllItems.Add_CheckedChanged({SelectOrUnselectAllItemsInDGV})
  $chkOnlyCachedItems.Add_CheckedChanged({ShowOnlyCachedItems})

  $dgvCacheItems.Add_SelectionChanged({
  
# =============================================================================================================================================
# If there is no Content ID then the whole row should be greyed out. 
# See https://stackoverflow.com/questions/72535521/powershell-datagridview-avoid-selection-of-rows-based-on-a-value-in-cell 
# for more background information. 
# =============================================================================================================================================
   
   ForEach ($Row in $dgvCacheItems.SelectedRows)
    {     
     If (($Row.Cells['Content ID'].Value).Length -eq 0)
      {
       $Row.Selected = $False     
       $dgvCacheItems.Rows[$Row.Index].DefaultCellStyle.BackColor = [System.Drawing.Color]::FromArgb(255, 249, 249, 249)
       $dgvCacheItems.Rows[$Row.Index].DefaultCellStyle.ForeColor = [System.Drawing.Color]::FromArgb(255, 160, 160, 160)
      }
      else
      {
       $dgvCacheItems.Rows[$Row.Index].DefaultCellStyle.BackColor = [System.Drawing.Color]::FromArgb(255, 255, 255, 255)
       $dgvCacheItems.Rows[$Row.Index].DefaultCellStyle.ForeColor = [System.Drawing.Color]::FromArgb(255, 0  , 0  , 0)
      }
    }
  })

  $btnCancel.add_Click({
   Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $($CCMCacheActions.$Language.PressedButtonName).Replace("<ButtonName>",$CCMCacheActions.$Language.btnCancel).Replace("&","")
   If ($Global:gblDetailedLogging)
    {
     $Null = Display-MessageBox -Text $($CCMCacheActions.$Language.afterCancel).Replace("<zzz>",$($Global:gblLogFile)) -GUI -Button Ok -Title "$ApplicationName $ApplicationVersion"
    }
   [void]$frmClearSCCache.Close()
   [void]$frmClearSCCache.Dispose()})

  $btnDeleteOrphanedFolders.add_Click({DeleteOrphanedFolders})

  $btnClearCache.add_Click({RemoveItemsFromCache})
  
  [void]$frmClearSCCache.ShowDialog()

# =============================================================================================================================================
# End all kind of actions.
# =============================================================================================================================================

More background information about the script

Introduction

Some additional information might be useful.

Display-MessageBox

I use

[System.Windows.Forms.MessageBox]::Show($this,$Text.Trim(),$($CCMCacheActions.$Language.Error),$Button,"Error")

to make sure that the Message Box is always on top. In the past, I used 

[System.Windows.MessageBox]::Show($Text.Trim(),$($CCMCacheActions.$Language.Error),$Button,"Error")

Powershell Language Mode

The script only works with the PowerShell Language Mode set to FullLanguage. It will throw up an error if that is not the case.

Elevated rights

Elevated rights are needed as 

$(New-Object -ComObject 'UIResource.UIResourceMgr').GetCacheInfo().GetCacheElements()

needs them. Also, elevated rights are needed to remove folders from C:\WINDOWS\CCMCache. 

Elevated rights are mandatory

If that requirement is not met, then an error message is shown:

Error message about ConstrainedMode and no local admin

Removing an item from the CCM Cache (Function CCM-DeleteItemFromCache)

$(New-Object -ComObject 'UIResource.UIResourceMgr').GetCacheInfo().DeleteCacheElementEx([string]$($CacheInfo.CacheElementID), [bool]$RemoveIfPersistentInCache)

 is used to remove an item from the CCM Cache.

The CacheElementID is a part of the 

$(New-Object -ComObject 'UIResource.UIResourceMgr').GetCacheInfo().GetCacheElements()

output.

$CacheInfo = $CCMComObject.GetCacheInfo().GetCacheElements() | Where-Object { ($_.ContentID -eq $ContentID) -and ($_.Location -eq $ContentLocation) }

Then you make sure that only 1 item is returned that can be removed.

When using the option to delete the orphaned folders, this function is also used to remove the folder with a Content ID. 

The script ClearSoftwareCenterCache_v12.ps1

<#
.SYNOPSIS
    Removes selected items from the CCMCache.

.DESCRIPTION
    Removes selected items from the CCMCache.

.EXAMPLE
     Start the application and show the GUI.
     ."ClearSoftwareCenterCache_v12.ps1" 

.EXAMPLE
     Start the application and show the GUI with verbose logging on the screen.
     ."ClearSoftwareCenterCache_v12.ps1" -Verbose

.EXAMPLE
     Start the application and show the GUI with detailed logging to a log file in %TEMP%
     ."ClearSoftwareCenterCache_v12.ps1" -DetailedLogging

.EXAMPLE
     Start the application and show the GUI with detailed logging to a log file in %TEMP% 
     and show only the items that are in the CCM Cache.
     ."ClearSoftwareCenterCache_v12.ps1" -DetailedLogging -OnlyItemsInCCMCache

.EXAMPLE
     Start the application, show the GUI in the Italian GUI language. Enable detailed logging.
     ."ClearSoftwareCenterCache_v12.ps1" -LanguageOverride it -DetailedLogging
     Use the switch OverviewSupportedLanguages the find the supported languages.

.NOTES
    Author:  Willem-Jan Vroom
    Website: 
    Twitter: @TheStingPilot

v0.1 Beta 1:
   * Initial version. 

v0.1 Beta 2:
   * Corrected the translation of the sentence 'You need elevated (admin) rights to run this script.'
   * Modified the function CCM-FindOrphanedCCMFolders.
   * The datagrid has the correct layout for the rows that can be selected.

v0.1 RC1
   * More help added.

v1.0
   * Added the command line option OnlyItemsInCCMCache
   * Added the option to ignore the ReferencedDays, so you can delete the item straight away.

v1.1
   * Better error handling if the removal failed if persistent in cache.
   * Introduction of the 'OverviewSupportedLanguages' switch

v1.2:
   * Changed the date notation of the logfile to yyyy-MM-dd HH-mm-ss, for example '2022-11-14 22-23-09'.
   * The function Userdetaisl has been modified.

Resources:
 - https://SCCM.Zone/Clean-CMClientCache
 - https://stackoverflow.com/questions/5648931/test-if-registry-value-exists
 - https://stackoverflow.com/questions/72535521/powershell-datagridview-avoid-selection-of-rows-based-on-a-value-in-cell
 - https://docs.microsoft.com/en-us/mem/configmgr/develop/reference/core/clients/sdk/ccm_application-client-wmi-class
 - https://docs.microsoft.com/en-us/mem/configmgr/develop/reference/core/clients/sdk/ccm_evaluationstate-client-wmi-class
 - https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ff357803(v=vs.85)
 - https://ss64.com/ps/psboundparameters.html
 - https://community.idera.com/database-tools/powershell/powertips/b/tips/posts/using-solid-alternatives-for-myinvocation

#>

[CmdletBinding()]

Param
  (
   [Parameter(HelpMessage='Enable detailed logging to a log file.')]
   [Switch]   $DetailedLogging,

   [Parameter(HelpMessage='Show only the items that are available in the CCM Cache.')]
   [Switch]   $OnlyItemsInCCMCache,

   [Parameter(HelpMessage='Force a per machine install or uninstall.')]
   [Switch]   $OverviewSupportedLanguages,

   [Parameter(HelpMessage = 'Override the language.')]
   [ValidateScript({
     $JSONFile                    = $PSCommandPath -replace ".ps1",".json"  
     if(Test-Path($JSONFile))
      {
       $JSONObject                  = Get-Content -Path $JSONFile -Raw -Encoding UTF8 | ConvertFrom-Json    
       $Languages                   = @(($JSONObject | Get-Member -type NoteProperty).Name)
       $Languages                   = $Languages | Sort-Object
       if($Languages -contains $_)
        {
         $True
        }
         else
        {
         $SupportedLanguages = ""
         ForEach ($Language in $Languages)
          {
           $LanguageName = ((([CultureInfo]::GetCultures([System.Globalization.CultureTypes]::SpecificCultures) | Where {$_.Name -like "$Language*"})[0]).DisplayName).Split(" ")[0]
           if($SupportedLanguages.Length -eq 0)
            {
             $SupportedLanguages = "$Language ($LanguageName)"
            }
             else
            {
             $SupportedLanguages += ", $Language ($LanguageName)"
            } 
          }
         if($Languages.Count -gt 1)
          {
           $Lastcomma          = $SupportedLanguages.LastIndexOf(", ")
           $Firstpart          = $SupportedLanguages.Substring(0,$Lastcomma)
           $Lastpart           = $SupportedLanguages.SubString($Lastcomma+2,($SupportedLanguages.Length) - $Lastcomma -2)
           Clear-Host
           Throw "$_ is no supported language. The following $($Languages.Count) languages are supported: $Firstpart and $Lastpart."
          }
           else
          {
           Throw "$_ is no supported language. The following language is supported: $SupportedLanguages."
          }
        }
      }
       else
      {
       Write-Host "The JSON File '$JSONFile' is not found."
       Exit 99
      }
     })]
   [String]   $LanguageOverride
  )

  Clear-Host

# =============================================================================================================================================
# Function block
# =============================================================================================================================================

  Function Display-MessageBox
   {

    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Created on:       24-May-2021 / 24-Apr-2024 / 09-June-2022
    Created by:       Willem-Jan Vroom
    Organization:     
    Functionname:     Display-MessageBox
    =============================================================================================================================================
    .SYNOPSIS

    This function displays a message box.

    The return value is:

    None (no result) 	0
    OK 	                1
    Cancel 	            2
    Yes 	            6
    No. 	            7

    #>

    param
     (
      [Parameter(Mandatory=$True)] [ValidateNotNullOrEmpty()][String] $Text,
      [Parameter(Mandatory=$False)][Switch] $GUI,
      [Parameter(Mandatory=$False)][Switch] $Error,
      [Parameter(Mandatory=$False)][ValidateSet('Ok','AbortRetryIgnore','OkCancel','YesNoCancel','YesNo')][String] $Button = "Ok"
     )
         
    If ($GUI)
     {
      If ($Error)
       {
        $Return = [System.Windows.Forms.MessageBox]::Show($this,$Text.Trim(),$($CCMCacheActions.$Language.Error),$Button,"Error")
       }
        else
       {
        $Return = [System.Windows.Forms.MessageBox]::Show($this,$Text.Trim(),$($CCMCacheActions.$Language.Information),$Button,"Asterisk")
       }
     }
      else
     {
      Write-Host "$Text`n"
      Return 0
     }

     Return $($Return.value__)
   }

  Function Add-EntryToLogFile
   {

    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Created on:       17-May-2020 / Modified 09-May-2022: Includes the function name
    Created by:       Willem-Jan Vroom
    Organization:     
    Functionname:     Add-EntryToLogFile
    =============================================================================================================================================
    .SYNOPSIS

    This function adds a line to a log file

    #>

    Param
     (
      [Parameter(Mandatory=$True)]  [string] $Entry,
      [Parameter(Mandatory=$False)] [String] $FunctionName

     )
      
     Write-Verbose "[Function: $FunctionName] - $Entry"
     If ($Global:gblDetailedLogging -and $Global:gblLogFile)
      {
       $Timestamp = (Get-Date -UFormat "%a %e %b %Y %X").ToString()
       Add-Content $Global:gblLogFile -Value $($Timestamp + "[Function: $FunctionName] - $Entry") -Force -ErrorAction SilentlyContinue
      }
   }

  Function Add-EntryToResultsFile
   {

    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Created on:       03-August-2018
    Created by:       Willem-Jan Vroom
    Organization:     
    Functionname:     Add-EntryToResultsFile
    =============================================================================================================================================
    .SYNOPSIS

    This function adds the success or failure information to the array that contains the log
    information.

    #>

    Param
     (
      [String]   $Name                  = "",
      [String]   $DeploymentType        = "",
      [String]   $Program               = "",
      [String]   $Evaluation            = "",
      [String]   $UpdateClassification  = "",
      [String]   $ContentID             = "",
      [String]   $ContentLocation       = "",
      [String]   $ContentSize           = "",
      [String]   $LastReferenceTime     = ""
     )

    $ThisFunctionName = $MyInvocation.MyCommand.Name

    $Record  = [ordered] @{"Name"                        = "";
                           "Deployment Type"             = "";
                           "Program"                     = "";
                           "Evaluation"                  = "";
                           "Update Classification"       = "";
                           "Content ID"                  = "";
                           "Content Location"            = "";
                           "Content Size"                = "";
                           "Content Last Reference Time" = ""}

    $Record."Name"                        = $Name
    $Record."Deployment Type"             = $DeploymentType
    $Record."Program"                     = $Program
    $Record."Evaluation"                  = $Evaluation
    $Record."Update Classification"       = $UpdateClassification
    $Record."Content ID"                  = $ContentID
    $Record."Content Location"            = $ContentLocation
    $Record."Content Size"                = $ContentSize
    $Record."Content Last Reference Time" = $LastReferenceTime

    $Global:arrTable                     += New-Object PSObject -Property $Record
    
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Discovered application, program or update:"
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "  * Name:                        $Name"
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "  * Deployment Type:             $DeploymentType"
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "  * Program:                     $Program"
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "  * Evaluation:                  $Evaluation"
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "  * Update Classification:       $UpdateClassification"
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "  * Content ID:                  $ContentID"
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "  * Content Location:            $ContentLocation"
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "  * Content Size:                $ContentSize"
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "  * Content Last Reference Time: $LastReferenceTime"
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "#######################################################################"
   }


  Function Create-Folder
   {

    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Created on:       03-August-2018
    Created by:       Willem-Jan Vroom
    Organization:     
    Functionname:     Create-Folder
    =============================================================================================================================================
    .SYNOPSIS

    This function creates the given folder.
    The function returns two results:
     1. A message.
     2. True or false (success or failure)

    #>
    
    param
     (
      [Parameter(Mandatory=$True)] [ValidateNotNullOrEmpty()][String] $FolderName
     )

   $bolResult     = $True
   $ResultMessage = ""

   If (-not(Test-Path $('FileSystem::' + $FolderName)))
    {
     New-Item -Path $FolderName -ItemType Directory | Out-Null
     Sleep 1
     If (test-path $('FileSystem::' + $FolderName))
      {
       $ResultMessage = $($CCMCacheActions.$Language.FolderCreated).Replace("<FolderName>", $FolderName)
      }
       else
      {
       $ResultMessage = $($CCMCacheActions.$Language.ErrorCreatingFolder).Replace("<FolderName>", $FolderName)
       $bolResult     = $False
      }
    }
     else
    {
     $ResultMessage = $($CCMCacheActions.$Language.FolderAlreadyExists).Replace("<FolderName>", $FolderName)
    }

    Return $ResultMessage,$bolResult 

   }

  Function Check-HasElevatedRights
   {

    <#
    .NOTES
    ========================================================================================================================
    Created with:     Windows PowerShell ISE
    Created on:       11-January-2019 / Modified 22-Apr-2022 / Modified 05-May-2022 / Modified on 28-Nov-2022.
    Created by:       Willem-Jan Vroom
    Organization:     
    Functionname:     Check-HasElevatedRights
    ========================================================================================================================
    .SYNOPSIS

    This function checks if an user has admin rights. The function returns $True or $False

    Initially, the function was ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]'544')
    But that command throws up an error due to the PowerShell ConstrainedMode.
    "S-1-5-32-544" -in (whoami /groups /FO csv | convertfrom-csv).sid does not work either as it will always return 'true', even if not elevated but
    the logged on user is an admin
    So the only good test: check if you can create a file in c:\windows\fonts. If yes, then admin / elevated rights otherwise not. 

    #>

    $RandomFile = ""
    For($a=1; $a -le 8; $a++)
     {
      $RandomFile += [char](Random(97..122))
     }

    $RandomFile = "$($env:windir)\Fonts\$RandomFile.txt"
    
    Try
     {
      New-Item $RandomFile -Force -ErrorAction SilentlyContinue | Out-Null
      If (test-path($RandomFile))
       {
        Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "The random file $RandomFile has been created, so there are elevated rights."
        Remove-Item $RandomFile -Force
        Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "The random file $RandomFile has been removed."
        Return $True
       }
        else
       {
        Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "The random file $RandomFile has not been created, so there are normal user rights."
        Return $False
       }
     }
      Catch
     {  
      Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "The random file $RandomFile has not been created, so there are normal user rights."
      Return $False
     } 
   }

  Function Test-RegistryKeyValue
   {
    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Created on:       30-Dec-20
    Created by:       (C) Aaron Jensen 
                      https://stackoverflow.com/questions/5648931/test-if-registry-value-exists
    Organization:     Carbon Module
    Functionname:     Test-RegistryKeyValue
    =============================================================================================================================================
    .SYNOPSIS

    #>

    [CmdletBinding()]
    param 
     (
      [Parameter(Mandatory = $True)][string]  $Path,
      [Parameter(Mandatory = $True)][string]  $Name
     )

    if ( -not(Test-Path -Path $Path -PathType Container))
     {
      return $False
     }

    $properties = Get-ItemProperty -Path $Path
    if (-not $properties)
     {
      return $False
     } 

    $member = Get-Member -InputObject $properties -Name $Name
    if ($member)
     {
      return $True
     }
      else
     {
      return $False
     }
   }

  Function UserDetails
   {
    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Created on:       03-Jan-21 / Modified on 14-Jan-21 / Modified on 22-Apr-22 / Modified on 01-May-2022 / Modified on 17-May-2022 /  
	                  Modified 21-Nov-2022
    Created by:       Willem-Jan Vroom
    Functionname:     UserDetails
    =============================================================================================================================================
    .SYNOPSIS

    This function returns 4 details of the Current Logged In Usser
    1. The username of the current logged in user
    2. User\Domain of the current logged in user
    3. User SID fo the User\Domain
    4. Account name that is using the script 

    Initially, the scriptAccount was found with the command 
    [System.Security.Principal.WindowsIdentity]::GetCurrent().Name

    But that command throws up an error due to the PowerShell ConstrainedMode.

    Also, the output of whoami /user depends on the language. In the German language the output is different.
    The header information is in the German language, so the header should be read from the CSV file.
    That can be done with PSObject.Properties.Name

    C:\Users\Test>whoami /user

    BENUTZERINFORMATIONEN
     ---------------------

    Benutzername         SID
    ==================== ==============================================
    windows10wkg_02\test S-1-5-21-1753037473-1212035439-1379364385-1001

    On 21-Nov-2022 the function has been changed.

    There are 6 situations that should be handled:
    1. Find the logged on user sid when there is one user logged on on a 'normal' Windows computer
    2. Find the logged on user sid when there are multiple users logged on in a RDS / Citrix environment where the users are no local admin.
    3. Find the logged on user sid when there are multiple users logged on in a RDS / Citrix environment where the users are local admin.
    4. Find the logged on user sid when there is a SYSTEM account and there is a user logged on on a 'normal' Windows computer. This is the
       case during a Software Center installation performed under the SYSTEM account.
    5. Find the logged on user sid when there is a SYSTEM account and there is a user logged on on a RDS / Citrix environment.
    6. Take care of the PowerShell Language Mode.

    While testing I found that the MainWindowHandle is not 0 for the user that is running the explorer process. That is true for all the
    above mentioned situations.

    You can check while running this command with elevated rights:

    get-process -IncludeUserName | Select-Object -Property Username, Id, Name,MainWindowHandle | Where {($_.Name -eq "explorer" -or $_.Name -eq "pfwsmgr")} | Format-Table

    The output will be something like this:

    UserName             Id Name     MainWindowHandle
    --------             -- ----     ----------------
    DEMO\user1         1380 explorer                0
    DEMO\adminuser2    6556 explorer           131134

    So, you have to continue with PID ID 6556.

    So, I used the method of finding the PID, and use that PID as a condition for the Get-WMIObject to search for the explorer or pfwsmgr
    process with that PID.

    #>

  # =============================================================================================================================================
  # Find the current logged on user by checking the rights on the explore.exe (of pfwsmgr.exe with Ivanti Workspace Control) process.
  # If the language mode is FullLanguage then use MainWindowHandle. That is needed when this script is run from SYSTEM context. I assume that
  # the SYSTEM account is not effected by the PowerShell Language Mode.
  # If the language mode is not FullLanguage then use a check on username and the owner of the explorer or pfwsmgr process. 
  # =============================================================================================================================================

    if($Global:gblFullLanguage)
     {
      $PIDID              = (get-process | Select-Object -Property Id, Name, MainWindowHandle | Where {(($_.Name -eq "explorer" -or $_.Name -eq "pfwsmgr") -and $($_.MainWindowHandle).ToInt32() -gt 0)}).Id
      Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "The process 'explorer' or 'pfwsmgr' has the PID $PIDID."
      $Explorer           = (Get-WMIObject -Query "Select * From Win32_Process Where (ProcessId=$PIDID)")
      Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "The process with PID $PIDID has the name $($Explorer.Name)."

      $UserName           = $Explorer.GetOwner()
      $SID                = ($Explorer.GetOwnerSID()).SID
     }
      else
     {
      $Explorer      = (Get-WMIObject -Query "Select * From Win32_Process Where (Name='explorer.exe' or Name='pfwsmgr.exe')")
      $EnvUSERNAME   = $Env:USERNAME

      Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "The process 'explorer.exe' or 'pfwsmgr.exe' is running $($Explorer.Count) times."

      If ($Explorer.Count -gt 0)
       {
        For ($a=0; $a -le ($Explorer.Count); $a++)
         {
          $UserName      = ($Explorer[$a]).GetOwner()
          If ($($UserName.User) -eq $EnvUSERNAME)
           {
            $SID           = (($Explorer[$a]).GetOwnerSID()).SID
            Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "A valid SID '$SID' has been found for user '$EnvUSERNAME'." 
            Break
           }
         }
       }
        else
       {
        $UserName      = $Explorer.GetOwner()
        $SID           = ($Explorer.GetOwnerSID()).SID
        Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "A valid SID '$SID' has been found for user '$EnvUSERNAME'."
       }
     }

    Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "Found domain:        $($UserName.Domain)."
    Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "Found username:      $($UserName.User)."
    Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "Found SID:           $SID."
        
    $UserAndDomain      = "$($Username.Domain )\$($Username.User)".ToUpper()
    $tmpScriptAccount   = (whoami /user /FO csv | convertfrom-csv)
    $TranslatedUserName = $tmpScriptAccount.PSObject.Properties.Name[0]
    $ScriptAccount      = $($tmpScriptAccount.$TranslatedUserName).ToUpper()

    Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "Found scriptaccount: $ScriptAccount"
    
    Return $($Username.User),$UserAndDomain,$SID,$ScriptAccount
   }

  Function Find-Language
   {
    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Created on:       30-Dec-20
    Created by:       Willem-Jan Vroom 
    Organisation:                      
    Functionname:     Find-Language
    =============================================================================================================================================
    .SYNOPSIS

    This function works the best, even running under system context.
    Get-UiCulture returns the UICulture details from the account that is used. So when the system account is used, incorrect
    details might be shown.

    #>

    [CmdletBinding()]
    Param
     (
      [Parameter(Mandatory = $True)][ValidateNotNullOrEmpty()][String] $CurrentUserSID
     )

    $Result           = "en-US"
    $ThisFunctionName = $MyInvocation.MyCommand.Name

    $RegKey = "REGISTRY::HKEY_USERS\$CurrentUserSID\Control Panel\Desktop"
    $Value  = "PreferredUILanguages"
    if (Test-RegistryKeyValue -Path $RegKey -Name $Value)
     {
      $Result = (get-itemproperty $RegKey | Select -ExpandProperty $Value).Split()[0]
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Regkey '$RegKey' value '$Value' exists. The data is '$Result'."
      Return $Result
     }
    else
     {
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Regkey '$RegKey' value '$Value' does not exists. Skipping to machine settings."
     }

    $RegKey = "REGISTRY::HKEY_USERS\.DEFAULT\Control Panel\Desktop\MuiCached"
    $Value  = "MachinePreferredUILanguages"
    if (Test-RegistryKeyValue -Path $RegKey -Name $Value)
     {
      $Result = (get-itemproperty $RegKey | Select -ExpandProperty $Value).Split()[0]
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Regkey '$RegKey' value '$Value' exists. The data is '$Result'."
      Return $Result
     }

    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "There was a problem reading the registry..."
    Return $Result
   }

  Function Check-SCCMClient
   {
    
    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Created on:       14-Jan-21
    Created by:       Willem-Jan Vroom 
    Organisation:                      
    Functionname:     Check-SCCMClient
    =============================================================================================================================================
    .SYNOPSIS

    This function checks if the SCCM client on the machine is operational. It returns either nothing or an error message.

    #>

    $ErrorMessage             = ""
    $ThisFunctionName         = $MyInvocation.MyCommand.Name

    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $CCMCacheActions.$Language.StartCheckSCCMClient

    Try
     {
      $class = Get-WmiObject -Class 'SMS_CLIENT' -List -Namespace 'root\ccm' -ErrorAction Stop
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $CCMCacheActions.$Language.SCCMClientOk
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $CCMCacheActions.$Language.EndCheckSCCMClient
      Return $True,$ErrorMessage
     }
      Catch
     {
      $ErrorMessage = $($_.Exception.Message).Trim()
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $($CCMCacheActions.$Language.SCCMClientError1)
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $($CCMCacheActions.$Language.SCCMClientError2).Replace("<ErrorMessage>",$ErrorMessage)
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $CCMCacheActions.$Language.EndCheckSCCMClient
      Return $False,$ErrorMessage
     }
   }

  Function CCM-FindApplications
   {

    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Created on:       06-June-2022
    Created by:       Willem-Jan Vroom 
    Organisation:                      
    Functionname:     CCM-FindApplications
    =============================================================================================================================================
    .SYNOPSIS

    This function finds all the via SCCM (installed) applications.

    The EnforcePreference is explained here:
    https://docs.microsoft.com/en-us/mem/configmgr/develop/reference/core/clients/sdk/ccm_application-client-wmi-class

    The EvaluationState is explained here:
    https://docs.microsoft.com/en-us/mem/configmgr/develop/reference/core/clients/sdk/ccm_evaluationstate-client-wmi-class

    #>

    $Applications             = Get-CimInstance -Namespace "Root\ccm\ClientSDK" -ClassName 'CCM_Application' -Verbose:$False
    $CCMComObject             = New-Object -ComObject 'UIResource.UIResourceMgr'
    $CacheInfo                = $($CCMComObject.GetCacheInfo().GetCacheElements())
    $ThisFunctionName         = $MyInvocation.MyCommand.Name

    $EnforcePreference = @{
    0 = "Immediate"
    1 = "NonBusinessHours"
    2 = "AdminSchedule"}

    $EvaluationSate = @{
    0 = "No state information is available."
    1 = "Application is enforced to desired/resolved state."
    2 = "Application is not required on the client."
    3 = "Application is available for enforcement (install or uninstall based on resolved state). Content may/may not have been downloaded."
    4 = "Application last failed to enforce (install/uninstall)."
    5 = "Application is currently waiting for content download to complete."
    6 = "Application is currently waiting for content download to complete."
    7 = "Application is currently waiting for its dependencies to download."
    8 = "Application is currently waiting for a service (maintenance) window."
    9 = "Application is currently waiting for a previously pending reboot."
    10 = "Application is currently waiting for serialized enforcement."
    11 = "Application is currently enforcing dependencies."
    12 = "Application is currently enforcing."
    13 = "Application install/uninstall enforced and soft reboot is pending."
    14 = "Application installed/uninstalled and hard reboot is pending."
    15 = "Update is available but pending installation."
    16 = "Application failed to evaluate."
    17 = "Application is currently waiting for an active user session to enforce."
    18 = "Application is currently waiting for all users to logoff."
    19 = "Application is currently waiting for a user logon."
    20 = "Application in progress, waiting for retry."
    21 = "Application is waiting for presentation mode to be switched off."
    22 = "Application is pre-downloading content (downloading outside of install job)."
    23 = "Application is pre-downloading dependent content (downloading outside of install job)."
    24 = "Application download failed (downloading during install job)."
    25 = "Application pre-downloading failed (downloading outside of install job)."
    26 = "Download success (downloading during install job)."
    27 = "Post-enforce evaluation."
    28 = "Waiting for network connectivity."}

    ForEach ($Application in $Applications)
     {
      $tmpName                 = ""
      $tmpDeploymentType       = ""
      $tmpEvaluation           = ""
      $tmpContentID            = ""
      $tmpContentLocation      = ""
      $tmpContentSize          = ""
      $tmpLastReferenceTime    = ""

      $AppDTS = ($Application | Get-CimInstance).AppDTs
      ForEach ($AppDT in $AppDTS)
       {
        ForEach ($ActionType in $AppDT.AllowedActions)
         {
          $Arguments = [hashtable]@{
          'AppDeliveryTypeID' = [string]$($AppDT.ID)
          'Revision'          = [uint32]$($AppDT.Revision)
          'ActionType'        = [string]$($ActionType)}
          $AppContentID = (Invoke-CimMethod -Namespace 'Root\ccm\cimodels' -ClassName 'CCM_AppDeliveryType' -MethodName 'GetContentInfo' -Arguments $Arguments -Verbose:$False).ContentID

          $tmpName            = $Application.Name
          $tmpDeploymentType  = "$($AppDT.Name) -> Action: $ActionType"
          $tmpEvaluation      = $($EvaluationSate.Item([int]$($Application.EvaluationState)))
           

          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Application: Name:                 $tmpName"
          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Application: Enforce preference:   $($EnforcePreference.Item([int]$($Application.EnforcePreference)))"
          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Application: Evaluation:           $tmpEvaluation"
          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Application: Deployment Type name: $($AppDT.Name)"
          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Application: Action:               $($ActionType)"
 
          $AppCacheInfo = $CacheInfo | Where-Object { $($_.ContentID) -eq $AppContentID }
          If ($AppCacheInfo)
           {
            $tmpContentID         = $AppCacheInfo.ContentID
            $tmpContentLocation   = $AppCacheInfo.Location
            $tmpContentSize       = $AppCacheInfo.ContentSize
            $tmpLastReferenceTime = $AppCacheInfo.LastReferenceTime
         
            Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Application: Content ID:           $tmpContentID"
            Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Application: Location:             $tmpContentLocation"
            Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Application: Content size:         $('{0:N2}' -f $($tmpContentSize / 1KB))Mb"
            Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Application: Last Reference time:  $tmpLastReferenceTime"
           }
          Add-EntryToResultsFile -Name $tmpName -DeploymentType $tmpDeploymentType -Evaluation $tmpEvaluation -ContentID $tmpContentID -ContentLocation $tmpContentLocation -LastReferenceTime $tmpLastReferenceTime -ContentSize $tmpContentSize
         }
       }
     }
   }

  Function CCM-FindPrograms
   {

    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Created on:       06-June-2022
    Created by:       Willem-Jan Vroom 
    Organisation:                      
    Functionname:     CCM-FindPrograms
    =============================================================================================================================================
    .SYNOPSIS

    This function finds all the via SCCM (installed) programs.

    #>

    $Packages                 = Get-CimInstance -Namespace 'ROOT\ccm\Policy\Machine\RequestedConfig' -ClassName 'CCM_SoftwareDistribution' -Verbose:$False
    $CCMComObject             = New-Object -ComObject 'UIResource.UIResourceMgr'
    $CacheInfo                = $($CCMComObject.GetCacheInfo().GetCacheElements())
    $ThisFunctionName         = $MyInvocation.MyCommand.Name

    ForEach ($Package in $Packages)
     {
      $tmpName                 = $Package.PKG_MIFName
      $tmpProgram              = $Package.PRG_ProgramID
      $tmpContentID            = ""
      $tmpContentLocation      = ""
      $tmpContentSize          = ""
      $tmpLastReferenceTime    = ""

      $PkgCacheInfo = $CacheInfo | Where-Object { $_.ContentID -eq $Package.PKG_PackageID }

      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Program: Name:                 $tmpName"
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Program: Program name:         $tmpProgram"

      # Only take the last items from the cache information. 
      If (($($PkgCacheInfo | Measure-Object).Count) -gt 1){$PkgCacheInfo1 = $PkgCacheInfo[-1]} else {$PkgCacheInfo1 = $PkgCacheInfo}

      ForEach ($CacheItem in $PkgCacheInfo1)
       {
        If ($CacheItem) 
         {
          #  Set content size to 0 if null to avoid division by 0
          If ($CacheItem.ContentSize -eq 0) { [int]$PkgContentSize = 0 } Else { [int]$PkgContentSize = $($CacheItem.ContentSize) }
          $tmpContentID         = $CacheItem.ContentID
          $tmpContentLocation   = $CacheItem.Location
          $tmpContentSize       = $PkgContentSize
          $tmpLastReferenceTime = $CacheItem.LastReferenceTime

          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Package: Content ID:           $tmpContentID"
          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Package: Location:             $tmpContentLocation"
          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Package: Content size:         $('{0:N2}' -f $($PkgContentSize / 1KB))Mb"
          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Package: Last reference time:  $tmpLastReferenceTime"   
         }
       }
      Add-EntryToResultsFile -Name $tmpName -Program $tmpProgram -ContentID $tmpContentID -ContentLocation $tmpContentLocation -LastReferenceTime $tmpLastReferenceTime -ContentSize $tmpContentSize
     }
   }

  Function CCM-FindUpdates
   {

    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Created on:       06-June-2022
    Created by:       Willem-Jan Vroom 
    Organisation:                      
    Functionname:     CCM-FindUpdates
    =============================================================================================================================================
    .SYNOPSIS

    This function finds all the via SCCM installed updates.
    The Classification Type is explained here:
    https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ff357803(v=vs.85)

    #>

    $CCMComObject             = New-Object -ComObject 'UIResource.UIResourceMgr'
    $CacheInfo                = $($CCMComObject.GetCacheInfo().GetCacheElements())
    $Updates                  = Get-CimInstance -Namespace 'Root\ccm\SoftwareUpdates\UpdatesStore' -ClassName 'CCM_UpdateStatus' -Verbose:$False
    $ThisFunctionName         = $MyInvocation.MyCommand.Name

    $ClassificationType = @{
    "5C9376AB-8CE6-464A-B136-22113DD69801" = "Application"
    "434DE588-ED14-48F5-8EED-A15E09A991F6" = "Connectors"
    "E6CF1350-C01B-414D-A61F-263D14D133B4" = "CriticalUpdates"
    "E0789628-CE08-4437-BE74-2495B842F43B" = "DefinitionUpdates"
    "E140075D-8433-45C3-AD87-E72345B36078" = "DeveloperKits"
    "B54E7D24-7ADD-428F-8B75-90A396FA584F" = "FeaturePacks"
    "9511D615-35B2-47BB-927F-F73D8E9260BB" = "Guidance"
    "0FA1201D-4330-4FA8-8AE9-B877473B6441" = "SecurityUpdates"
    "68C5B0A3-D1A6-4553-AE49-01D3A7827828" = "ServicePacks"
    "B4832BD8-E735-4761-8DAF-37F882276DAB" = "Tools"
    "28BC880E-0592-4CBF-8F95-C79B17911D5F" = "UpdateRollups"
    "CD5FFD1E-E932-4E3A-BF74-18BF0B1BBD83" = "Updates"} 

    ForEach ($Update in $Updates)
     {
      $tmpName                 = "$($Update.Title) Bulletin ID: $($Update.Article)"
      $tmpEvaluation           = $Update.Status
      $tmpUpdateClassification = $($ClassificationType.Item($($Update.UpdateClassification)))
      $tmpContentID            = ""
      $tmpContentLocation      = ""
      $tmpContentSize          = ""
      $tmpLastReferenceTime    = ""

      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Update: Title and  Bulletin ID:   $tmpName"
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Update: Evaluation:               $tmpEvaluation"

      $UpdateCacheInfo = $CacheInfo | Where-Object { $_.ContentID -eq $Update.UniqueID}
      If ($UpdateCacheInfo)
       {
        ForEach ($Update in $UpdateCacheInfo)
         {
          $tmpContentID            = $Update.ContentID
          $tmpContentLocation      = $Update.Location
          $tmpContentSize          = $Update.ContentSize
          $tmpLastReferenceTime    = $Update.LastReferenceTime

          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Update: Content ID:               $tmpContentID"
          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Update: Location:                 $tmpContentLocation"
          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Update: Content size:             $('{0:N2}' -f $($tmpContentSize / 1KB))Mb"
          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Update: Last reference time:      $tmpLastReferenceTime"
         }   

       }
      Add-EntryToResultsFile -Name $tmpName -Evaluation $tmpEvaluation -UpdateClassification $tmpUpdateClassification -ContentID $tmpContentID -ContentLocation $tmpContentLocation -LastReferenceTime $tmpLastReferenceTime  -ContentSize $tmpContentSize  
     }
   }

  Function CCM-FindOrphanedCCMFolders
   {

    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Created on:       06-June-2022
    Created by:       Willem-Jan Vroom 
    Organisation:                      
    Functionname:     CCM-FindOrphanedCCMFolders
    =============================================================================================================================================
    .SYNOPSIS

    This function finds all the folders in the CCMCache that are no longer needed or used. Also, there is no write action in that folder for
    at least one hour.

    Also, there might be folders that are still in the CCMCache, have details like ContentID but that content is no longer used by any application,
    program or update.

    Skip Content Locations that contains a dot. 
        
    #>

    $CCMComObject                    = New-Object -ComObject 'UIResource.UIResourceMgr'
    $CCMCacheFolderUsedByDeployments = $CCMComObject.GetCacheInfo().GetCacheElements()
    $CacheLocation                   = $CCMComObject.GetCacheInfo().Location
    $AllFoldersInCache               = (Get-ChildItem $CacheLocation -Directory).FullName
    $ThisFunctionName                = $MyInvocation.MyCommand.Name
    
    $FoundOrhanedFolders             = @()

    $Record  = [ordered] @{"Orphaned Folder Name"  = "";
                           "ContentId"             = ""}

  # =============================================================================================================================================
  # Step 1: Check for folders in the CacheLocation (for example C:\windows\ccmcache) that are not used by any application, program or update.
  #         Add these folders to the array FoundOrhanedFolders
  # =============================================================================================================================================
  
    ForEach ($FolderInCache in $AllFoldersInCache)
     {
      If (($CCMCacheFolderUsedByDeployments | ForEach-Object {$_.Location}) -notcontains $FolderInCache)
       {
        if ((Get-ItemProperty $FolderInCache).LastWriteTime -le (get-date).AddHours(-1) -and -not $FolderInCache.Contains("."))
         {
          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $($CCMCacheActions.$Language.OrphanedFolder).Replace("<FolderInCache>",$FolderInCache)
          $Record."Orphaned Folder Name" = $FolderInCache
          $objRecord                      = New-Object PSObject -Property $Record
          $FoundOrhanedFolders           += $objRecord
         }
       }
     }

  # =============================================================================================================================================
  # Step 2: The array Global:arrTable contains all the found applications, programs and updates, including the content location.
  #         Now, we check if there are folders in the CacheLocation (for example C:\windows\ccmcache) that are not in the array Global:arrTable. 
  #         Add these folders to the array FoundOrhanedFolders.
  # =============================================================================================================================================
  
    ForEach ($FolderInCache in ($CCMCacheFolderUsedByDeployments | ForEach-Object {$_.Location} | Where-Object {$_ -notin $Global:arrTable."Content Location"}))
     {
      $ContentID = $CCMCacheFolderUsedByDeployments | Where-Object {$_.Location -eq $FolderInCache} | ForEach-Object {$_.ContentId}
      $Record."Orphaned Folder Name" = $FolderInCache
      $Record."ContentId"            = $ContentID
      $objRecord                      = New-Object PSObject -Property $Record
      $FoundOrhanedFolders           += $objRecord
     }

    $FoundOrhanedFolders = $FoundOrhanedFolders | Sort-Object "Orphaned Folder Name"

    Return $FoundOrhanedFolders
   }

  Function CCM-DeleteItemFromCache
   {

    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Created on:       09-June-2022 / Modified on 29_nov-2022: check on an empty CacheElementID and an empty $CacheInfoArray.
    Created by:       Willem-Jan Vroom 
    Organisation:                      
    Functionname:     CCM-DeleteItemFromCache
    =============================================================================================================================================
    .SYNOPSIS

    This function removes the specified item from the cache.
        
    #>

    [CmdletBinding()]

    param
    (
     [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [String]  $ContentID,
     [Parameter(Mandatory = $False)]                           [Boolean] $RemoveIfPersistentInCache = $False,
     [Parameter(Mandatory = $False)][ValidateNotNullorEmpty()] [int16]   $ReferencedThreshold = 2,
     [Parameter(Mandatory = $True)]                            [String]  $ContentLocation
    )
  
    $CCMComObject                    = New-Object -ComObject 'UIResource.UIResourceMgr'
    $ThisFunctionName                = $MyInvocation.MyCommand.Name
    $CacheInfoArray                  = @($CCMComObject.GetCacheInfo().GetCacheElements() | Where-Object { ($_.ContentID -eq $ContentID) -and ($_.Location -eq $ContentLocation) })
    $ReturnValue                     = $True
    $ReturnReason                    = ""
    [datetime]$OlderThan             = (Get-Date).ToUniversalTime().AddDays(-$ReferencedThreshold)
    $ReturnText                      = @{
    1 = $($CCMCacheActions.$Language.CCMDeleted_1)                                                           # The item has been removed successfully.
    2 = $($CCMCacheActions.$Language.CCMDeleted_2)                                                           # This item is used by other items, therefore not removed.
    3 = $($CCMCacheActions.$Language.CCMDeleted_3).Replace("<ReferencedThreshold>",$ReferencedThreshold)     # The content has been used in the last <ReferencedThreshold> days.
    4 = $($CCMCacheActions.$Language.CCMDeleted_4)                                                           # The item has not been removed successfully.
    5 = $($CCMCacheActions.$Language.CCMDeleted_5)                                                           # The item has no valid Last Reference Time, therefore skipped.
    6 = $($CCMCacheActions.$Language.CCMDeleted_6)                                                           # The item must remain in cache, therefore not removed.
	7 = $($CCMCacheActions.$Language.CCMDeleted_7)                                                           # The CacheElementID is empty, nothing is done.
    8 = $($CCMCacheActions.$Language.CCMDeleted_8)                                                           # Skipped as this item has already been removed.
    }
   
    If ($($CacheInfoArray.Count) -eq 0)
     {
      $ReturnNumber = 8
      $ReturnValue  = $True
     }
      else
     {
      ForEach ($CacheInfo in $CacheInfoArray)
       {

      # =============================================================================================================================================
      # Only delete this entry if not referenced by other items and not used in the last number of <ReferencedThreshold> days.
      # =============================================================================================================================================

        If ($([string]$($CacheInfo.CacheElementID)))
         {
          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "----> Processing $([string]$($CacheInfo.CacheElementID))"
          If ($CacheInfo.LastReferenceTime)
           {
            Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "----> The last reference time is $($CacheInfo.LastReferenceTime)."
            If ( (($CacheInfo.ReferenceCount) -lt 1) -and ([datetime]($CacheInfo.LastReferenceTime) -le $OlderThan) )
             {
              $Result    = $CCMComObject.GetCacheInfo().DeleteCacheElementEx([string]$($CacheInfo.CacheElementID), [bool]$RemoveIfPersistentInCache)

            # =============================================================================================================================================
            # Check after the deletion of the item.
            # =============================================================================================================================================

              $CacheInfo = $CCMComObject.GetCacheInfo().GetCacheElements() | Where-Object { ($_.ContentID -eq $ContentID) -and ($_.Location -eq $ContentLocation) }

            # =============================================================================================================================================
            # Remove the object and check if the item has been deleted successfully.
            # The variable $Result is always empty. So you have the check by quering the CCM Object.
            # =============================================================================================================================================
      
              If ( -not($CacheInfo))    
                {
                 $ReturnNumber = 1
                 $ReturnValue  = $True
                } 
                 else 
                {

                 If ($RemoveIfPersistentInCache)
                  {
                   $ReturnNumber = 6
                   $ReturnValue  = $False
                  }
                   else
                  {
                   $ReturnNumber = 4
                   $ReturnValue  = $False
                  }
                }
             }
              else
             {
        
            # =============================================================================================================================================
            # The content is used by other items or the content is used within the number of <ReferencedThreshold> days. 
            # So, checking. 
            # =============================================================================================================================================
        
              If (-not($CacheInfo.ReferenceCount) -lt 1)  
               {
                $ReturnNumber = 2
                $ReturnValue  = $False
               }
                else
               {
                $ReturnNumber = 3
                $ReturnValue  = $False
               } 
             }
           }
            else
           {
      
          # =============================================================================================================================================
          # No valid reference time.
          # =============================================================================================================================================
      
            $ReturnNumber = 5
            $ReturnValue  = $False 
           }
         }
          else
         {
          Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "----> The CacheElementID appears to be empty..."
		  $ReturnNumber = 7
         }
       }

     }
    
    Return $ReturnValue, $($ReturnText.Item($ReturnNumber))
   }

# =============================================================================================================================================
# End function block
# =============================================================================================================================================

# =============================================================================================================================================
# Functions, used in the forms blocks
# =============================================================================================================================================

  Function SelectOrUnselectAllItemsInDGV
   {

    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Functionname:     SelectOrUnselectAllItemsInDGV
    =============================================================================================================================================
    .SYNOPSIS

    Selects only the items that are in the local CCMCache. So these items can be cleared from the CCMCache.
        
    #>

    $ThisFunctionName                = $MyInvocation.MyCommand.Name
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $($CCMCacheActions.$Language.PressedButtonName).Replace("<ButtonName>",$CCMCacheActions.$Language.chkAllItems)
    If ($chkAllItems.Checked)
     {
      $dgvCacheItems.SelectAll()
      ForEach ($Row in $dgvCacheItems.SelectedRows)
       {
        If ( -not($dgvCacheItems.Rows[$Row.Index].Cells['Content ID'].Value))
         {
          $dgvCacheItems.Rows[$Row.Index].Selected = $False
         }
       }
     }
      else
     {
      $dgvCacheItems.ClearSelection()
     }
   }

  Function FindAppsProgramsAndUpdates
   {
    
    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Functionname:     FindAppsProgramsAndUpdates
    =============================================================================================================================================
    .SYNOPSIS

    Calls the functions to find all the installed applications, programs and updates and fills the datagrid dgvCacheItems. 
        
    #>
    
    $ThisFunctionName                      = $MyInvocation.MyCommand.Name
    $chkOnlyCachedItems.Enabled            = $False
    $chkDeleteImmediately.Enabled          = $False
    $btnClearCache.Enabled                 = $False
    $chkAllItems.Enabled                   = $False 
    
    If ($OnlyItemsInCCMCache) {$chkOnlyCachedItems.Checked = $True}   
        
    $frmProcessing                         = New-Object 'System.Windows.Forms.Form'
    $txtMessage                            = New-Object 'System.Windows.Forms.TextBox'
        
    $frmProcessing.Controls.Add($txtMessage)
    $frmProcessing.AutoScaleDimensions     = New-Object System.Drawing.SizeF(6, 13)
    $frmProcessing.AutoScaleMode           = 'Font'
    $frmProcessing.BackColor               = [System.Drawing.SystemColors]::ControlDark 
    $frmProcessing.ClientSize              = New-Object System.Drawing.Size(430, 44)
    $frmProcessing.FormBorderStyle         = 'None'
    $frmProcessing.Name                    = 'frmProcessing'
    $frmProcessing.ShowIcon                = $False
    $frmProcessing.StartPosition           = 'CenterParent'
    $frmProcessing.Text = "Form"
    $frmProcessing.add_Shown({$Global:arrTable = @()
                              $tmpTable        = @()
                              CCM-FindApplications
                              CCM-FindPrograms
                              CCM-FindUpdates            
                              [void]$frmProcessing.Close()
                              [void]$frmProcessing.Dispose()
                              })
    #
    # txtMessage
    #
    $txtMessage.ReadOnly                   = $True
    $txtMessage.Location                   = New-Object System.Drawing.Point(12, 12)
    $txtMessage.MaxLength                  = 50
    $txtMessage.Name                       = 'txtMessage'
    $txtMessage.Size                       = New-Object System.Drawing.Size(400, 20)
    $txtMessage.TabIndex                   = 0
    $txtMessage.Text                       = $CCMCacheActions.$Language.txtMessage
    $txtMessage.TextAlign                  = 'Center'
    $txtMessage.WordWrap                   = $False

    [void]$frmProcessing.ShowDialog()
    $dgvCacheItems.DataSource = $null
    $dgvCacheItems.Rows.Clear()

    if ($Global:arrTable.Count -gt 0)
     {
      $chkOnlyCachedItems.Enabled       = $True
      $btnClearCache.Enabled            = $True
      If (@($Global:arrTable | Where-Object {$_."Content ID"}).Count -gt 0)  {$chkDeleteImmediately.Enabled     = $True; $chkAllItems.Enabled = $true}
      $Global:arrTable = $Global:arrTable | Sort-Object -Property "Name","Deployment Type","Program","Evaluation","Update Classification","Content ID","Content Location","Content Size","Content Last Reference Time" -Unique
      If ($chkOnlyCachedItems.Checked)
       {
        $tmpTable = @($Global:arrTable | Where-Object {$_."Content ID"})
       }
        else
       {
        $tmpTable = @($Global:arrTable)
       }
     }
        
    if ($tmpTable.Count -gt 0)
     {
      $dgvCacheItems.DataSource = [System.Collections.ArrayList]$tmpTable
      $dgvCacheItems.Update()
      $dgvCacheItems.SelectAll()
      ForEach ($Row in $dgvCacheItems.SelectedRows)
       {
        If (($dgvCacheItems.Rows[$Row.Index].Cells['Content ID'].Value).Length -eq 0)
         {
          $dgvCacheItems.Rows[$Row.Index].DefaultCellStyle.BackColor = [System.Drawing.Color]::FromArgb(255, 249, 249, 249)
          $dgvCacheItems.Rows[$Row.Index].DefaultCellStyle.ForeColor = [System.Drawing.Color]::FromArgb(255, 160, 160, 160)
         }
       }
      $dgvCacheItems.ClearSelection()
     }
   }

   Function ShowOnlyCachedItems
    {

     <#
     .NOTES
     =============================================================================================================================================
     Created with:     Windows PowerShell ISE
     Functionname:     ShowOnlyCachedItems
     =============================================================================================================================================
     .SYNOPSIS

     Displays only the items that are in the local CCMCache. So these items can be cleared from the CCMCache.
        
     #>

     $ThisFunctionName                = $MyInvocation.MyCommand.Name
     Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $($CCMCacheActions.$Language.PressedButtonName).Replace("<ButtonName>",$CCMCacheActions.$Language.chkOnlyCachedItems).Replace("&","")

     $dgvCacheItems.DataSource = $null
     $dgvCacheItems.Rows.Clear()
     If ($chkOnlyCachedItems.Checked)
      {
       $tmpTable = @($Global:arrTable | Where-Object {$_."Content ID"})
       Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $($CCMCacheActions.$Language.NumberOfItemsInCache).Replace("<Number>",$tmpTable.Count)
       if ($tmpTable.Count -gt 0)
        {
         $dgvCacheItems.DataSource = $Null
         $dgvCacheItems.DataSource = [System.Collections.ArrayList]$tmpTable
         $dgvCacheItems.Update()
        }
      }
       else
      {
       if ($Global:arrTable.Count -gt 0)
        {
         $dgvCacheItems.DataSource = $Null
         $dgvCacheItems.DataSource = [System.Collections.ArrayList]$Global:arrTable
         $dgvCacheItems.Update()
         $dgvCacheItems.SelectAll()
         ForEach ($Row in $dgvCacheItems.SelectedRows)
          {
           If (($dgvCacheItems.Rows[$Row.Index].Cells['Content ID'].Value).Length -eq 0)
            {
             $dgvCacheItems.Rows[$Row.Index].DefaultCellStyle.BackColor = [System.Drawing.Color]::FromArgb(255, 249, 249, 249)
             $dgvCacheItems.Rows[$Row.Index].DefaultCellStyle.ForeColor = [System.Drawing.Color]::FromArgb(255, 160, 160, 160)
            }
          }
         $dgvCacheItems.ClearSelection()
        }
      }
    }
  
  Function DeleteOrphanedFolders
   {

    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Functionname:     DeleteOrphanedFolders
    =============================================================================================================================================
    .SYNOPSIS

    Deletes the non used folders from the CCMCache.
        
    #>

    $ThisFunctionName                = $MyInvocation.MyCommand.Name
    $ArrayOrphanedFolders            = @(CCM-FindOrphanedCCMFolders)
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $($CCMCacheActions.$Language.PressedButtonName).Replace("<ButtonName>",$CCMCacheActions.$Language.btnDeleteOrphanedFolders).Replace("&","")
    If ($ArrayOrphanedFolders.Count -gt 0)
     {
      $OrphanedFolders                 = $ArrayOrphanedFolders | ForEach-Object {$_."Orphaned Folder Name"}  | Out-String
      $Text                            = "$($CCMCacheActions.$Language.ConfirmRemovalFolders)`n`n$OrphanedFolders"
      $Result                          = Display-MessageBox -Text $Text -GUI -Button OkCancel
      If ($Result -eq 1)
       {
        ForEach ($OrphanedFolder in $ArrayOrphanedFolders)
         {
          if (-not $OrphanedFolder."ContentID")
           {
            Add-EntryToLogFile -Entry $($CCMCacheActions.$Language.DeletingFolder).Replace("<FolderName>",$($OrphanedFolder."Orphaned Folder Name")) -FunctionName $ThisFunctionName
            Remove-Item -Path $($OrphanedFolder."Orphaned Folder Name") -Recurse -Force
           }
            else
           {
            $Result,$ResultText = CCM-DeleteItemFromCache -ContentID $($OrphanedFolder."ContentID") -ContentLocation $($OrphanedFolder."Orphaned Folder Name") -RemoveIfPersistentInCache:$True -ReferencedThreshold 0
            Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "$($OrphanedFolder."Orphaned Folder Name") --> $ResultText"
           }
         }
        $btnDeleteOrphanedFolders.Enabled = $False
       }
     }
      else
     {
      $Text       = $CCMCacheActions.$Language.NoOrphanedFolders
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $Text 
      $Result     = Display-MessageBox -Text $Text -GUI
     }
   }

  Function RemoveItemsFromCache
   {

    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Functionname:     RemoveItemsFromCache
    =============================================================================================================================================
    .SYNOPSIS

    Removes an item from the CCMCache.
        
    #>

    $ThisFunctionName                = $MyInvocation.MyCommand.Name
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $($CCMCacheActions.$Language.PressedButtonName).Replace("<ButtonName>",$CCMCacheActions.$Language.btnClearCache).Replace("&","")
    $Text = "$($CCMCacheActions.$Language.RemovedFromCache)`n"
    $Rerun_FindAppsProgramsAndUpdates = $null
    If ($chkDeleteImmediately.Checked) {$ReferenceDays = 0} else {$ReferenceDays = 2}
    ForEach($Row in $dgvCacheItems.SelectedRows)
     {
      $SelectedContentID       = $dgvCacheItems.Rows[$Row.Index].Cells['Content ID'].Value
      $SelectedContentLocation = $dgvCacheItems.Rows[$Row.Index].Cells['Content Location'].Value
      $AppName                 = $dgvCacheItems.Rows[$Row.Index].Cells['Name'].Value
      $ProgName                = $dgvCacheItems.Rows[$Row.Index].Cells['Program'].Value
      $DTName                  = $dgvCacheItems.Rows[$Row.Index].Cells['Deployment Type'].Value
      If ($ProgName)           {$AppName += " (Program: $ProgName)"}
      If ($DTName)             {$AppName += " (Deployemnt type: $DTName)"}
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Removing from cache: $AppName"
      $Result,$ResultText = CCM-DeleteItemFromCache -ContentID $SelectedContentID -ContentLocation $SelectedContentLocation -ReferencedThreshold $ReferenceDays
      If ($Result)
       {
        $Line = $($CCMCacheActions.$Language.RemovedFromCacheSuccess).Replace("<Cell>",$AppName)
        Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $Line
        $Rerun_FindAppsProgramsAndUpdates = $True
       }
        else
       {
        $Line = $($CCMCacheActions.$Language.RemovedFromCacheFailure).Replace("<Cell>",$AppName).Replace("<ResultText>",$ResultText)
        Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $Line
        If (-not($Rerun_FindAppsProgramsAndUpdates)) {$Rerun_FindAppsProgramsAndUpdates = $False}
       }
      $Text +="`n$Line`n"
     }
    $Result = Display-MessageBox -Text $Text -GUI -Button Ok
    If ($Rerun_FindAppsProgramsAndUpdates)
     {
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $CCMCacheActions.$Language.txtMessage
      $Global:arrTable = @()
      FindAppsProgramsAndUpdates
     }
   }

# =============================================================================================================================================
# End functions, used in the forms blocks.
# =============================================================================================================================================

# =============================================================================================================================================
# Show the supported languages
# =============================================================================================================================================

  If ($OverviewSupportedLanguages)
   {
    Clear-Host
    $PSFile                      = $PSCommandPath
    $JSONFile                    = $PSFile.Replace(".ps1",".json")
    If (Test-Path($JSONFile))
      {
       $JSONObject                  = Get-Content -Path $JSONFile -Raw -Encoding UTF8 | ConvertFrom-Json    
       $Languages                   = @(($JSONObject | Get-Member -type NoteProperty).Name)
       $Languages                   = $Languages | Sort-Object
       Write-Host "$($Languages.Count) Supported languages:"
       ForEach ($Language in $Languages)
        {
         $LanguageName = ((([CultureInfo]::GetCultures([System.Globalization.CultureTypes]::SpecificCultures) | Where {$_.Name -like "$Language*"})[0]).DisplayName).Split(" ")[0]
         Write-Host " - Use '$Language' for the $LanguageName language." 
        }
       
       Write-Host "Use '$([char](34))$PSFile$([char](34)) -LanguageOverride <language>' to override the language."
      }
       else
      {
       Write-Host "The file '$JSONFile' does not exists."
      }
    Exit 0
   }

# =============================================================================================================================================
# Declares the variables.
# =============================================================================================================================================

  $Global:gblDetailedLogging                     = $DetailedLogging
  $strCurrentDir                                 = Split-Path -parent $MyInvocation.MyCommand.Definition
  $ApplicationName                               = "Clear Software Center Cache"
  $ApplicationVersion                            = "v1.2"
  $PowerShellLanguageMode                        = $ExecutionContext.SessionState.LanguageMode
  $Global:gblFullLanguage                        = $PowerShellLanguageMode -eq "FullLanguage"
  $Global:gblElevatedRights                      = Check-HasElevatedRights
  $MinimumWidth                                  = 1000
  $MinimumHeight                                 = 700
  $ErrorNumber                                   = 0
  $ErrorText                                     = ""
  
# =============================================================================================================================================
# Find the logpath.
# It is the key 'HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders' with the value 'Local AppData'.
# And then '\temp' is added. 
# =============================================================================================================================================

  $OnlyUserName,               `
  $LoggedOnUserInDomainFormat, `
  $UseridSID,                  `
  $InstallAccount                 = UserDetails
  $RegKey                         = "REGISTRY::HKEY_USERS\$UseridSID\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
  $ValueName                      = "Local AppData"
  $Value                          = (Get-ItemProperty $RegKey).$ValueName
  $LogPath                        = $Value + "\temp"    
  $Global:gblLogPath              = $LogPath

# =============================================================================================================================================
# Define the results file. This file contains all the results.
# =============================================================================================================================================
 
  $strLastPartOfFileName                         = " ($((Get-Date -format "yyyy-MM-dd HH-mm-ss").ToString()))"
  $PreFixLogFile                                 = $ApplicationName -replace(" ","")
  $ThisFunctionName                              = "[Main - Resultsfile]"
   
  If ($Global:gblDetailedLogging)
   {
    $Global:gblLogFile = $Global:gblLogPath + "\"+ $PreFixLogFile + $($strLastPartOfFileName + ".log")
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Logfile: $Global:gblLogFile"
   }

# =============================================================================================================================================
# Find all the arguments and put them in the log file
# Source: https://ss64.com/ps/psboundparameters.html
# =============================================================================================================================================

  $ThisFunctionName                              = "[Main - Arguments]"
  If ($Global:gblFullLanguage)
   {
    $TableWithParameters  = @()
    $RecordWithParameters = [ordered] @{"Key"     = "";
                                        "Value"   = ""}
  
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "***** Parameters part           *****"
  
    ForEach($boundparam in $PSBoundParameters.GetEnumerator()) 
     {
      $tmpValue                     = $($boundparam.Value)
      $Value                        = ""
      If ($tmpValue -is [array])
       {
        ForEach ($object in $tmpValue)
         {
          If (-not($value))
           {
            $Value = $object
           }
            else
           {
            $Value +=",$($object)"
           }
         }
       }
        else
       {
        $Value = $tmpValue
       }
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Key: $($boundparam.Key) Value: $Value" 
     }
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "***** End Parameters part       *****`r`n" 
   }
    else
   {
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "The parameters are not written to the logfile due to PowerShell ConstrainedMode."
   }

# =============================================================================================================================================
# Write the logged in user details to the log file. 
# =============================================================================================================================================
  
  $ThisFunctionName                              = "[Main - User Details]"
  Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "***** User details part         *****" 
  Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Logged on user:                  $LoggedOnUserInDomainFormat"
  Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Logged on user (SID):            $UseridSID"
  Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Has (elevated) admin rights:     $Global:gblElevatedRights"
  Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Installation account:            $InstallAccount"
  If ($Global:gblDetailedLogging)
   {
    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Logfile:                         $Global:gblLogFile"
   }
  Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "***** End User details part     *****`r`n"

# =============================================================================================================================================
# Read the JSON file with the translations.
# If the JSON file does not contain the detected language, then fallback to English.
# =============================================================================================================================================

  $ThisFunctionName                              = "[Main - Language]"
  Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "***** Language part             *****"

  if ($LanguageOverride)
   {
    $Language = $LanguageOverride
    Add-EntryToLogFile -FunctionName $ThisFunctionName  -Entry "The parameter -LanguageOverride is used. The language is '$Language'."
   }
    else
   {
    $Language = (Find-Language -CurrentUserSID $UseridSID).SubString(0, 2)
   }

  $JSONFile = $strCurrentDir + "\"+ $($MyInvocation.MyCommand.Name -replace ".ps1",".json")

  if (Test-Path($JSONFile))
   {
    $CCMCacheActions = Get-Content $JSONFile -Encoding UTF8 | ConvertFrom-Json

    if (-not($CCMCacheActions.$Language))
     {
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "The language '$Language' is not found in the json file '$JSONFile'."
      $Language = "en"
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "Falling back to the default language '$Language'."
     }

    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "The language '$Language' is used."

    Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry "***** End language part         *****`r`n" 

  # =============================================================================================================================================
  # Check if there is a (working) SCCM Client installed.
  # =============================================================================================================================================

    $BoleanSCCMClient,$ErrorMessageRegardingSCCMClient = Check-SCCMClient

    $Error02 = $($CCMCacheActions.$Language.Error02Text)
    $Error04 = $($CCMCacheActions.$Language.Error04Text).Replace("<ErrorSCCMClient>",$ErrorMessageRegardingSCCMClient).Trim()
    $Error08 = $($CCMCacheActions.$Language.Error08Text)

    If (-not $Global:gblFullLanguage)   {$ErrorNumber = $ErrorNumber +  2}
    if (-not $BoleanSCCMClient)         {$ErrorNumber = $ErrorNumber +  4}
    if (-not $Global:gblElevatedRights) {$ErrorNumber = $ErrorNumber +  8}
   } 
   else
   {
    $Error01 = "The '$JSONFile' file with the translations does not exists."
    $ErrorNumber = $ErrorNumber +  1
   }

# =============================================================================================================================================
# End reading the JSON file with the translations.
# =============================================================================================================================================

# =============================================================================================================================================
# Add assembly (only FullLanguage)
# =============================================================================================================================================
  
  If ($Global:gblFullLanguage)
   {    
    Add-Type -AssemblyName System.Windows.Forms
    Add-Type -AssemblyName System.Drawing
    Add-Type -AssemblyName System.Runtime
    Add-Type -AssemblyName PresentationFramework 
    [System.Windows.Forms.Application]::EnableVisualStyles()
    $DetectedWidth  = ([System.Windows.Forms.SystemInformation]::PrimaryMonitorSize).Width
    $DetectedHeight = ([System.Windows.Forms.SystemInformation]::PrimaryMonitorSize).Height
    If ($DetectedWidth -le $MinimumWidth)   
     {
      $ErrorNumber = $ErrorNumber + 16
      $Error16 = $($CCMCacheActions.$Language.Error16Text).Replace("<DetectedWidth>",$DetectedWidth).Replace("<AllowedWidth>",$MinimumWidth)
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $Error16
     }
    If ($DetectedHeight -le $MinimumHeight) 
     {
      $ErrorNumber = $ErrorNumber + 32
      $Error32 = $($CCMCacheActions.$Language.Error32Text).Replace("<DetectedHeight>",$DetectedHeight).Replace("<AllowedHeight>",$MinimumHeight)
      Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $Error32
     }
   }

# =============================================================================================================================================
# Error handling.
# =============================================================================================================================================

  If (($ErrorNumber -band 1) -eq 1)      {If ($ErrorText) {$ErrorText += "`n$Error01"}  else {$ErrorText = $Error01}}
  If (($ErrorNumber -band 2) -eq 2)      {If ($ErrorText) {$ErrorText += "`n$Error02"}  else {$ErrorText = $Error02}}
  If (($ErrorNumber -band 4) -eq 4)      {If ($ErrorText) {$ErrorText += "`n$Error04"}  else {$ErrorText = $Error04}}
  If (($ErrorNumber -band 8) -eq 8)      {If ($ErrorText) {$ErrorText += "`n$Error08"}  else {$ErrorText = $Error08}}
  If (($ErrorNumber -band 16) -eq 16)    {If ($ErrorText) {$ErrorText += "`n$Error16"}  else {$ErrorText = $Error16}}
  If (($ErrorNumber -band 32) -eq 32)    {If ($ErrorText) {$ErrorText += "`n$Error32"}  else {$ErrorText = $Error32}}
  
  If ($ErrorNumber -gt 0)
   {
    $DummyValue = Display-MessageBox -Text $ErrorText -GUI:$Global:gblFullLanguage -Error
    Exit $ErrorNumber
   }

# =============================================================================================================================================
# End Error handling.
# =============================================================================================================================================

  $frmClearSCCache                                           = New-Object 'System.Windows.Forms.Form'
  $btnDeleteOrphanedFolders                                  = New-Object 'System.Windows.Forms.Button'
  $chkAllItems                                               = New-Object 'System.Windows.Forms.CheckBox'
  $btnCancel                                                 = New-Object 'System.Windows.Forms.Button'
  $btnClearCache                                             = New-Object 'System.Windows.Forms.Button'
  $chkOnlyCachedItems                                        = New-Object 'System.Windows.Forms.CheckBox'
  $dgvCacheItems                                             = New-Object 'System.Windows.Forms.DataGridView'
  $lblDiscoveredItems                                        = New-Object 'System.Windows.Forms.Label'
  $chkDeleteImmediately                                      = New-Object 'System.Windows.Forms.CheckBox'

  #
  # frmClearSCCache
  #
  $frmClearSCCache.Controls.Add($btnDeleteOrphanedFolders)
  $frmClearSCCache.Controls.Add($chkAllItems)
  $frmClearSCCache.Controls.Add($btnCancel)
  $frmClearSCCache.Controls.Add($btnClearCache)
  $frmClearSCCache.Controls.Add($chkOnlyCachedItems)
  $frmClearSCCache.Controls.Add($dgvCacheItems)
  $frmClearSCCache.Controls.Add($lblDiscoveredItems)
  $frmClearSCCache.Controls.Add($chkDeleteImmediately)
  $frmClearSCCache.AutoScaleDimensions                       = New-Object System.Drawing.SizeF(6, 13)
  $frmClearSCCache.AutoScaleMode                             = 'Font'
  $frmClearSCCache.ClientSize                                = New-Object System.Drawing.Size(984, 676)
  $frmClearSCCache.FormBorderStyle                           = 'FixedSingle'
  $frmClearSCCache.Name                                      = 'frmClearSCCache'
  $frmClearSCCache.StartPosition                             = 'CenterScreen'
  $frmClearSCCache.Text                                      = "$($CCMCacheActions.$Language.frmClearSCCache) $ApplicationVersion"
  $frmClearSCCache.add_Load($frmClearSCCache_Load)
  $frmClearSCCache.MaximizeBox                               = $False
  $frmClearSCCache.MinimizeBox                               = $False
  #
  # chkDeleteImmediately
  #
  $chkDeleteImmediately.Location                             = New-Object System.Drawing.Point(13, 645)
  $chkDeleteImmediately.Name                                 = 'chkDeleteImmediately'
  $chkDeleteImmediately.Size                                 = New-Object System.Drawing.Size(400, 25)
  $chkDeleteImmediately.TabIndex                             = 7
  $chkDeleteImmediately.Text                                 = $CCMCacheActions.$Language.chkDeleteImmediately
  $chkDeleteImmediately.UseVisualStyleBackColor              = $True
  # 
  # btnDeleteOrphanedFolders
  #
  $btnDeleteOrphanedFolders.Location                         = New-Object System.Drawing.Point(540, 625)
  $btnDeleteOrphanedFolders.Name                             = 'btnDeleteOrphanedFolders'
  $btnDeleteOrphanedFolders.Size                             = New-Object System.Drawing.Size(140, 45)
  $btnDeleteOrphanedFolders.TabIndex                         = 6
  $btnDeleteOrphanedFolders.Text                             = $CCMCacheActions.$Language.btnDeleteOrphanedFolders
  $btnDeleteOrphanedFolders.UseVisualStyleBackColor          = $True
  #
  # chkAllItems
  #
  $chkAllItems.Location                                      = New-Object System.Drawing.Point(13, 625)
  $chkAllItems.Name                                          = 'chkAllItems'
  $chkAllItems.Size                                          = New-Object System.Drawing.Size(400, 25)
  $chkAllItems.TabIndex                                      = 5
  $chkAllItems.Text                                          = $CCMCacheActions.$Language.chkAllItems
  $chkAllItems.UseVisualStyleBackColor                       = $True
  #
  # btnCancel
  #
  $btnCancel.Location                                        = New-Object System.Drawing.Point(832, 625)
  $btnCancel.Name                                            = 'btnCancel'
  $btnCancel.Size                                            = New-Object System.Drawing.Size(140, 45)
  $btnCancel.TabIndex                                        = 4
  $btnCancel.Text                                            = $CCMCacheActions.$Language.btnCancel
  $btnCancel.UseVisualStyleBackColor                         = $True
  #
  # btnClearCache
  #
  $btnClearCache.Location                                    = New-Object System.Drawing.Point(686, 625)
  $btnClearCache.Name                                        = 'btnClearCache'
  $btnClearCache.Size                                        = New-Object System.Drawing.Size(140, 45)
  $btnClearCache.TabIndex                                    = 3
  $btnClearCache.Text                                        = $CCMCacheActions.$Language.btnClearCache
  $btnClearCache.UseVisualStyleBackColor                     = $True
  #
  # chkOnlyCachedItems
  #
  $chkOnlyCachedItems.Location                               = New-Object System.Drawing.Point(13, 605)
  $chkOnlyCachedItems.Name                                   = 'chkOnlyCachedItems'
  $chkOnlyCachedItems.Size                                   = New-Object System.Drawing.Size(400, 25)
  $chkOnlyCachedItems.TabIndex                               = 2
  $chkOnlyCachedItems.Text                                   = $CCMCacheActions.$Language.chkOnlyCachedItems
  $chkOnlyCachedItems.UseVisualStyleBackColor                = $True
  #
  # dgvCacheItems
  #
  $dgvCacheItems.AllowUserToAddRows                          = $False
  $dgvCacheItems.AllowUserToDeleteRows                       = $False
  $dgvCacheItems.AllowUserToResizeRows                       = $False
  $dgvCacheItems.AutoSizeColumnsMode                         = 'AllCells'
  $dgvCacheItems.AutoSizeRowsMode                            = 'AllCells'
  $dgvCacheItems.ClipboardCopyMode                           = 'Disable'
  $dgvCacheItems.ColumnHeadersHeightSizeMode                 = 'AutoSize'
  $dgvCacheItems.Location                                    = New-Object System.Drawing.Point(13, 36)
  $dgvCacheItems.Name                                        = 'dgvCacheItems'
  $dgvCacheItems.RowTemplate.Resizable                       = 'True'
  $dgvCacheItems.SelectionMode                               = 'FullRowSelect'
  $dgvCacheItems.ShowEditingIcon                             = $False
  $dgvCacheItems.Size                                        = New-Object System.Drawing.Size(959, 562)
  $dgvCacheItems.TabIndex                                    = 1
  $dgvCacheItems.ReadOnly                                    = $True
  #
  # lblDiscoveredItems
  #
  $lblDiscoveredItems.AutoSize                               = $True
  $lblDiscoveredItems.Location                               = New-Object System.Drawing.Point(12, 9)
  $lblDiscoveredItems.Name                                   = 'lblDiscoveredItems'
  $lblDiscoveredItems.Size                                   = New-Object System.Drawing.Size(235, 13)
  $lblDiscoveredItems.TabIndex                               = 0
  $lblDiscoveredItems.Text                                   = $CCMCacheActions.$Language.lblDiscoveredItems
  
# =============================================================================================================================================
# All kind of actions
# =============================================================================================================================================
  
  $btnDeleteOrphanedFolders.Enabled                          = $False

  
  $frmClearSCCache.add_Shown({FindAppsProgramsAndUpdates                              
                              If (@(CCM-FindOrphanedCCMFolders).Count -gt 0) {$btnDeleteOrphanedFolders.Enabled = $True}
                             })


  $chkAllItems.Add_CheckedChanged({SelectOrUnselectAllItemsInDGV})
  $chkOnlyCachedItems.Add_CheckedChanged({ShowOnlyCachedItems})

  $dgvCacheItems.Add_SelectionChanged({
  
# =============================================================================================================================================
# If there is no Content ID then the whole row should be greyed out. 
# See https://stackoverflow.com/questions/72535521/powershell-datagridview-avoid-selection-of-rows-based-on-a-value-in-cell 
# for more background information. 
# =============================================================================================================================================
   
   ForEach ($Row in $dgvCacheItems.SelectedRows)
    {     
     If (($Row.Cells['Content ID'].Value).Length -eq 0)
      {
       $Row.Selected = $False     
       $dgvCacheItems.Rows[$Row.Index].DefaultCellStyle.BackColor = [System.Drawing.Color]::FromArgb(255, 249, 249, 249)
       $dgvCacheItems.Rows[$Row.Index].DefaultCellStyle.ForeColor = [System.Drawing.Color]::FromArgb(255, 160, 160, 160)
      }
      else
      {
       $dgvCacheItems.Rows[$Row.Index].DefaultCellStyle.BackColor = [System.Drawing.Color]::FromArgb(255, 255, 255, 255)
       $dgvCacheItems.Rows[$Row.Index].DefaultCellStyle.ForeColor = [System.Drawing.Color]::FromArgb(255, 0  , 0  , 0)
      }
    }
  })

  $btnCancel.add_Click({
   Add-EntryToLogFile -FunctionName $ThisFunctionName -Entry $($CCMCacheActions.$Language.PressedButtonName).Replace("<ButtonName>",$CCMCacheActions.$Language.btnCancel).Replace("&","")
   If ($Global:gblDetailedLogging)
    {
     $Null = Display-MessageBox -Text $($CCMCacheActions.$Language.afterCancel).Replace("<zzz>",$($Global:gblLogFile)) -GUI -Button Ok
    }
   [void]$frmClearSCCache.Close()
   [void]$frmClearSCCache.Dispose()})

  $btnDeleteOrphanedFolders.add_Click({DeleteOrphanedFolders})

  $btnClearCache.add_Click({RemoveItemsFromCache})
  
  [void]$frmClearSCCache.ShowDialog()

# =============================================================================================================================================
# End all kind of actions.
# =============================================================================================================================================

And the JSON file ClearSoftwareCenterCache_v13.json with all the translations. You can use my script Translate content of a JSON file with Google, Microsoft and DeepL to add additional languages.

{
    "en":  {
               "afterCancel":  "The logfile '<zzz>' has been created.",
               "btnCancel":  "&Cancel",
               "btnClearCache":  "&Remove selected items from Software Center cache",
               "btnDeleteOrphanedFolders":  "Delete orphaned folders in CCM Cache",
               "CCMDeleted_1":  "The item has been removed successfully.",
               "CCMDeleted_2":  "This item is used by other items, therefore not removed.",
               "CCMDeleted_3":  "The content has been used in the last <ReferencedThreshold> days.",
               "CCMDeleted_4":  "The item has not been removed successfully.",
               "CCMDeleted_5":  "The item has no valid Last Reference Time, therefore skipped.",
               "CCMDeleted_6":  "The item must remain in cache, therefore not removed.",
               "CCMDeleted_7":  "The CacheElementID is empty, nothing is done.",
               "CCMDeleted_8":  "Skipped as this item has already been removed.",
               "chkAllItems":  "Select or unselect all items",
               "chkDeleteImmediately":  "Delete the item immediately and ignore ReferenceDays",
               "chkOnlyCachedItems":  "Show only items that are in the CCM Cache.",
               "ConfirmRemovalFolders":  "Press Ok to remove these folders, Cancel to remove nothing.",
               "ContentIDNotFoundInCache":  "The item '<Cell>' is not found in the CCM Cache.",
               "DeletingFolder":  "Deleting the folder '<FolderName>'.",
               "EndCheckSCCMClient":  "***** End checking SCCM Client  *****",
               "Error":  "Error!",
               "Error02Text":  "Powershell ConstrainedMode blocks running this script.",
               "Error04Text":  "There is no working SCCM Client. Error message: '<ErrorSCCMClient>'",
               "Error08Text":  "You need elevated (admin) rights to run this script.",
               "Error16Text":  "The screenwidth of '<DetectedWidth>' is smaller than the minimum width of '<AllowedWidth>'.",
               "Error32Text":  "The screenheight of '<DetectedHeight>' is smaller than the minimum height of '<AllowedHeight>'.",
               "ErrorCreatingFolder":  "Something went wrong while creating the folder '<FolderName>'.",
               "FolderAlreadyExists":  "The folder '<FolderName>' already exists.",
               "FolderCreated":  "The folder '<FolderName>' has been created.",
               "frmClearSCCache":  "Clear Software Center Cache",
               "Information":  "Information!",
               "lblDiscoveredItems":  "Discovered applications, packages and updates",
               "NoOrphanedFolders":  "There are no orphaned folders in the CCM Cache.",
               "NumberOfItemsInCache":  "There are <Number> items in the CCM Cache.",
               "OrphanedFolder":  "The folder '<FolderInCache>' is orphaned!",
               "PressedButtonName":  "The button '<ButtonName>' has been pressed.",
               "RemovedFromCache":  "Results of removing items from the CCM Cache",
               "RemovedFromCacheFailure":  "Failure while deleting <Cell>: <ResultText>",
               "RemovedFromCacheSuccess":  "<Cell> has been deleted successfully.",
               "SCCMClientError1":  "There is no working SCCM Client.",
               "SCCMClientError2":  "The error message is: '<ErrorMessage>'.",
               "SCCMClientOk":  "The SCCM Client is working fine.",
               "StartCheckSCCMClient":  "***** Check SCCM Client         *****",
               "txtMessage":  "Finding all the installed applications, packages and installed updates"
           },
    "de":  {
               "afterCancel":  "Das Logfile '<zzz>' wurde erstellt.",
               "btnCancel":  "Abbrechen",
               "btnClearCache":  "Entfernen ausgewählter Elemente aus dem Softwarecenter-Cache",
               "btnDeleteOrphanedFolders":  "Verwaiste Ordner im CCM Cache löschen",
               "CCMDeleted_1":  "Das Element wurde erfolgreich entfernt.",
               "CCMDeleted_2":  "Dieses Element wird von anderen Elementen verwendet und daher nicht entfernt.",
               "CCMDeleted_3":  "Der Inhalt wurde in den letzten <ReferencedThreshold> Tagen verwendet.",
               "CCMDeleted_4":  "Das Element wurde nicht erfolgreich entfernt.",
               "CCMDeleted_5":  "Das Element hat keine gültige letzte Referenzzeit, daher übersprungen.",
               "CCMDeleted_6":  "Das Element muss im Cache verbleiben und darf daher nicht entfernt werden.",
               "CCMDeleted_7":  "Die CacheElementID ist leer, nichts wird getan.",
               "CCMDeleted_8":  "Übersprungen, da dieses Element bereits entfernt wurde.",
               "chkAllItems":  "Aktivieren oder Aufheben der Auswahl aller Elemente",
               "chkDeleteImmediately":  "Löschen Sie das Element sofort und ignorieren Sie ReferenceDays",
               "chkOnlyCachedItems":  "Zeigt nur Elemente an, die sich im CCM-Cache befinden.",
               "ConfirmRemovalFolders":  "Klicken Sie auf OK, um diese Ordner zu entfernen, und auf Abbrechen, um nichts zu entfernen.",
               "ContentIDNotFoundInCache":  "Das Element '<Cell>' wird im CCM-Cache nicht gefunden.",
               "DeletingFolder":  "Löschen des Ordners '<FolderName>'.",
               "EndCheckSCCMClient":  "Prüfung beenden SCCM Client *****",
               "Error":  "Fehler!",
               "Error02Text":  "Powershell ConstrainedMode blockiert die Ausführung dieses Skripts.",
               "Error04Text":  "Es gibt keinen funktionierenden SCCM-Client. Fehlermeldung: '<ErrorSCCMClient>'",
               "Error08Text":  "Sie benötigen erhöhte (Administrator-) Rechte, um dieses Skript auszuführen.",
               "Error16Text":  "Die Bildschirmbreite von '<DetectedWidth>' ist kleiner als die Mindestbreite von '<AllowedWidth>'.",
               "Error32Text":  "Die Bildschirmhöhe von '<DetectedHeight>' ist kleiner als die Mindesthöhe von '<AllowedHeight>'.",
               "ErrorCreatingFolder":  "Beim Erstellen des Ordners '<FolderName>' ist ein Fehler aufgetreten.",
               "FolderAlreadyExists":  "Der Ordner '<FolderName>' ist bereits vorhanden.",
               "FolderCreated":  "Der Ordner '<FolderName>' wurde erstellt.",
               "frmClearSCCache":  "Softwarecenter-Cache leeren",
               "Information":  "Information!",
               "lblDiscoveredItems":  "Erkannte Anwendungen, Pakete und Updates",
               "NoOrphanedFolders":  "Es gibt keine verwaisten Ordner im CCM-Cache.",
               "NumberOfItemsInCache":  "Der CCM-Cache enthält <Number> Elemente.",
               "OrphanedFolder":  "Der Ordner '<FolderInCache>' ist verwaist!",
               "PressedButtonName":  "Der Knopf '<ButtonName>' wurde gedrückt.",
               "RemovedFromCache":  "Ergebnisse des Entfernens von Elementen aus dem CCM-Cache",
               "RemovedFromCacheFailure":  "Fehler beim Löschen von <Cell>: <ResultText>",
               "RemovedFromCacheSuccess":  "<Cell> wurde erfolgreich gelöscht.",
               "SCCMClientError1":  "Es gibt keinen funktionierenden SCCM-Client.",
               "SCCMClientError2":  "Die Fehlermeldung lautet: '<ErrorMessage>'.",
               "SCCMClientOk":  "Der SCCM-Client funktioniert einwandfrei.",
               "StartCheckSCCMClient":  "SCCM-Client prüfen *****",
               "txtMessage":  "Finden aller installierten Anwendungen, Pakete und installierten Updates"
           },
    "es":  {
               "afterCancel":  "Se ha creado el archivo de registro '<zzz>'.",
               "btnCancel":  "Cancelar",
               "btnClearCache":  "Eliminar elementos seleccionados de la caché del Centro de software",
               "btnDeleteOrphanedFolders":  "Eliminar carpetas huérfanas en CCM Cache",
               "CCMDeleted_1":  "El elemento se ha quitado correctamente.",
               "CCMDeleted_2":  "Este elemento es utilizado por otros elementos, por lo tanto, no se elimina.",
               "CCMDeleted_3":  "El contenido se ha utilizado en los últimos <ReferencedThreshold> días.",
               "CCMDeleted_4":  "El elemento no se ha eliminado correctamente.",
               "CCMDeleted_5":  "El elemento no tiene ninguna hora de última referencia válida, por lo tanto, se ha omitido.",
               "CCMDeleted_6":  "El elemento debe permanecer en caché, por lo tanto, no se quita.",
               "CCMDeleted_7":  "El CacheElementID está vacío, no se hace nada.",
               "CCMDeleted_8":  "Omitido porque este elemento ya se ha eliminado.",
               "chkAllItems":  "Seleccionar o anular la selección de todos los elementos",
               "chkDeleteImmediately":  "Eliminar el elemento inmediatamente e ignorar ReferenceDays",
               "chkOnlyCachedItems":  "Mostrar sólo los elementos que están en la caché de CCM.",
               "ConfirmRemovalFolders":  "Presione Aceptar para eliminar estas carpetas, Cancelar para eliminar nada.",
               "ContentIDNotFoundInCache":  "El elemento '<Cell>' no se encuentra en la caché del CCM.",
               "DeletingFolder":  "Eliminar la carpeta '<FolderName>'.",
               "EndCheckSCCMClient":  "Finalizar comprobación Cliente SCCM *****",
               "Error":  "¡Error!",
               "Error02Text":  "Powershell ConstrainedMode bloquea la ejecución de este script.",
               "Error04Text":  "No hay ningún cliente SCCM en funcionamiento. Mensaje de error: '<ErrorSCCMClient>'",
               "Error08Text":  "Necesita derechos elevados (admin) para ejecutar este script.",
               "Error16Text":  "El ancho de pantalla de '<DetectedWidth>' es menor que el ancho mínimo de '<AllowedWidth>'.",
               "Error32Text":  "La altura de pantalla de '<DetectedHeight>' es menor que la altura mínima de '<AllowedHeight>'.",
               "ErrorCreatingFolder":  "Algo salió mal al crear la carpeta '<FolderName>'.",
               "FolderAlreadyExists":  "La carpeta '<FolderName>' ya existe.",
               "FolderCreated":  "Se ha creado la carpeta '<FolderName>'.",
               "frmClearSCCache":  "Borrar caché del Centro de software",
               "Information":  "¡Información!",
               "lblDiscoveredItems":  "Aplicaciones, paquetes y actualizaciones descubiertos",
               "NoOrphanedFolders":  "No hay carpetas huérfanas en la caché de CCM.",
               "NumberOfItemsInCache":  "Hay <Number> elementos en la caché de CCM.",
               "OrphanedFolder":  "¡La carpeta '<FolderInCache>' está huérfana!",
               "PressedButtonName":  "Se ha pulsado el botón '<ButtonName>'.",
               "RemovedFromCache":  "Resultados de la eliminación de elementos de la caché CCM",
               "RemovedFromCacheFailure":  "Error al eliminar <Cell>: <ResultText>",
               "RemovedFromCacheSuccess":  "<Cell> se ha eliminado correctamente.",
               "SCCMClientError1":  "No hay ningún cliente SCCM en funcionamiento.",
               "SCCMClientError2":  "El mensaje de error es: '<ErrorMessage>'.",
               "SCCMClientOk":  "El cliente SCCM funciona bien.",
               "StartCheckSCCMClient":  "Comprobar cliente SCCM *****",
               "txtMessage":  "Encontrar todas las aplicaciones instaladas, paquetes y actualizaciones instaladas"
           },
    "fr":  {
               "afterCancel":  "Le fichier journal '<zzz>' a été créé.",
               "btnCancel":  "Annuler",
               "btnClearCache":  "Supprimer les éléments sélectionnés du cache du Centre logiciel",
               "btnDeleteOrphanedFolders":  "Supprimer les dossiers orphelins dans le cache CCM",
               "CCMDeleted_1":  "L’élément a été supprimé avec succès.",
               "CCMDeleted_2":  "Cet élément est utilisé par d’autres éléments, donc pas supprimé.",
               "CCMDeleted_3":  "Le contenu a été utilisé au cours des <ReferencedThreshold> derniers jours.",
               "CCMDeleted_4":  "L’élément n’a pas été supprimé avec succès.",
               "CCMDeleted_5":  "L’élément n’a pas d’heure de dernière référence valide, donc ignoré.",
               "CCMDeleted_6":  "L’élément doit rester en cache et donc ne pas être supprimé.",
               "CCMDeleted_7":  "Le CacheElementID est vide, rien n’est fait.",
               "CCMDeleted_8":  "Ignoré car cet élément a déjà été supprimé.",
               "chkAllItems":  "Sélectionner ou désélectionner tous les éléments",
               "chkDeleteImmediately":  "Supprimer l’élément immédiatement et ignorer ReferenceDays",
               "chkOnlyCachedItems":  "Afficher uniquement les éléments qui se trouvent dans le cache CCM.",
               "ConfirmRemovalFolders":  "Appuyez sur OK pour supprimer ces dossiers, sur Annuler pour ne rien supprimer.",
               "ContentIDNotFoundInCache":  "L’élément '<Cell>' est introuvable dans le cache CCM.",
               "DeletingFolder":  "Suppression du dossier '<FolderName>'.",
               "EndCheckSCCMClient":  "Fin de la vérification du client SCCM *****",
               "Error":  "Erreur!",
               "Error02Text":  "Powershell ConstrainedMode bloque l’exécution de ce script.",
               "Error04Text":  "Il n’existe aucun client SCCM fonctionnel. Message d’erreur : '<ErrorSCCMClient>'",
               "Error08Text":  "Vous avez besoin de droits (administrateur) élevés pour exécuter ce script.",
               "Error16Text":  "La largeur d’écran de '<DetectedWidth>' est inférieure à la largeur minimale de '<AllowedWidth>'.",
               "Error32Text":  "La hauteur de l’écran de '<DetectedHeight>' est inférieure à la hauteur minimale de '<AllowedHeight>'.",
               "ErrorCreatingFolder":  "Quelque chose s’est mal passé lors de la création du dossier '<FolderName>'.",
               "FolderAlreadyExists":  "Le dossier '<FolderName>' existe déjà.",
               "FolderCreated":  "Le dossier '<FolderName>' a été créé.",
               "frmClearSCCache":  "Vider le cache du Centre logiciel",
               "Information":  "Information!",
               "lblDiscoveredItems":  "Applications, packages et mises à jour découverts",
               "NoOrphanedFolders":  "Il n’y a aucun dossier orphelin dans le cache CCM.",
               "NumberOfItemsInCache":  "Il y a <Number> éléments dans le cache CCM.",
               "OrphanedFolder":  "Le dossier '<FolderInCache>' est orphelin !",
               "PressedButtonName":  "Le bouton '<ButtonName>' a été enfoncé.",
               "RemovedFromCache":  "Résultats de la suppression d’éléments du cache CCM",
               "RemovedFromCacheFailure":  "Échec lors de la suppression de <Cell> : <ResultText>",
               "RemovedFromCacheSuccess":  "<Cell> a été supprimé avec succès.",
               "SCCMClientError1":  "Il n’existe aucun client SCCM fonctionnel.",
               "SCCMClientError2":  "Le message d’erreur est : '<ErrorMessage>'.",
               "SCCMClientOk":  "Le client SCCM fonctionne correctement.",
               "StartCheckSCCMClient":  "Vérifier le client SCCM *****",
               "txtMessage":  "Recherche de toutes les applications, packages et mises à jour installés"
           },
    "id":  {
               "afterCancel":  "Log file '<zzz>' telah dibuat.",
               "btnCancel":  "Membatalkan",
               "btnClearCache":  "Menghapus item yang dipilih dari cache Pusat Perangkat Lunak",
               "btnDeleteOrphanedFolders":  "Hapus folder yatim piatu di CCM Cache",
               "CCMDeleted_1":  "Item telah berhasil dihapus.",
               "CCMDeleted_2":  "Item ini digunakan oleh item lain, oleh karena itu tidak dihapus.",
               "CCMDeleted_3":  "Konten telah digunakan dalam <ReferencedThreshold> hari terakhir.",
               "CCMDeleted_4":  "Item belum berhasil dihapus.",
               "CCMDeleted_5":  "Item tidak memiliki Waktu Referensi Terakhir yang valid, oleh karena itu dilewati.",
               "CCMDeleted_6":  "Item harus tetap dalam cache, oleh karena itu tidak dihapus.",
               "CCMDeleted_7":  "CacheElementID kosong, tidak ada yang dilakukan.",
               "CCMDeleted_8":  "Dilewati karena item ini telah dihapus.",
               "chkAllItems":  "Memilih atau membatalkan pilihan semua item",
               "chkDeleteImmediately":  "Hapus item segera dan abaikan ReferenceDays",
               "chkOnlyCachedItems":  "Hanya tampilkan item yang ada di Cache CCM.",
               "ConfirmRemovalFolders":  "Tekan Ok untuk menghapus folder ini, Batal untuk menghapus apa pun.",
               "ContentIDNotFoundInCache":  "Item '<Cell>' tidak ditemukan di Cache CCM.",
               "DeletingFolder":  "Menghapus folder '<FolderName>'.",
               "EndCheckSCCMClient":  "Akhiri pemeriksaan SCCM Client *****",
               "Error":  "Kesalahan!",
               "Error02Text":  "Blok Powershell ConstrainedMode yang menjalankan skrip ini.",
               "Error04Text":  "Tidak ada Klien SCCM yang berfungsi. Pesan kesalahan: '<ErrorSCCMClient>'",
               "Error08Text":  "Anda memerlukan hak (admin) yang ditinggikan untuk menjalankan skrip ini.",
               "Error16Text":  "Lebar layar '<DetectedWidth>' lebih kecil dari lebar minimum '<AllowedWidth>'.",
               "Error32Text":  "Ketinggian layar '<DetectedHeight>' lebih kecil dari ketinggian minimum '<AllowedHeight>'.",
               "ErrorCreatingFolder":  "Ada yang tidak beres saat membuat folder '<FolderName>'.",
               "FolderAlreadyExists":  "Folder '<FolderName>' sudah ada.",
               "FolderCreated":  "Folder '<FolderName>' telah dibuat.",
               "frmClearSCCache":  "Hapus Cache Pusat Perangkat Lunak",
               "Information":  "Informasi!",
               "lblDiscoveredItems":  "Aplikasi, paket, dan pembaruan yang ditemukan",
               "NoOrphanedFolders":  "Tidak ada folder yatim piatu di Cache CCM.",
               "NumberOfItemsInCache":  "Ada <Number> item di Cache CCM.",
               "OrphanedFolder":  "Folder '<FolderInCache>' yatim piatu!",
               "PressedButtonName":  "Tombol '<ButtonName>' telah ditekan.",
               "RemovedFromCache":  "Hasil menghapus item dari Cache CCM",
               "RemovedFromCacheFailure":  "Kegagalan saat menghapus <Cell>: <ResultText>",
               "RemovedFromCacheSuccess":  "<Cell> telah berhasil dihapus.",
               "SCCMClientError1":  "Tidak ada Klien SCCM yang berfungsi.",
               "SCCMClientError2":  "Pesan kesalahannya adalah: '<ErrorMessage>'.",
               "SCCMClientOk":  "Klien SCCM berfungsi dengan baik.",
               "StartCheckSCCMClient":  "Periksa Klien SCCM *****",
               "txtMessage":  "Menemukan semua aplikasi yang diinstal, paket, dan pembaruan yang diinstal"
           },
    "it":  {
               "afterCancel":  "Il file di registro '<zzz>' è stato creato.",
               "btnCancel":  "Annulla",
               "btnClearCache":  "Rimuovere gli elementi selezionati dalla cache di Software Center",
               "btnDeleteOrphanedFolders":  "Eliminazione delle cartelle orfane nella cache CCM",
               "CCMDeleted_1":  "L'elemento è stato rimosso correttamente.",
               "CCMDeleted_2":  "Questo elemento è utilizzato da altri elementi, quindi non rimosso.",
               "CCMDeleted_3":  "Il contenuto è stato utilizzato negli ultimi <ReferencedThreshold> giorni.",
               "CCMDeleted_4":  "L'elemento non è stato rimosso correttamente.",
               "CCMDeleted_5":  "L'articolo non ha un Ultimo Riferimento Ora valido, pertanto saltato.",
               "CCMDeleted_6":  "L'elemento deve rimanere nella cache, quindi non rimosso.",
               "CCMDeleted_7":  "Il CacheElementID è vuoto, non viene fatto nulla.",
               "CCMDeleted_8":  "Saltato in quanto questo elemento è già stato rimosso.",
               "chkAllItems":  "Selezionare o deselezionare tutti gli elementi",
               "chkDeleteImmediately":  "Eliminare immediatamente l'elemento e ignorare ReferenceDays",
               "chkOnlyCachedItems":  "Mostra solo gli elementi presenti nella cache CCM.",
               "ConfirmRemovalFolders":  "Premere OK per rimuovere queste cartelle, Annulla per non rimuovere nulla.",
               "ContentIDNotFoundInCache":  "L'elemento '<Cell>' non si trova nella cache CCM.",
               "DeletingFolder":  "Eliminazione della cartella '<FolderName>'.",
               "EndCheckSCCMClient":  "Fine controllo SCCM Client *****",
               "Error":  "Errore!",
               "Error02Text":  "Powershell ConstrainedMode blocca l'esecuzione di questo script.",
               "Error04Text":  "Non esiste un client SCCM funzionante. Messaggio di errore: '<ErrorSCCMClient>'",
               "Error08Text":  "Per eseguire questo script sono necessari diritti elevati (amministratore).",
               "Error16Text":  "La larghezza dello schermo di '<DetectedWidth>' è inferiore alla larghezza minima di '<AllowedWidth>'.",
               "Error32Text":  "L'altezza dello schermo di '<DetectedHeight>' è inferiore all'altezza minima di '<AllowedHeight>'.",
               "ErrorCreatingFolder":  "Qualcosa è andato storto durante la creazione della cartella '<FolderName>'.",
               "FolderAlreadyExists":  "La cartella '<FolderName>' esiste già.",
               "FolderCreated":  "La cartella '<FolderName>' è stata creata.",
               "frmClearSCCache":  "Cancella cache di Software Center",
               "Information":  "Informazione!",
               "lblDiscoveredItems":  "Applicazioni, pacchetti e aggiornamenti rilevati",
               "NoOrphanedFolders":  "Non ci sono cartelle orfane nella cache CCM.",
               "NumberOfItemsInCache":  "Ci sono <Number> elementi nella cache CCM.",
               "OrphanedFolder":  "La cartella '<FolderInCache>' è orfana!",
               "PressedButtonName":  "Il pulsante '<ButtonName>' è stato premuto.",
               "RemovedFromCache":  "Risultati della rimozione di elementi dalla cache CCM",
               "RemovedFromCacheFailure":  "Errore durante l'eliminazione di <Cell>: <ResultText>",
               "RemovedFromCacheSuccess":  "<Cell> è stato eliminato correttamente.",
               "SCCMClientError1":  "Non esiste un client SCCM funzionante.",
               "SCCMClientError2":  "Il messaggio di errore è: '<ErrorMessage>'.",
               "SCCMClientOk":  "Il client SCCM funziona correttamente.",
               "StartCheckSCCMClient":  "Controlla il client SCCM *****",
               "txtMessage":  "Trovare tutte le applicazioni installate, i pacchetti e gli aggiornamenti installati"
           },
    "nl":  {
               "afterCancel":  "Het logbestand '<zzz>' is aangemaakt.",
               "btnCancel":  "Annuleren",
               "btnClearCache":  "Geselecteerde items verwijderen uit de Software Center-cache",
               "btnDeleteOrphanedFolders":  "Zwevende mappen verwijderen in CCM Cache",
               "CCMDeleted_1":  "Het item is verwijderd.",
               "CCMDeleted_2":  "Dit item wordt gebruikt door andere items, dus niet verwijderd.",
               "CCMDeleted_3":  "De inhoud is de afgelopen <ReferencedThreshold> dagen gebruikt.",
               "CCMDeleted_4":  "Het item is niet verwijderd.",
               "CCMDeleted_5":  "Het item heeft geen geldige laatste referentietijd en is daarom overgeslagen.",
               "CCMDeleted_6":  "Het item moet in de cache blijven en dus niet worden verwijderd.",
               "CCMDeleted_7":  "De CacheElementID is leeg, er wordt niets gedaan.",
               "CCMDeleted_8":  "Overgeslagen omdat dit item al is verwijderd.",
               "chkAllItems":  "Alle items in- of uitschakelen",
               "chkDeleteImmediately":  "Verwijder het item onmiddellijk en negeer ReferenceDays",
               "chkOnlyCachedItems":  "Alleen items weergeven die zich in de CCM-cache bevinden.",
               "ConfirmRemovalFolders":  "Druk op OK om deze mappen te verwijderen, op Annuleren om niets te verwijderen.",
               "ContentIDNotFoundInCache":  "Het item '<Cell>' is niet te vinden in de CCM-cache.",
               "DeletingFolder":  "Het verwijderen van de map '<FolderName>'.",
               "EndCheckSCCMClient":  "Einde controle SCCM Client *****",
               "Error":  "Fout!",
               "Error02Text":  "Powershell ConstrainedMode blokkeert het uitvoeren van dit script.",
               "Error04Text":  "Er is geen werkende SCCM Client. Foutmelding: '<ErrorSCCMClient>'",
               "Error08Text":  "U hebt verhoogde (beheerders)rechten nodig om dit script uit te voeren.",
               "Error16Text":  "De schermbreedte van '<DetectedWidth>' is kleiner dan de minimale breedte van '<AllowedWidth>'.",
               "Error32Text":  "De schermhoogte van '<DetectedHeight>' is kleiner dan de minimale hoogte van '<AllowedHeight>'.",
               "ErrorCreatingFolder":  "Er is iets misgegaan tijdens het aanmaken van de map '<FolderName>'.",
               "FolderAlreadyExists":  "De map '<FolderName>' bestaat al.",
               "FolderCreated":  "De map '<FolderName>' is aangemaakt.",
               "frmClearSCCache":  "Software Center-cache wissen",
               "Information":  "Informatie!",
               "lblDiscoveredItems":  "Ontdekte applicaties, pakketten en updates",
               "NoOrphanedFolders":  "Er zijn geen zwevende mappen in de CCM-cache.",
               "NumberOfItemsInCache":  "Er zijn <Number> items in de CCM-cache.",
               "OrphanedFolder":  "De map '<FolderInCache>' is verweesd!",
               "PressedButtonName":  "De knop '<ButtonName>' is ingedrukt.",
               "RemovedFromCache":  "Resultaten van het verwijderen van items uit de CCM-cache",
               "RemovedFromCacheFailure":  "Mislukt tijdens het verwijderen van <Cell>: <ResultText>",
               "RemovedFromCacheSuccess":  "<Cell> is verwijderd.",
               "SCCMClientError1":  "Er is geen werkende SCCM Client.",
               "SCCMClientError2":  "De foutmelding is: '<ErrorMessage>'.",
               "SCCMClientOk":  "De SCCM Client werkt prima.",
               "StartCheckSCCMClient":  "Controleer SCCM Client *****",
               "txtMessage":  "Alle geïnstalleerde applicaties, pakketten en geïnstalleerde updates vinden"
           },
    "pt":  {
               "afterCancel":  "O arquivo de log '<zzz>' foi criado.",
               "btnCancel":  "Cancelar",
               "btnClearCache":  "Remover itens selecionados do cache do Centro de Software",
               "btnDeleteOrphanedFolders":  "Excluir pastas órfãs no Cache do CCM",
               "CCMDeleted_1":  "O item foi removido com êxito.",
               "CCMDeleted_2":  "Este item é usado por outros itens, portanto, não removido.",
               "CCMDeleted_3":  "O conteúdo foi usado nos últimos <ReferencedThreshold> dias.",
               "CCMDeleted_4":  "O item não foi removido com êxito.",
               "CCMDeleted_5":  "O item não tem Hora da Última Referência válida, portanto, ignorado.",
               "CCMDeleted_6":  "O item deve permanecer em cache, portanto, não deve ser removido.",
               "CCMDeleted_7":  "O CacheElementID está vazio, nada é feito.",
               "CCMDeleted_8":  "Ignorado, pois este item já foi removido.",
               "chkAllItems":  "Selecionar ou desmarcar todos os itens",
               "chkDeleteImmediately":  "Exclua o item imediatamente e ignore ReferenceDays",
               "chkOnlyCachedItems":  "Mostrar somente os itens que estão no Cache do CCM.",
               "ConfirmRemovalFolders":  "Pressione Ok para remover essas pastas, Cancelar para remover nada.",
               "ContentIDNotFoundInCache":  "O item '<Cell>' não é encontrado no cache do CCM.",
               "DeletingFolder":  "Excluindo a pasta '<FolderName>'.",
               "EndCheckSCCMClient":  "Verificação final do cliente SCCM *****",
               "Error":  "Erro!",
               "Error02Text":  "O Powershell ConstrainedMode bloqueia a execução desse script.",
               "Error04Text":  "Não há nenhum cliente SCCM funcionando. Mensagem de erro: '<ErrorSCCMClient>'",
               "Error08Text":  "Você precisa de direitos elevados (admin) para executar esse script.",
               "Error16Text":  "A largura de tela de '<DetectedWidth>' é menor do que a largura mínima de '<AllowedWidth>'.",
               "Error32Text":  "A altura da tela de '<DetectedHeight>' é menor do que a altura mínima de '<AllowedHeight>'.",
               "ErrorCreatingFolder":  "Algo deu errado ao criar a pasta '<FolderName>'.",
               "FolderAlreadyExists":  "A pasta '<FolderName>' já existe.",
               "FolderCreated":  "A pasta '<FolderName>' foi criada.",
               "frmClearSCCache":  "Limpar cache do Centro de Software",
               "Information":  "Informação!",
               "lblDiscoveredItems":  "Descobertas, pacotes e atualizações",
               "NoOrphanedFolders":  "Não há pastas órfãs no Cache do CCM.",
               "NumberOfItemsInCache":  "Há <Number> itens no Cache do CCM.",
               "OrphanedFolder":  "A pasta '<FolderInCache>' está órfã!",
               "PressedButtonName":  "O botão '<ButtonName>' foi pressionado.",
               "RemovedFromCache":  "Resultados da remoção de itens do Cache do CCM",
               "RemovedFromCacheFailure":  "Falha ao excluir <Cell>: <ResultText>",
               "RemovedFromCacheSuccess":  "<Cell> foi excluído com êxito.",
               "SCCMClientError1":  "Não há nenhum cliente SCCM funcionando.",
               "SCCMClientError2":  "A mensagem de erro é: '<ErrorMessage>'.",
               "SCCMClientOk":  "O cliente SCCM está funcionando bem.",
               "StartCheckSCCMClient":  "Verifique o cliente SCCM *****",
               "txtMessage":  "Localizando todos os aplicativos, pacotes e atualizações instalados"
           },
    "zh":  {
               "afterCancel":  "日志文件“<zzz>”已创建。",
               "btnCancel":  "取消",
               "btnClearCache":  "从软件中心缓存中删除所选项",
               "btnDeleteOrphanedFolders":  "删除 CCM 缓存中的孤立文件夹",
               "CCMDeleted_1":  "已成功删除该项目。",
               "CCMDeleted_2":  "此项目由其他项目使用,因此不会删除。",
               "CCMDeleted_3":  "该内容在过去 <ReferencedThreshold> 天内被使用过。",
               "CCMDeleted_4":  "该项目尚未成功删除。",
               "CCMDeleted_5":  "该项目没有有效的最后参考时间,因此已跳过。",
               "CCMDeleted_6":  "该项必须保留在缓存中,因此不会被删除。",
               "CCMDeleted_7":  "CacheElementID 为空,不执行任何操作。",
               "CCMDeleted_8":  "已跳过,因为此项目已被删除。",
               "chkAllItems":  "选择或取消选择所有项目",
               "chkDeleteImmediately":  "立即删除项目并忽略参考日",
               "chkOnlyCachedItems":  "仅显示 CCM 缓存中的项目。",
               "ConfirmRemovalFolders":  "按“确定”删除这些文件夹,按“取消”删除任何内容。",
               "ContentIDNotFoundInCache":  "在 CCM 缓存中找不到项目“<Cell>”。",
               "DeletingFolder":  "删除文件夹“<FolderName>”。",
               "EndCheckSCCMClient":  "结束检查 SCCM 客户端 *****",
               "Error":  "错误!",
               "Error02Text":  "Powershell 受限模式阻止运行此脚本。",
               "Error04Text":  "没有正常工作的 SCCM 客户端。错误消息:“<ErrorSCCMClient>”",
               "Error08Text":  "需要提升的(管理员)权限才能运行此脚本。",
               "Error16Text":  "“<DetectedWidth>”的屏幕宽度小于“<AllowedWidth>”的最小宽度。",
               "Error32Text":  "“<DetectedHeight>”的屏幕高度小于“<AllowedHeight>”的最小高度。",
               "ErrorCreatingFolder":  "创建文件夹“<FolderName>”时出错。",
               "FolderAlreadyExists":  "文件夹“<FolderName>”已存在。",
               "FolderCreated":  "文件夹“<FolderName>”已创建。",
               "frmClearSCCache":  "清除软件中心缓存",
               "Information":  "信息!",
               "lblDiscoveredItems":  "发现的应用程序、包和更新",
               "NoOrphanedFolders":  "CCM 缓存中没有孤立文件夹。",
               "NumberOfItemsInCache":  "CCM 缓存中有<Number>项。",
               "OrphanedFolder":  "文件夹“<FolderInCache>”是孤立的!",
               "PressedButtonName":  "已按下“<ButtonName>”按钮。",
               "RemovedFromCache":  "从 CCM 缓存中删除项目的结果",
               "RemovedFromCacheFailure":  "删除<Cell>时失败:<ResultText>",
               "RemovedFromCacheSuccess":  "已成功删除<Cell>。",
               "SCCMClientError1":  "没有正常工作的 SCCM 客户端。",
               "SCCMClientError2":  "错误消息为:“<ErrorMessage>”。",
               "SCCMClientOk":  "SCCM 客户端工作正常。",
               "StartCheckSCCMClient":  "检查 SCCM 客户端 *****",
               "txtMessage":  "查找所有已安装的应用程序、软件包和已安装的更新"
           }
}









After extracting the ZIP file, please 'unblock' the Powershell script and/or the batch file. 

Unblock
Unblock