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
.
Good article.
In the first example:
. line 3, I think this should read:
#-hidden will use the -force parameter for Get-ChildItem
. nice touch on lines 35-36! 🙂
. lines 51-53 – I would have written this as:
$os = gwmi win32_operatingsystem
$lbt = $os.converttodatetime($os.lastbootuptime)
. not sure what the last line of code is – it looks like it’s passing -force, etc no matter what the original caller says?
Thanks a lot for this script – it’s very useful!!
Thomas
I was being deliberately “tricky” with the switches since both Get-ChildItem and Remove-item use -force but they actually mean different things in my mind. So I came up with -hidden as an alias if you will when using -force with Get-ChildItem and -force with Remove-Item to “force” deletion. This all made more sense to me.
Using $os instead of my variable is probably more meaningful. I guess I was tired of that variable when you write about the same code over and over.
Line 55 is calling the function passing all the parameters. In effect,if you download the script and run it, all files in %TEMP% will be deleted, recursively and forcibly. Users may want to modify the script or just use the function in their own scripts. But thanks for the comments. Always helpful.