I have written the below with the intention of deleting all folders in a directory that have a creation date of 2 days or more and log this in an output file if it is successful or not.
The script works as I would like with the exception that the name of the file will not show in the output file. All that is displayed is 'Deletion of Failed/Successful'
$dump_path = "C:\desktop"
$max_days = "-2"
$curr_date = Get-Date
$del_date = $curr_date.AddDays($max_days)
ForEach-Object {
$filename = $_
Get-ChildItem $statfolder\$_ -Recurse | Where-Object {
$_.CreationTime -lt $del_date
} | Remove-Item -Recurse -Force
if ($? -eq $false) {
echo "$Deletion of $filename Failed" |
Out-File -Append C:\Logs\DELETION_FAIL_K_$(Get-Date -Format `"dd-MMM-yyyy`").txt
} else {
Write-Output "Deletion of $filename Successful" |
Out-File -Append C:\Logs\DELETION_SUCCESS_K_$(Get-Date -format `"dd-MMM-yyyy`").txt
}
}
I would ideally like the log to display the parent folder name and a list of the sub folders in next level down only. Is this possible?
Eg. the log would read
Deletion of folder 12-Jan-2017 containing sub folders R2015, R2086 was Successful
If the sub folders in the next level cannot be added then just the below would be great:
Deletion of folder 12-Jan-2017 was Successful
The way you're using ForEach-Object the current object variable $_ (and consequentially the variable $filename) is never populated. Where would you expect the value to come from?
Feed the output of Get-ChildItem | Where-Object into ForEach-Object, but sort the results by full name first, so that nested folders are deleted before their parents.
Get-ChildItem $statfolder\$_ -Recurse | Where-Object {
$_.PSIsContainer -and
$_.CreationTime -lt $del_date
} | Sort-Object FullName | ForEach-Object {
$folder = $_.FullName
"Deleting folder '$folder'."
Remove-Item $folder -Recurse -Force -WhatIf
}
With the -WhatIf switch present you'll be doing a dry-run, just echoing what would be deleted without actually deleting it. After you verified that everything would work as intended remove the switch and re-run.
Related
this is my first post on this forum. Im a beginner in coding and I need help with one of my very first self coded tools.
I made a small script, which deletes files based on if they are older than date x (lastwritetime). Now to my problem: I want the script also to check for files inside of folders inside of a directory and only delete a folder afterwards if it is truly empty. I cant figure out how to solve the recursion in this problem, seems like the script deletes just the entire folder in relation to the date x. Could anyone tell me please what I missed in this code and help me to create a own recursion to solve the problem or fix the code? Thanks to you all, guys! Here is my code:
I would be glad if someone knows how to make the code work by using a function
$path = Read-Host "please enter your path"
"
"
$timedel = Read-Host "Enter days in the past (e.g -12)"
$dateedit = (Get-Date).AddDays($timedel)
"
"
Get-ChildItem $path -File -Recurse | foreach{ if ($_.LastWriteTime -and !$_.LastAccessTimeUtc -le $dateedit) {
Write-Output "older as $timedel days: ($_)" } }
"
"
pause
Get-ChildItem -Path $path -Force -Recurse | Where-Object { $_.PsisContainer -and $_.LastWriteTime -le $dateedit } | Remove-Item -Force -Recurse
""
Write-Output "Files deleted"
param(
[IO.DirectoryInfo]$targetTolder = "d:\tmp",
[DateTime]$dateTimeX = "2020-11-15 00:00:00"
)
Get-ChildItem $targetTolder -Directory -Recurse | Sort-Object {$_.FullName} -Descending | ForEach-Object {
Get-ChildItem $_ -File | Where-Object {$_.LastWriteTime -lt $dateTimeX} | Remove-Item -Force
if ((Get-ChildItem $_).Count -eq 0){Remove-Item $_ -Force}
}
remove -WhatIf after test
To also remove folders that are older than the set days in the past if they are empty leaves you with the problem that as soon as a file is removed from such a folder, the LastWriteTime of the folder is set to that moment in time.
This means you should get a list of older folders first, before you start deleting older files and use that list afterwards to also remove these folders if they are empty.
Also, a minimal check on user input from Read-Host should be done. (i.e. the path must exist and the number of days must be convertable to an integer number. For the latter I chose to simply cast it to [int] because if that fails, the code would generate an execption anyway.
Try something like
$path = Read-Host "please enter your path"
# test the user input
if (-not (Test-Path -Path $path -PathType Container)) {
Write-Error "The path $path does not exist!"
}
else {
$timedel = Read-Host "Enter days in the past (e.g -12)"
# convert to int and make sure it is a negative value
$timedel = -[Math]::Abs([int]$timedel)
$dateedit = (Get-Date).AddDays($timedel).Date # .Date sets this date to midnight (00:00:00)
# get a list of all folders (FullNames only)that have a LastWriteTime older than the set date.
# we check this list later to see if any of the folders are empty and if so, delete them.
$folders = (Get-ChildItem -Path $path -Directory -Recurse | Where-Object { $_.LastWriteTime -le $dateedit }).FullName
# get a list of files to remove
Get-ChildItem -Path $path -File -Recurse | Where-Object { $_.LastWriteTime -le $dateedit} | ForEach-Object {
Write-Host "older as $timedel days: $($_.FullName)"
$_ | Remove-Item -Force -WhatIf # see below about the -WhatIf safety switch
}
# now that old files are gone, test the folder list we got earlier and remove any if empty
$folders | ForEach-Object {
if ((Get-ChildItem -Path $_ -Force).Count -eq 0) {
Write-Host "Deleting empty folder: $_"
$_ | Remove-Item -Force -WhatIf # see below about the -WhatIf safety switch
}
}
Write-Host "All Done!" -ForegroundColor Green
}
The -WhatIf switch used on Remove-Item is there for your own safety. With that, no file or folder is actually deleted, instead in the console it is written what would be deleted. If you are satisfied that this is all good, remove the -WhatIf and run the code again to really delete the files and folders
try something like this:
$timedel=-12
#remove old files
Get-ChildItem "C:\temp" -Recurse -File | Where LastWriteTime -lt (Get-Date).AddDays($timedel) | Remove-Item -Force
#remove directory without file
Get-ChildItem "C:\temp\" -Recurse -Directory | where {(Get-ChildItem $_.FullName -Recurse -File).count -eq 0} | Remove-Item -Force -recurse
I'm trying to build a script that I can use to delete old files based on Last Accessed date. As part of the script I want to interrogate each sub folder, find files not accessed in the last X days, create a log in the same folder of the files found and record file details in the log then delete the files.
What I think I need is a nested loop, loop 1 will get each subfolder (Get-ChildItem -Directory -Recurse) then for each folder found a second loop checks all files for last accessed date and if outside the limit will append the file details to a logfile in the folder (for user reference) and also to a master logfile (for IT Admin)
loop 1 is working as expected and getting the subfolders, but I cannot get the inner loop to recurse through the objects in the folder, I'm trying to use Get-ChildItem inside the first loop, is this the correct approach?
Code sample below, I have added pseudo to demo the logic, its really the loops I need help with:
# Set variables
$FolderPath = "E:TEST_G"
$ArchiveLimit = 7
$ArchiveDate = (Get-Date).AddDays(-$ArchiveLimit)
$MasterLogFile = "C:\Temp\ArchiveLog $(Get-Date -f yyyy-MM-dd).csv"
# Loop 1 - Iterate through each subfolder of $FolderPath
Get-ChildItem -Path $FolderPath -Directory -Recurse | ForEach-Object {
# Loop 2 - Check each file in the Subfolder and if Last Access is past
# $ArchiveDate take Action
Get-ChildItem -Path $_.DirectoryName | where {
$_.LastAccessTime -le $ArchiveDate
} | ForEach-Object {
# Check if FolderLogFile Exists, if not create it
# Append file details to folder Log
# Append File & Folder Details to Master Log
}
}
I think you're overcomplicating a bit:
#Set Variables
$FolderPath = "E:\TEST_G"
$ArchiveLimit = 7
$ArchiveDate = (Get-Date).AddDays(-$ArchiveLimit)
$MasterLogFile = "C:\Temp\ArchiveLog $(get-date -f yyyy-MM-dd).csv"
If (!(Test-Path $MasterLogFile)) {New-Item $MasterLogFile -Force}
Get-ChildItem -Path $FolderPath -File -Recurse |
Where-Object { $_.LastAccessTime -lt $ArchiveDate -and
$_.Extension -ne '.log' } |
ForEach-Object {
$FolderLogFile = Join-Path $_.DirectoryName 'name.log'
Add-Content -Value "details" -Path $FolderLogFile,$MasterLogFile
Try {
Remove-Item $_ -Force -EA Stop
} Catch {
Add-Content -Value "Unable to delete item! [$($_.Exception.GetType().FullName)] $($_.Exception.Message)"`
-Path $FolderLogFile,$MasterLogFile
}
}
Edit:
Multiple recursive loops are unnecessary since you're already taking a recursive action in the pipeline. It's powerful enough to do the processing without having to take extra action. Add-Content from the other answer is an excellent solution over Out-File as well, so I replaced mine.
One note, though, Add-Content's -Force flag does not create the folder structure like New-Item's will. That is the reason for the line under the $MasterLogFile declaration.
Your nested loop doesn't need recursion (the outer loop already takes care of that). Just process the files in each folder (make sure you exclude the folder log):
Get-ChildItem -Path $FolderPath -Directory -Recurse | ForEach-Object {
$FolderLogFile = Join-Path $_.DirectoryName 'FolderLog.log'
Get-ChildItem -Path $_.DirectoryName -File | Where-Object {
$_.LastAccessTime -le $ArchiveDate -and
$_.FullName -ne $FolderLogFile
} | ForEach-Object {
'file details' | Add-Content $FolderLogFile
'file and folder details' | Add-Content $MasterLogFile
Remove-Item $_.FullName -Force
}
}
You don't need to test for the existence of the folder log file, because Add-Content will automatically create it if it's missing.
I wrote a powershell script which will iterate through three different path and get list of files that are less then 7 years and delete them from current timestamp.
I am getting creation year of file and if am able to recursively iterate through all those three path.
Problem is out of 3, two paths have too many folders and files due to which when script is in loop it shows memory exception. Also I will not be able to set maxmemorypershellMB, since I don't have access.
Anything else that I can do this to avoid memory exception
this is piece of code below:
$files = Get-ChildItem "$path" –Recurse -file
for ($i=0; $i -lt $files.Count; $i++) {
$outfile = $files[$i].FullName #file name
$FileDate = (Get-ChildItem $outfile).CreationTime #get creation date of file
$creationYear = $FileDate.Year
$creationMonth =$FileDate.Month #get only year out of creation date
If( $creationYear -lt $purgeYear ){
If (Test-Path $outfile){ #check if file exist then only proceed
$text=[string]$creationYear+" "+$outfile
$text >> 'listOfFilesToBeDeleted_PROD.txt' #this will get list of files to be deleted
#remove-item $outfile
}
}
}
You could try to filter the the files using where-object instead of a for loop:
$limit = (Get-Date).AddYears(-7)
$path = "c:\"
$outfile = "c:\test.txt"
Get-ChildItem -Path "$path" -Recurse -file |
Where-Object { $_.CreationTime -lt $limit } |
foreach { '{0} {1}' -f $_.CreationTime, $_.FullName |
Out-File -FilePath $outfile -Append }
Solution for your comment:
# retrieve all affected files and select the fullname and the creationtime
$affectedFiles = Get-ChildItem -Path "$path" -Recurse -file |
Where-Object { $_.CreationTime.Year -lt $purgeYear } |
select FullName, CreationTime
foreach ($file in $affectedFiles)
{
# write the file to listOfFilesToBeDeleted
'{0} {1}' -f $file.CreationTime.Year, $file.FullName |
Out-File -FilePath listOfFilesToBeDeleted.txt -Append
# delete the file
Remove-Item -Path $file.FullName -Force
}
I'm trying to write a script that deletes all folders that are older than 60 days and create a logfile with the folder names in the directory where the folders have been deleted.
What I have now is:
Get-ChildItem -Directory -Path "\\share\dir1\dir2" |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-10) } |
Remove-Item -Force -Recurse | Out-File Auto_Clean.log -Append -WhatIf
The output stays like this for ages:
What if: Performing the operation "Output to File" on target "C:\users\bgijbels\Downloads\Auto_Clean.log".
When I remove the part for Out-File it works fine. It seems like the Out-File part is trying to write the name of every file in the folder to the log, while I only need to have the folder name. I think that's why it takes so long, if at all it gets past the part of creating the logfile. Any ideas? Thank you for your help.
You are getting a list of all files because -Recurse switch enumerates contents of folders so it can be deleted prior to the root folder removal. Try this:
Get-ChildItem -Directory -Path "\\share\dir1\dir2" |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-60) } | % {
$folder = $_;
Remove-Item $folder.FullName -Force -Recurse | Out-Null
$folder.FullName } |
Out-File Auto_Clean.log -Append -WhatIf
Directory object is kept as $folder var and you effectively echo its full path after deletion. Obviously take -WhatIf off the end after you are happy with results.
I have a script right now that looks for all files certain day old and certain file extension and it deletes all of the files. This works fine and it counts fine
Then I have to delete all folders that correspond to being empty and that includes all sub folders too.
I also have to output this into a file and display each file deleted. The output would show 30 folders deleted but actually 48 were really deleted.
Now my question is i am trying to do a count of all the folders deleted. I have this script but it just counts the deepest folders not all the ones deleted.
Here is the part of the script i can not get to count
$TargetFolder = "C:\Users\user\Desktop\temp"
$LogFile = "C:\Summary.txt"
$Count = 0
Date | Out-File -filepath $LogFile
get-childitem $TargetFolder -recurse -force | Where-Object {$_.psIsContainer}| sort fullName -des |
Where-Object {!(get-childitem $_.fullName -force)} | ForEach-Object{$Count++; $_.fullName} | remove-item -whatif | Out-File -filepath $LogFile -append
$Count = "Total Folders = " + $Count
$Count | Out-File -filepath $LogFile -append
Although the sort call will correctly send each directory through the pipeline in nesting order, since they are not really being removed (remove-item -whatif), the parents will still contain their empty child directories and so will not pass the second condition (!(get-childitem $_.fullName -force)). Also note that Remove-Item does not produce any output, so the deleted directories will not appear in the log.
Adapting Keith Hill's answer to a similar question, here is a modified version of the original script that uses a filter to retrieve all empty directories first, then removes and logs each one:
filter Where-Empty {
$children = #($_ |
Get-ChildItem -Recurse -Force |
Where-Object { -not $_.PSIsContainer })
if( $_.PSIsContainer -and $children.Length -eq 0 ) {
$_
}
}
$emptyDirectories = #(
Get-ChildItem $TargetFolder -Recurse -Force |
Where-Empty |
Sort-Object -Property FullName -Descending)
$emptyDirectories | ForEach-Object {
$_ | Remove-Item -WhatIf -Recurse
$_.FullName | Out-File -FilePath $LogFile -Append
}
$Count = $emptyDirectories.Count
"Total Folders = $Count" | Out-File -FilePath $LogFile -Append
Note that -Recurse was added to the call to Remove-Item, as empty child directories will remain when using -WhatIf. Neither flag should be needed when performing an actual remove on an empty directory.
Not tested:
get-childitem $TargetFolder -recurse -force |
where-object{$_.psiscontainer -and -not (get-childitem $_.fullname -recurse -force | where-object {!($_.psiscontainer)}}|
sort fullName -des |
Where-Object {!(get-childitem $.fullName -force)} |
ForEach-Object{$Count++; $_.fullName} |
remove-item -whatif |
Out-File -filepath $LogFile -append