Clean Temp With PowerShell

You would think that by now Windows and Windows applications would be much better about cleaning up temporary files. But no.  It is amazing how many files I still see in my %TEMP% directory.  I’ve always operated under the assumption that any file in %TEMP% that was created or earlier than the time I last booted up is fair game for deletion. I finally got around to using PowerShell for this management task.

I developed a script called Clean-Temp.ps1 that deletes all files in my %TEMP% directory including subfolders, if the last modified time is older than my last boot time.

   1: Function Remove-File {
   2: #-Recurse will search through all subfolders
   3: #-hidden will the -force parameter for Get-ChildItem
   4: #-force will use the -force parameter with Remove-Item
   5:  
   6:     Param([string]$path=$env:temp,
   7:           [datetime]$cutoff=$(Throw "You must enter a cutoff date!"),
   8:           [Switch]$Recurse,
   9:           [Switch]$hidden,
  10:           [Switch]$force)
  11:     
  12:     Write-Host "Removing files in $path older than $cutoff" -foregroundcolor CYAN
  13:         
  14:     $cmd="Get-ChildItem $path"
  15:     
  16:     if ($recurse) {
  17:         $cmd=$cmd + " -recurse"
  18:     }
  19:     
  20:     if ($hidden) {
  21:         $cmd=$cmd + " -force"
  22:     }
  23:     
  24:     #create an array to store file information
  25:     $files=@()
  26:     
  27:     # execute the command string filtering out directories
  28:     &$executioncontext.InvokeCommand.NewScriptBlock($cmd) | 
  29:     where {-not $_.PSIsContainer -and $_.lastwritetime -lt $cutoff} | 
  30:      foreach {
  31:         #add current file to array
  32:         $files+=$_
  33:         
  34:         if ($force) {
  35:         #YOU MUST REMOVE -WHATIF TO ACTUALLY DELETE FILES
  36:             Remove-Item $_.fullname -force -WHATIF
  37:             }
  38:         else {
  39:             Remove-Item $_.fullname -WHATIF
  40:             }     
  41:     } #end forEach
  42:     
  43:     $stats=$files | Measure-Object -Sum length
  44:     $msg="Attempted to delete {0} files for a total of {1} MB ({2} bytes)" -f 
  45:     $stats.count,($stats.sum/1MB -as [int]),$stats.sum
  46:     
  47:     Write-Host $msg -foregroundcolor CYAN
  48:  
  49: }
  50:  
  51: $query="Select LastBootUpTime from Win32_OperatingSystem"
  52: $boot=Get-WmiObject -query $query
  53: [datetime]$boottime=$boot.ConvertToDateTime($boot.Lastbootuptime)
  54:  
  55: Remove-File $env:temp $boottime -recurse -hidden -force
  56:  

The main body of the script is at line 51 which defines a WMI query string. At Line 53 I’m converting the LastBootUpTime to a datetime object. Line 55 is calling the Remove-File function which does all the heavy lifting. The function can be used for any directory. You need to specify a folder path.  The default is the %TEMP% folder. You must also specify a date time value to use for the cut off. The function uses Get-ChildItem and Remove-Item. In order to pass on parameters to those functions to search for hidden files or force a deletion, the parameter set also includes some switches.

   1: Param([string]$path=$env:temp,
   2:       [datetime]$cutoff=$(Throw "You must enter a cutoff date!"),
   3:       [Switch]$Recurse,
   4:       [Switch]$hidden,
   5:       [Switch]$force)

If you pass -recurse to the function, then Get-ChildItem will use -recurse. I accomplish this by building a command string for Get-ChildItem, adding parameters as necessary as you can see in lines 14-22.

I thought it might be nice to report some statistics on the number of files deleted and how much space was recovered so at line 25 I create an array which I’ll use to store file information. Now to work. To execute the Get-ChildItem command string I use the $executioncontext’s InvokeCommand() method in line 28. The Get-ChildItem expression is executed and results are piped to Where-Object which filters out folders and keeps files that are older than my cutoff date, which in this script is the time my computer last booted up.

Each file object is then piped to ForEach-Object. On line 32 I add the file object to my $files array. After that the file is deleted using Remove-Item. If -force was passed to the function, then line 36 is executed otherwise line 39 is executed.

After all the files have been deleted, the $files array is piped to Measure-Object at line 43 to calculate the total size of the deleted files. I use the -f operator at line 44 to build a message string. This is a terrific technique and much easier than trying to concatenate objects and properties. The summary message is written to the console using Write-Host

As written, this function and script does not delete any folders so you could end up with a bunch of empty and old folders. I’ll save that project for another day. Finally, because $files contains all the file information you could easily add code to export that to a CSV or XML file or save to a text file. I expect some of you will also come up with enhancements that I haven’t thought of as well.  If you do, I hope you’ll share.

Important: I didn’t want anyone to accidentally delete files so you must remove the -WHATIF parameters for this to actually delete files. Please, please test script and function completely before using it in production.

You can download the script here

 

.