I am trying to write a silent script that deletes files older than 14 days and removes empty folders. The part that deletes files work fine, but the part that deletes folders is popping up a confirmation window no matter what I do to suppress it. Here's my code:
$date=(get-date).AddDays(-14)
$ConfirmPreference="None"
$DebugPreference="SilentlyContinue"
$ErrorActionPreference="SilentlyContinue"
$ProgressPreference="SilentlyContinue"
$VerbosePreference="SilentlyContinue"
$WarningPreference="SilentlyContinue"
$OutputEncoding=[console]::OutputEncoding
function FullNuke ([string] $strPath) {
Get-ChildItem -Path $strPath -Recurse -Force | Where-Object {!$_.PSIsContainer -and $_.LastAccessTime -lt $date} | Remove-Item -Force
#The line below is the one that triggers the confirmation
Get-ChildItem -Path $strPath -Recurse -Force | Where-Object {$_.PSIsContainer -and #(Get-ChildItem -LiteralPath $_.FullName -Recurse -Force | Where-Object {!$_.PSIsContainer}).Length -eq 0} | Remove-Item -Force
}
Most of the answers I have found say to add -Recurse to my final Remove-Item command, but that would be the opposite of what I want. If the folder is empty, I want it removed. If it is not empty, I do not want it removed. I'm not sure why non-empty folders are even being caught in the first place.
UPDATE
After much frustration, I discovered that the second line was processing items in reverse order, thus requiring confirmation. It was also not properly identifying empty folders, which also triggered confirmation. I ended up using the following function.
function FullNuke ([string] $strPath) {
Get-ChildItem -Path $strPath -Recurse -Force | Where-Object {!$_.PSIsContainer} | Where-Object {$_.LastAccessTime -lt $date} | Remove-Item -Force
Get-ChildItem -Path $strPath -Recurse -Force | Where-Object {$_.PSIsContainer} | Where-Object {#(Get-ChildItem -LiteralPath $_.FullName -Recurse -Force).Length -eq 0} | Remove-Item -Force
}
I put it here because while it is a solution (it erases files and folders to my satisfaction), it is not an answer to my posted question.
Remove -Force from first Get-ChildItem and add -Recurse and -Confirm:$false.
This will work:
Get-ChildItem -Path $strPath -Recurse |
Where-Object {$_.PSIsContainer -and
#(Get-ChildItem -LiteralPath $_.FullName -Recurse -Force |
Where-Object {!$_.PSIsContainer}).Length -eq 0} |
Remove-Item -Force -Recurse -Confirm:$false
If you are using v3 or higher powershell client you can use the -directory and -file switches for Get-ChildItem and use this:
function FullNuke ([string] $strPath) {
Get-ChildItem -Path $strPath -Recurse -Force -File | Where-Object {$_.LastAccessTime -lt $date} | Remove-Item -Force
Get-ChildItem -Path $strPath -Recurse -Force -Directory | Where-Object {(gci $_.FullName -File -Recurse -Force).count -eq 0} | Remove-Item -Force -Recurse
}
Yes, I added -Recurse to the folder removal because with my testing no folders with any files in them were being passed to that point, and if it is a folder with nothing but empty folders in it then they all need to go anyway right?
Related
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 tried
Get-ChildItem -Path 'C:\temp' -Recurse Select Name | Where {$_ -notlike 'C:\temp\one*'} | sort length -Descending | Remove-Item -force
but it doesn't work
Get-ChildItem : A positional parameter cannot be found that accepts argument 'Name'
What's wrong
You were missing a |
Get-ChildItem -Path 'C:\temp' -Recurse | Select -ExpandProperty FullName | Where {$_ -notlike 'C:\temp\one*'} | Remove-Item -force
Try this with -Exclude (And why sort when deleting files?)
Get-ChildItem -Path 'C:\temp' -Recurse -Exclude 'C:\temp\one*' | Remove-Item -force
Use the function below:
Function Delete-Except
{
$path = ""
$exceptions = #(
#Enter files/folders to omit#
)
try:
Get-ChildItem $source -Exclude $exceptions| Remove-Item $_ -Force -Recurse
catch:
Write-Host "Delete operation failed." - Foregroundcolor Red
Pause
}
I want to know how to log the actions from this script to a text file because I don't know how to do it as the cmdlet Start-Transcript doesn't work for me and I wasn't able to find a solution on the Internet.
The problem is that the Where-Object cmdlet doesn't output anything captured by Get-ChildItem.
Does anybody has a good idea to solve this?
$limit = (Get-Date).AddDays(-30)
$path = Split-Path -Parent $MyInvocation.MyCommand.Definition
Get-ChildItem -Path $path -Recurse -Force | Where-Object {
!$_.PSIsContainer -and
$_.LastWriteTime -lt $limit
} | Remove-Item -Force
Get-ChildItem -Path $path -Recurse -Force | Where-Object {
$_.PSIsContainer -and
(Get-ChildItem -Path $_.FullName -Recurse -Force | Where-Object {
!$_.PSIsContainer
}) -eq $null
} | Remove-Item -Force -Recurse
try something like this
$limit = (Get-Date).AddDays(-30)
$path =Split-Path -Parent $MyInvocation.MyCommand.Definition
Get-ChildItem $path -file -recurse -force | where LastWriteTime -lt $limit |
Tee-Object -FilePath "c:\temp\deleted.txt" -Append | Remove-Item
Get-ChildItem $path -directory |
where {(Get-ChildItem $_.FullName -file -Recurse | select -First 1) -eq $null} |
Tee-Object -FilePath "c:\temp\deleted.txt" -Append | Remove-Item
howdy error666,
you can use use a few different methods ...
Tee-Object = fork the stream to a file
-PipelineVariable = accumulate the info in a variable
use a loop = put a log-to-file step in it
put a ForEach-Object in the pipeline
that can both log your info and do the Remove-Item.
the loop is the easiest to understand. [grin] however, if you want to keep it in a pipeline, you could add a ForEach-Object where the Where-Object scriptblock is and put both the filter test and the various actions in that block.
take care,
lee
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 the following code to keep on top of old folders which I no longer want to keep
Get-ChildItem -Path $path -Recurse -Force -EA SilentlyContinue|
Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $limit } |
Remove-Item -Force -EA SilentlyContinue
Get-ChildItem -Path $path -Recurse -Force -EA SilentlyContinue|
Where-Object { $_.PSIsContainer -and (Get-ChildItem -Path
$_.FullName -Recurse -Force | Where-Object { !$_.PSIsContainer })
-eq $null } | Remove-Item -Force -Recurse -EA SilentlyContinue
It deletes anything older than a certain number of days ($limit) including files and folders.
However, what I am after is ONLY deleting old folders and their contents.
For example, a day old folder may have file within that is a year old but I want to keep that folder and the old file. The code above keeps the folder but deletes the file. All I want to do is delete folders (and their contents) within the root that are older than the $limit else leave the other folders and content alone.
Thanks in advance.
Well look at this bit:
Get-ChildItem -Path $path -Recurse -Force -EA SilentlyContinue|
Where-Object { !$_.PSIsContainer -and $_.CreationTime -ge $limit } |
Remove-Item -Force -EA SilentlyContinue
It's basically saying "everything not a folder and older than specified is removed". So your first step is to remove that.
The second part is just deleting empty folders, you can keep it as-is or you could add to the Where statement to include the CreationTime:
Get-ChildItem -Path $path -Recurse -Force -EA SilentlyContinue|
Where-Object { $_.PSIsContainer -and $_.CreationTime -lt $limit -and (Get-ChildItem -Path
$_.FullName -Recurse -Force | Where-Object { $_.CreationTime -lt $limit })
-eq $null } | Remove-Item -Force -Recurse -EA SilentlyContinue
The second Where statement returns a list of files and folders newer than $limit, and only deletes the folder if that is null.