CATE.ps1


<#PSScriptInfo
 
.VERSION 4.7
 
.GUID f842f577-3f42-4cb0-91e7-97b499260a21
 
.AUTHOR Jonathan E. Brickman
 
.COMPANYNAME Ponderworthy Music
 
.COPYRIGHT (c) 2018 Jonathan E. Brickman
 
.TAGS
 
.LICENSEURI https://opensource.org/licenses/BSD-3-Clause
 
.PROJECTURI https://github.com/jebofponderworthy/windows-tools
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
CATE - Clean All Temp Etc
Cleans temporary files and folders from all standard user temp folders,
system profile temp folders, and system temp folders (they are not the same!);
also clears logs, IE caches, Firefox caches, Chrome caches, Ask Partner Network data,
Adobe Flash caches, Java deployment caches, and Microsoft CryptnetURL caches.
 
.PRIVATEDATA
 
#>
 















































































<#
 
.DESCRIPTION
Clean All Temp Etc - cleans temporary files and folders from all standard user and system temp folders, clears logs, and more
 
#>


Param()


#############################
# CATE: Clean All Temp Etc. #
#############################

#
# by Jonathan E. Brickman
#
# Cleans temp files from all user profiles and
# several other locations. Also clears log files.
#
# Copyright 2018 Jonathan E. Brickman
# https://notes.ponderworthy.com/
# This script is licensed under the 3-Clause BSD License
# https://opensource.org/licenses/BSD-3-Clause
# and is reprised at the end of this file
#

# Self-elevate if not already elevated.

"*************************"
" Clean All Temp Etc. "
"*************************"

if (([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))
    {
    "Running elevated; good."
    ""
    }
else {
    "Not running as elevated. Starting elevated shell."
    Start-Process powershell -WorkingDirectory $PSScriptRoot -Verb runAs -ArgumentList "-noprofile -noexit -file $PSCommandPath"
    return "Done. This one will now exit."
    ""
    }

# Get environment variables etc.

$envTEMP = [Environment]::GetEnvironmentVariable("TEMP", "Machine")
$envTMP = [Environment]::GetEnvironmentVariable("TEMP", "Machine")
$envSystemRoot = $env:SystemRoot
$envProgramData = $env:ProgramData

$PSDefaultParameterValues['Out-File:Encoding'] = 'utf8'

$originalLocation = Get-Location

$CATEStatus = ""

# Get initial free disk space.

function DriveSpace {
    param( [string] $strComputer)

    # Does the server responds to a ping (otherwise the WMI queries will fail)

    $result = Get-CimInstance -query "select * from win32_pingstatus where address = '$strComputer'"
    if ($result.protocoladdress) {

        $totalFreeSpace = 0

        # Get the disks for this computer, and total the free space
        Get-CimInstance -Query "Select * FROM Win32_LogicalDisk WHERE DriveType=3" | ForEach-Object {
            $totalFreeSpace = $totalFreeSpace + $_.freespace
            }

        return $totalFreeSpace
        }
    }

function RptDriveSpace {
    param( $rawDriveSpace )

    $MBDriveSpace = [double]$rawDriveSpace / 1024000.0
    $truncatedDriveSpace = [double]([math]::Truncate($MBDriveSpace))
    $fracDriveSpace = [double]$MBDriveSpace - [double]$truncatedDriveSpace

    return "{0:N0}{1:.###}" -f $truncatedDriveSpace, $fracDriveSpace
    }

$initialFreeSpace = DriveSpace("localhost")
$strOut = RptDriveSpace( $initialFreeSpace )
$strOut = "Initial free space (all drives): " + $strOut + " megabytes."
Write-Output $strOut

""
"Working..."
""

# Here is an external variable to contain the "Status" text
# for progress reporting.

$CATEStatus = "Working..."

# Now we set up an array containing folders to be checked for and
# cleaned out if present, for every profile.

$foldersToClean = @(
    "\Local Settings\Temp",
    "\Local Settings\Temporary Internet Files",
    "\AppData\Local\Microsoft\Windows\Temporary Internet Files",
    "\AppData\Local\Microsoft\Windows\INetCache\IE",
    "\AppData\Local\Microsoft\Windows\INetCache\Low\Content.IE5",
    "\AppData\Local\Microsoft\Windows\INetCache\Low\Flash",
    "\AppData\Local\Microsoft\Windows\INetCache\Content.Outlook",
    "\AppData\Local\Google\Chrome\User Data\Default\Cache",
    "\AppData\Local\AskPartnerNetwork",
    "\AppData\Local\Temp",
    "\Application Data\Local\Microsoft\Windows\WER",
    "\Application Data\Adobe\Flash Player\AssetCache",
    "\Application Data\Sun\Java\Deployment\cache",
    "\Application Data\Microsoft\CryptnetUrlCache"
    )

$ffFoldersToClean = @(
    "cache",
    "cache2\entries",
    "thumbnails",
    "cookies.sqlite",
    "webappstore.sqlite",
    "chromeappstore.sqlite"
    )

# A quasiprimitive for PowerShell-style progress reporting.

function ShowCATEProgress {
    param( [string]$reportStatus, [string]$currentOp )

    Write-Progress -Activity "Clean All Temp Etc" -Status $reportStatus -PercentComplete -1 -CurrentOperation $currentOp
    }

# Embedded C# code for actual deletes.
# Needed because, as is reported very very rarely, PowerShell deletes are buggy
# and in practice often don't work, sometimes without error messages.

$RecursiveDeleteSource = @"
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Management.Automation.Runspaces;
using System.Runtime.InteropServices;
 
 
namespace CATEcsharp
{
    public class runtime
    {
        public static void RecursiveDeleteContentsOf(DirectoryInfo baseDir, int ProfileNumber)
        {
            if (!baseDir.Exists)
                return;
 
            Parallel.ForEach(baseDir.EnumerateDirectories(), (dir) =>
                {
                RecursiveDelete(dir, ProfileNumber);
                });
             
            Parallel.ForEach(baseDir.EnumerateFiles(), (file) =>
            {
                try
                {
                    file.IsReadOnly = false;
                    file.Delete();
                    nextProgressDot();
                    nextProgressString(ProgressDot);
                    ShowCATEProgress("Deleting in " + baseDir.FullName + " ... ", ProgressString, ProfileNumber);
                }
                catch
                {
                }
            });
        }
     
        public static void RecursiveDelete(DirectoryInfo baseDir, int ProfileNumber)
        {
            if (!baseDir.Exists)
                return;
 
            Parallel.ForEach(baseDir.EnumerateDirectories(), (dir) =>
                {
                RecursiveDelete(dir, ProfileNumber);
                });
 
            Parallel.ForEach(baseDir.EnumerateFiles(), (file) =>
            {
                try
                {
                    file.IsReadOnly = false;
                    file.Delete();
                    nextProgressDot();
                    nextProgressString(ProgressDot);
                    ShowCATEProgress("Deleting in " + baseDir.FullName + " ... ", ProgressString, ProfileNumber);
                }
                catch
                {
                }
            });
     
            try
            {
                baseDir.Delete();
                nextProgressString("/");
                ShowCATEProgress("Deleting in " + baseDir.FullName + " ... ", ProgressString, ProfileNumber);
            }
            catch
            {
            }
        }
         
        public static void RecursiveDeleteFilesOnly(DirectoryInfo baseDir, string Wildcard, int ProfileNumber)
        {
            if (!baseDir.Exists)
                return;
 
            Parallel.ForEach(baseDir.EnumerateDirectories(), (dir) =>
            {
                RecursiveDeleteFilesOnly(dir, Wildcard, ProfileNumber);
            });
 
            Parallel.ForEach(baseDir.EnumerateFiles(Wildcard), (file) =>
            {
                try
                {
                    file.IsReadOnly = false;
                    file.Delete();
                    nextProgressDot();
                    nextProgressString(ProgressDot);
                    ShowCATEProgress("Deleting in " + baseDir.FullName + " ... ", ProgressString, ProfileNumber);
                }
                catch
                {
                }
            });
        }
         
        // ProgressString will gradually look something like this: .....//..././...//.///.
        // maximum 60 characters long, updated by deleting the first and adding to the end.
         
        public static string ProgressString;
     
        public static void nextProgressString(string ProgressChar)
        {
            ProgressString += ProgressChar;
            if (ProgressString.Length > 60)
                ProgressString = ProgressString.Remove(0,1); // Remove the first character.
        }
         
        // ProgressDot alternates between . (period, 46) and ? (middle dot, 183), so animation stays
        // visible when it's just many files.
         
        public static string ProgressDot;
        public static int DotCount = 1;
         
        public static void nextProgressDot()
        {
        DotCount++;
        if (DotCount % 3 == 0)
            {
            ProgressDot = ((char)183).ToString(); // Middle dot
            DotCount = 1;
            }
        else
            ProgressDot = ((char)46).ToString(); // Period
        }
     
        private static void ShowCATEProgress(string reportStatus, string currentOp, int profileNumber)
        {
            string statusText;
         
            if (profileNumber != 0)
                statusText = "Working on profile #" + profileNumber.ToString() + ". " + reportStatus;
            else
                statusText = reportStatus;
         
            var RptCmd = @"Write-Progress -Activity ""Clean All Temp Etc""";
            RptCmd += @" -Status """ + statusText + @""" -PercentComplete -1";
            RptCmd += @" -CurrentOperation """ + currentOp + @"""";
            // Console.Write(RptCmd + "\r\n");
            var runspace = Runspace.DefaultRunspace;
            var pipeline = runspace.CreateNestedPipeline(RptCmd, false);
            pipeline.Invoke();
        }
    }
}
"@

# Add the c# code to the powershell type definitions
Add-Type -TypeDefinition $RecursiveDeleteSource -Language CSharp

# Example code for a delete, working:
# [CATEcsharp.runtime]::RecursiveDeleteContentsOf('C:\test', 0)

function CATE-Recursive-Delete {
    param( [string]$strFolderPath, [int]$ProfileNumber=0  )
    
    ShowCATEProgress $CATEStatus $strFolderPath
    try {
        [CATEcsharp.runtime]::RecursiveDelete($strFolderPath, $ProfileNumber)
        }
    catch {
        }
    }
    
function CATE-Recursive-Delete-Folder-Contents {
    param( [string]$strFolderPath, [int]$ProfileNumber=0  )
    
    ShowCATEProgress $CATEStatus $strFolderPath
    try {
        [CATEcsharp.runtime]::RecursiveDeleteContentsOf($strFolderPath, $ProfileNumber)
        }
    catch {
        }
    }
    
function CATE-Recursive-Delete-Files-Only {
    param( [string]$strFolderPath, [string]$WildCard, [int]$ProfileNumber=0  )

    ShowCATEProgress $CATEStatus $strFolderPath
    try {
        [CATEcsharp.runtime]::RecursiveDeleteFilesOnly($strFolderPath, $WildCard)
        }
    catch {
        }
    }
        
# Next we loop through all of the paths for all user profiles
# as recorded in the registry, and delete temp files.

# Outer loop enumerates all user profiles
$ProfileList = Get-ChildItem -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\"
$ProfileCount = $ProfileList.Count
$ProfileNumber = 1
$ProfileList | ForEach-Object {
    $profileItem = Get-ItemProperty $_.pspath
    $CATEStatus = "Working on (profile " + $ProfileNumber + "/" + $ProfileCount + ") " + $profileItem.ProfileImagePath + " ..."
    $ProfileNumber += 1

    # This loop enumerates all non-Firefox folder subpaths within profiles to be cleaned
    ForEach ($folderSubpath in $foldersToClean) {
        $ToClean = $profileItem.ProfileImagePath + $folderSubpath
        If (Test-Path $ToClean) {
            # If the actual path exists, clean it
            CATE-Recursive-Delete-Folder-Contents $ToClean $ProfileNumber
            }
        }
        
    # These loops handle Firefox and multiple FF profiles if they exist
    $ffProfilePath = $profileItem.ProfileImagePath + '\AppData\Local\Mozilla\Firefox\Profiles\'
    If (Test-Path $ffProfilePath) {
        Get-ChildItem -Path $ffProfilePath | ForEach-Object {
            $ffProfilePath = Get-ItemProperty $_.pspath
        
            ForEach ($subPath in $ffFoldersToClean) {
                $ToClean = "$ffProfilePath\$subPath"
                CATE-Recursive-Delete-Folder-Contents $ToClean $ProfileNumber
                }
            }
        }
    $ffProfilePath = $profileItem.ProfileImagePath + '\AppData\Roaming\Mozilla\Firefox\Profiles\'
    If (Test-Path $ffProfilePath) {
        Get-ChildItem -Path $ffProfilePath | ForEach-Object {
            $ffProfilePath = Get-ItemProperty $_.pspath
        
            ForEach ($subPath in $ffFoldersToClean) {
                $ToClean = "$ffProfilePath\$subPath"
                CATE-Recursive-Delete-Folder-Contents $ToClean $ProfileNumber
                }
            }
        }

    # A subpath to be eliminated altogether, also present in the $foldersToClean list above
    CATE-Recursive-Delete ($profileItem.ProfileImagePath + '\AppData\Local\AskPartnerNetwork') $ProfileNumber
    }

# Now empty certain folders

$CATEStatus = "Working on other folders ..."

CATE-Recursive-Delete-Folder-Contents $envTEMP

CATE-Recursive-Delete-Folder-Contents $envTMP

CATE-Recursive-Delete-Folder-Contents ($envSystemRoot + "\Temp")

CATE-Recursive-Delete-Folder-Contents ($envSystemRoot + "\system32\wbem\logs")

CATE-Recursive-Delete-Folder-Contents ($envSystemRoot + "\system32\Debug")

CATE-Recursive-Delete-Folder-Contents ($envSystemRoot + "\PCHEALTH\ERRORREP\UserDumps")

CATE-Recursive-Delete-Folder-Contents ($envProgramData + "\Microsoft\Windows\WER\ReportQueue")

# And then delete log files by wildcard, recursing through folders

CATE-Recursive-Delete-Files-Only ($envSystemRoot + '\system32\Logfiles') '*.log'

CATE-Recursive-Delete-Files-Only ($envSystemRoot + '\system32\Logfiles') '*.EVM'

CATE-Recursive-Delete-Files-Only ($envSystemRoot + '\system32\Logfiles') '*.EVM.*'

CATE-Recursive-Delete-Files-Only ($envSystemRoot + '\system32\Logfiles') '*.etl'

CATE-Recursive-Delete-Files-Only ($envSystemRoot + '\Logs') '*.log'

CATE-Recursive-Delete-Files-Only ($envSystemRoot + '\Logs') '*.etl'

CATE-Recursive-Delete-Files-Only ($envSystemRoot + '\inf') '*.log'

CATE-Recursive-Delete-Files-Only ($envSystemRoot + '\Prefetch') '*.pf'

""
""

$strOut = RptDriveSpace( $initialFreeSpace )
$strOut = "Initial free space (all drives): " + $strOut + " megabytes."
Write-Output $strOut

$finalFreeSpace = DriveSpace("localhost")
$strOut = RptDriveSpace( $finalFreeSpace )
$strOut = "Final free space (all drives): " + $strOut + " megabytes."
Write-Output $strOut

$freedSpace = $finalFreeSpace - $initialFreeSpace
$strOut = RptDriveSpace ( $freedSpace )
$strOut = "Freed " + $strOut + " megabytes."
Write-Output ""
Write-Output $strOut
Write-Output ""

Set-Location $originalLocation

Start-Sleep 7

# The 3-Clause BSD License

# SPDX short identifier: BSD-3-Clause

# Note: This license has also been called
# the New BSD License or the Modified BSD License.
# See also the 2-clause BSD License.

# Copyright 2017 Jonathan E. Brickman

# Redistribution and use in source and binary
# forms, with or without modification, are
# permitted provided that the following conditions are met:

# 1. Redistributions of source code must retain the
# above copyright notice, this list of conditions and
# the following disclaimer.

# 2. Redistributions in binary form must reproduce the
# above copyright notice, this list of conditions and
# the following disclaimer in the documentation and/or
# other materials provided with the distribution.

# 3. Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or
# promote products derived from this software without
# specific prior written permission.

# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
# CONTRIBUTORS AS IS, AND ANY EXPRESS OR IMPLIED WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
# OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.