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.
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 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 wondering if there is better way to make a script on PowerShell these instructions:
Search on 3 paths. Ex.
$LOGDIRS="C:\NETiKA\GED\Production\RI\log";"C:\NETiKA\GED\Test\RI\log";"C:\NETiKA\Tomcat-8.0.28\logs"
Find all files that are older than 7 days and copy on a file that I will call file.list . EX. > C:\Test\file.list
When I copied on my file.list, I need to search all the name of the files and delete them.
Apparently when you have more than thousands of file, this is the
fastest way to delete.
$LOGDIRS=C:/NETiKA/GED/Production/RI/log;C:/NETiKA/GED/Test/RI/log;C:/NETiKA/Tomcat-8.0.28/logs
$KEEP=-7
Get-ChildItem -Path $LOGDIRS -Recurse -Directory -Force -ErrorAction SilentlyContinue |
Select-Object FullName > files.list |
Foreach-Object {
if ($_.LastAccessTime -le (get-date).adddays($KEEP)) {
remove-item -recurse -force $_
}
};
Something like this should help you get started.
$path1 = "E:\Code\powershell\myPS\2018\Jun"
$path2 = "E:\Code\powershell\myPS\2018\Jun\compareTextFiles"
$path3 = "E:\Code\powershell\myPS\2018\May"
$allFiles = dir $path1, $path2, $path3 -File
$fileList = New-Item -type file file.list -Force
$keep = -7
$allFiles | foreach {
if ($_.LastAccessTime -le (Get-Date).AddDays($keep)) {
"$($_.FullName) is older than 7 days"
$_.FullName.ToString() | Out-File $fileList -Append
}
else {
"$($_.FullName) is new"
}
}
You can add deletion in the code in IF Block if you wish or check the file and do it later on. Your code has many issues which are very basic to PowerShell, e.g: once you use Select-Object the next pipeline will only receive the property you selected. You have tried using LastAccessTime in later pipe when you only selected to go ahead with FullName property.
Also, redirecting to a file and again using pipeline looks very messy.
Remove-Item accepts piped input and a
Where will filter the age
to first check what would be deleted I appended a -WhatIf to the Remove-Item
$LOGDIRS="C:\NETiKA\GED\Production\RI\log","C:\NETiKA\GED\Test\RI\log","C:\NETiKA\Tomcat-8.0.28\logs"
$KEEP=-7
Get-ChildItem -Path $LOGDIRS -Recurse -Directory -Force -ErrorAction SilentlyContinue |
Where-Object LastAccessTime -le ((get-date).AddDays($KEEP))
Remove-Item -Recurse -Force $_ -Whatif
Am trying to delete files older than x days and would like to know which file is being deleted.
Am using below powershell script, it doesnt work
$limit = (Get-Date).AddDays(-365)
$path = $args[0]
# Delete files older than the $limit.
Get-ChildItem -Path $path -Recurse -Force | Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $limit } | Remove-Item -Force | select Name,LastWriteTime | Export-CSV -NoTypeInformation -Path $args[1]
Am passing first argument as path where files are there.
Second argument is the output file which should contain the file and date modified values of those which gets deleted.
The above code works fine for deletion, but doesnt redirects the file names and the last modified values which got deleted.
If I use below code, it only redirects the file names and last modified values but files doesnt get deleted.
Get-ChildItem -Path $path -Recurse -Force | Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $limit } | select Name,LastWriteTime | Export-CSV -NoTypeInformation -Path $args[1] | Remove-Item -Force
Using below command to run it -
./OlderFiles_Cleansing.ps1 'C:\Dev\PS' 'C:\dev\CleanedFiles_01062016.csv'
What am I missing?
Neither the Export-Csv nor the Remove-Item Cmdlet return the collection you pipe in and so make it impossible to work with the items further in the pipeline.
You can do following though - split the command:
$filesToDelete = Get-ChildItem -Path $path -Recurse -Force -Attributes !Directory | Where-Object CreationTime -lt $limit
$filesToDelete | select Name,LastWriteTime | Export-CSV -NoTypeInformation -Path $args[1]
$filesToDelete | Remove-Item -Force
Note I have improved the way of detecting that an item is a file using the
Attributes param and so could simplify the Where pipe part
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?