I'm writing a custom script to keep our Exchange servers clean. It consists of several parts.
The last part is to clean TEMP folders, and it's working with no problems.
The first part is where my problem is. I want to select all .BAK .TMP and .XML files and delete them if they are over 3 days old, and select and delete all .log files if they are over 30 days old. But no files are being selected.
$Path ="$env:SystemDrive\Program Files (x86)\GFI\MailEssentials\EmailSecurity\DebugLogs\", "$env:SystemDrive\Program Files (x86)\GFI\MailEssentials\AntiSpam\DebugLogs\", "$env:SystemDrive\inetpub\logs", "$env:windir\System32\LogFiles"
# How long do you want to keep files by default?
$Daysback = "3"
# How long do you want to keep .log files? (Recommended 30 days at least)
$DaysbackLog = "30"
$DatetoDelete = (Get-Date).AddDays(-$Daysback)
$DatetoDeleteLog = (Get-Date).AddDays(-$DaysbackLog)
Get-ChildItem $Path -Recurse -Hidden | Where-Object {($_.extension -like ".log" -and $_.LastWriteTime -lt $DatetoDeleteLog)} | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue -WhatIf
Get-ChildItem $Path -Recurse -Hidden | Where-Object {($_.extension -like ".bak", "tmp", "xml" -and $_.LastWriteTime -lt $DatetoDelete)} | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue -WhatIf
# The following lines clears temp folder and empty folders in the temp folder.
Get-ChildItem "$env:windir\Temp", "$env:TEMP" -recurse | Where-Object { $_.LastWriteTime -lt $DatetoDelete } | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue -WhatIf
Get-ChildItem "$env:windir\Temp", "$env:TEMP" -recurse | Where-Object { $_.LastWriteTime -lt $DatetoDelete } | Where {$_.PSIsContainer -and #(Get-ChildItem -LiteralPath:$_.fullname).Count -eq 0} | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue -WhatIf
There are a few ways to do this, but much of it is based on personal preference and/or performance. The latter of which is not likely to be a big design factor here.
$Path = #(
"$env:SystemDrive\Program Files (x86)\GFI\MailEssentials\EmailSecurity\DebugLogs\"
"$env:SystemDrive\Program Files (x86)\GFI\MailEssentials\AntiSpam\DebugLogs\"
"$env:SystemDrive\inetpub\logs"
"$env:windir\System32\LogFiles"
)
# Extensions
$Extensions = "*.bak", "*.tmp", "*.xml"
# Temp folders to clean up
$Temps = "$env:windir\Temp", "$env:TEMP"
# How long do you want to keep files by default?
$Daysback = "3"
# How long do you want to keep .log files? (Recommended 30 days at least)
$DaysbackLog = "30"
$DatetoDelete = (Get-Date).AddDays(-$Daysback)
$DatetoDeleteLog = (Get-Date).AddDays(-$DaysbackLog)
Get-ChildItem $Path -Filter "*.log" -Recurse -Hidden |
Where-Object { $_.LastWriteTime -le $DatetoDeleteLog } |
Remove-Item -Force -ErrorAction SilentlyContinue -WhatIf
# > Move filtering left, which works because you are only looking for a single
# extension.
# > Change to -le to accommodate edge case where $_.LastWriteTime is right on
# the boundary.
$Extensions |
ForEach-Object{
Get-ChildItem $Path -Filter $_ -Recurse -Hidden
} |
Where-Object { $_.LastWriteTime -le $DatetoDelete } |
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue -WhatIf
# Set up extensions as an array of wild card filters.
# -Filter is much faster than -Include which may be another alternative approach
Get-ChildItem $Temps -File -Recurse |
Where-Object { $_.LastWriteTime -le $DatetoDelete } |
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue -WhatIf
Get-ChildItem $Temps -Directory -Recurse |
Where-Object { !$_.GetFileSystemInfos() } |
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue -WhatIf
I haven't tested any of the refactor. However, the approach is to simply rerun the Get-ChildItem cmdlet for each needed scenario. In my experience that's faster than trying to use the -Include parameter to grab all the extensions in 1 shot, while still be faster and easier to read than adding to a Where{} clause to filter on extension.
In the part for clearing the temp folders. I use the .Net Method .GetFileSystemInfos() on the [System.IO.DirectoryInfo] objects returned from Get-ChildItem. The method returns an array of all child objects, so if it's null we know the folder is empty. That sounds complicated, but as you can see it significantly shrinks the code and will likely perform better. I use the -File & -Directory parameters respectively to make sure to make sure I've got the right object types.
This is a little more advanced, but another way I played with to clean up the temp folders is to use a ForEach-Object loop with 2 process blocks.
$Temps |
ForEach-Object -Process {
# 1st process block get Empty directories:
Get-ChildItem -Directory -Recurse |
Where-Object{ !$_.GetFileSystemInfos() }
}, {
# 2nd process block get files older than the boundary date.
Get-ChildItem -File -Recurse |
Where-Object { $_.LastWriteTime -le $DatetoDelete }
} |
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue -WhatIf
Again untested, and I'm not sure how this will preform. Nevertheless, since I developed it thought I'd share.
Note: the -Process argument is necessary so that ForEach-Object assigns both block to process.
Check out ForEach-Object with Multiple Script Blocks for more information.
I'm pretty new to powershell.
I need to learn it though.
I started with a simple script that deletes certain file extensions in different paths, if they are older than 10 days.
$DeleteDate = (Get-Date).AddDays(-10)
$path1 = "\\path1\*.tibx*"
$path2 = "\\path2\*Backup Set*"
$path3 = "\\path3\*.tibx*"
Get-ChildItem $path1 | Where {$_.LastWriteTime -lt "$DeleteDate"} | Remove-Item
Get-ChildItem $path2 -Recurse | Where {$_.LastWriteTime -lt "$DeleteDate"} | Remove-Item
Get-ChildItem $path3 | Where {_.LastWriteTime -lt "$DeleteDate"} | Remove-Item
This works so far. Now what I would also need is, that it is logging, what and when it was deleted.
The only problem about this is, that Remove-Item doesnt seem to have output. Not even with "verbose".
I thought about putting the Items in an array, then write the array before and after the delete in a log file.
But this seems kinda complex for a task this simple.
Do you have any Ideas that could help me?
Insert ForEach-Object in the pipeline(s) just before Remove-Item, then log it there:
Get-ChildItem $path1 | Where {$_.LastWriteTime -lt "$DeleteDate"} | ForEach-Object {
Write-Verbose "Deleting '$($_.FullName)' at [$(Get-Date -Format o)]"
$_
} | Remove-Item
I am currently new at PowerShell, I want to delete .xlsx files older than X days using Powershell.
I tried below
Get-ChildItem D:\temp | ? { $_.PSIsContainer -and $_.LastWriteTime -lt $timeLimit } | Remove-Item -WhatIf
But above command deleted my all data (including C drive data)
Please suggest a modification or a new command. Thanks in Advance.
You do not appear to be filtering for xlsx files in your code. An xlsx file is not a folder so I have removed that condition from your filter.
$timelimit is not defined in the code shown but will need to be of the dateTime type to enable a comparison
Get-ChildItem d:\temp -filter "*.xlsx"| ? { $_.LastWriteTime -lt $timeLimit } | Remove-Item -WhatIf
This should do the trick:
$timeLimit = 60 # Days
Get-ChildItem -Path "D:\temp" -Recurse -Include *.xlsx | Where-Object {$_.LastWriteTime -lt (Get-Date).AddDays(-$timeLimit)} | Remove-Item -WhatIf
If this outputs the expected result, you can remove the -WhatIf switch and give it a try.
I am new to PowerShell and I have created the following code to delete specific files and folders:
$myFolderPath = "C:\Test"
$myLimit = (Get-Date).AddDays(-14)
# Delete files according to filter.
Get-ChildItem -Path $myFolderPath -Recurse -Force | Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $myLimit} | Remove-Item -Force
# Delete empty folders.
Get-ChildItem -Path $myFolderPath -Recurse -Force | Where-Object { $_.PSIsContainer -and (Get-ChildItem -Path $_.FullName -Recurse -Force | Where-Object { !$_.PSIsContainer }) -eq $null } | Remove-Item -Force -Recurse
Is it possible to print out the full path of each item that will be removed to the console before the actual Remove-Item operation will be performed?
I guess sth. has to be added here....
... | Remove-Item -Force
and here...
... | Remove-Item -Force -Recurse
but I cannot find out how to implement that in an elegant way (without code duplication).
You can replace the remove-Item-Parts with
Foreach-Object { $_.fullname; Remove-Item -Path $_.Fullname (-Recurse) -Force}
LotPings comment might be better idea, if that is what you want.
It does not get a lot of attention but Tee-Object could be a simple addition to the pipeline here. Redirect the output to a variable that you can print later.
...Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $myLimit} |
Tee-Object -Variable removed | Remove-Item -Force
$removed | Write-Host
All of the file objects piped will be sent to $removed and then to Remove-Item. Since you have more than one delete pipeline you can also use the -Append parameter so that all files are saved in one variable if you so desired.
However this does not mean they were deleted. Just they made it passed the pipe. If you really wanted to be sure you should be using another utility like robocopy which has logging features.
I have a folder where I need to delete all files and folders except a small list of files and folders.
I can already exclude a list of files, but don't see a way to exclude a folder and its contents.
Here is the folder structure:
|-C:\temp
\-C:\temp\somefile.txt
\-C:\temp\someotherfile.txt
| |-C:\temp\foldertodelete
\-C:\temp\foldertodelete\file1.txt
| |-C:\temp\foldertokeep
| \-C:\temp\foldertokeep\file2.txt
I want to keep somefile.txt and the folder foldertokeep and its content.
This is what I have right now:
Get-ChildItem -Path 'C:\temp' -Recurse -exclude somefile.txt | Remove-Item -force -recurse
This really does not delete somefile.txt. Is there a way to exclude folder foldertokeep and its content from the delete list?
Get-ChildItem -Path 'C:\temp' -Recurse -exclude somefile.txt |
Select -ExpandProperty FullName |
Where {$_ -notlike 'C:\temp\foldertokeep*'} |
sort length -Descending |
Remove-Item -force
The -recurse switch does not work properly on Remove-Item (it will try to delete folders before all the child items in the folder have been deleted). Sorting the fullnames in descending order by length insures than no folder is deleted before all the child items in the folder have been deleted.
In PowerShell 3.0 and below, you can try simply doing this:
Remove-Item -recurse c:\temp\* -exclude somefile.txt,foldertokeep
Unless there's some parameter I'm missing, this seems to be doing the trick...
Edit: see comments below, the behavior of Remove-Item has changed after PS3, this solution doesn't seem applicable anymore.
Select everything excluding what needs to be keep and pipe that to a delete command.
Say you have those folders
C:.
├───delme1
│ │ delme.txt
│ │
│ └───delmetoo
├───delme2
├───keepme1
│ keepmetoo.txt
│
└───keepme2
To delete everything but preserve the keepme1 and keepme2 folders.
Get-ChildItem -Exclude keepme1,keepme2 | Remove-Item -Recurse -Force
Other solutions are fine but I found this easy to understand and to remember.
I used the below and just removed -Recurse from the 1st line and it leaves all file and sub folders under the exclude folder list.
Get-ChildItem -Path "PATH_GOES_HERE" -Exclude "Folder1", "Folder2", "READ ME.txt" | foreach ($_) {
"CLEANING :" + $_.fullname
Remove-Item $_.fullname -Force -Recurse
"CLEANED... :" + $_.fullname
}
Yes I know this is an old thread. I couldn't get any of the answers above to work in Powershell 5, so here is what I figured out:
Get-ChildItem -Path $dir -Exclude 'name_to_ignore' |
ForEach-Object {Remove-Item $_ -Recurse }
This moves the -Recurse to Remove-Item instead of where the items are found.
According to MSDN Remove-Item has a known issue with the -exclude param. Use this variant instead.
Get-ChildItem * -exclude folderToExclude | Remove-Item
I ran into this and found a one line command that works for me. It will delete all the folders and files on the directory in question, while retaining anything on the "excluded" list. It also is silent so it won't return an error if some files are read-only or in-use.
#powershell Remove-item C:\Random\Directory\* -exclude "MySpecialFolder", "MySecondSpecialFolder" -force -erroraction 'silentlycontinue'
This would also help someone...
Adding a variable for PATH_GOES_HERE that is empty or isn't defined prior can cause a recursive deletion in the user directory (or C:\windows\system32 if the script is ran as admin). I found this out the hard way and had to re-install windows.
Try it yourself! (below will only output the file directories into a test.txt)
Get-ChildItem -Path $dir2 -Recurse -Exclude "Folder1 ", FileName.txt | foreach ($_) {
$_.fullname >> C:\temp\test.txt
}
I used this, that works perfectly for me
Get-ChildItem -Path 'C:\Temp\*' -Recurse | Where-Object {($_.FullName -notlike "*windirstat*") -and ($_.FullName -notlike "C:\Temp\GetFolderSizePortable*")} | Remove-Item -Recurse
If your paths include regex special characters then you need to use the -LiteralPath option which does not allow piping. The correct solution in that case looks like this:
Remove-Item -force -LiteralPath(
Get-ChildItem -Path 'C:\temp' -Recurse -exclude somefile.txt |
Select-Object -ExpandProperty FullName |
Where-Object { $_ -notlike 'C:\temp\foldertokeep*' } |
Sort-Object length -Descending
)
This would also help someone...
Get-ChildItem -Path PATH_GOES_HERE -Recurse -Exclude "Folder1 ", "Folder2", FileName.txt | foreach ($_) {
"CLEANING :" + $_.fullname
Remove-Item $_.fullname -Force -Recurse
"CLEANED... :" + $_.fullname
}
I want get contribution for this idea
delete all folder and files include hidden folder
$get-childitem -Path D:\path\folder\to\delete* -Force |select-object -Expandproperty Fullname |remove-item -recurse -Confirm:$false -Force
delete all folder and file include hidden folder but retain exclude folder
$get-childitem -Path D:\path\folder\to\delete* -Exclude nameexludefolder -Force | select-object -Expandproperty Fullname | remove-item -Force
$get-childitem -Path D:\path\folder\to\delete\ -Exclude nameexludefolder -Force | select-object -Expandproperty Fullname | remove-item -Force
first line remain folders,
2nd line remove remain folder