How to delete all but the most recent files in a directory of folders and files - powershell

I have a folder that has a bunch of backups in in separated by folder. I want a script to use the directory (C:\Users\user\Desktop\TEST) and in that directory I have any number of folders with any number of files in them, I only want to keep the latest in the folder for every folder in the directory and delete the rest.
I have this but it only does 1 folder at a time and it has to be hardcoded.
$path = "C:\Users\user\Desktop\TEST\Folder1"
$FileNumber = (get-childitem $path).count - 1
get-childitem -path $path | sort CreationTime -Descending | select -last $FileNumber | Remove-Item -Force -WhatIf
Is there any way to automate this?
Thanks,

You can try this:
$Path = "C:\Users\user\Desktop\TEST"
$Folders = Get-ChildItem $Path
foreach ($Folder in $Folders)
{
$FolderName = $Folder.FullName
$Files = Get-ChildItem -Path $FolderName
$FileNumber = $Files.Count - 1
$Files | sort CreationTime -Descending | select -last $FileNumber | Remove-Item -Force -WhatIf
}

You would need a loop of your choice to accomplish this, this example uses ForEach-Object. Instead of using Select-Object -Last N you could just use Select-Object -Skip 1 to skip the newest file. Also note the use of -Directory and -File with Get-ChildItem to filter only directories or only files.
$path = "C:\Users\user\Desktop\TEST\Folder1"
# Get all Directories in `$path`
Get-ChildItem $path -Directory | ForEach-Object {
# For each Directory, get the Files
Get-ChildItem $_.FullName -File |
# Sort them from Newest to Oldest
Sort-Object CreationTime -Descending |
# Skip the first 1 (the newest)
Select-Object -Skip 1 |
# Remove the rest
Remove-Item -Force -WhatIf
}

Related

Powershell: Move all files except most recently modified?

Trying to simplify one of my archiving tasks, but I'm stumped on how to go about it. Basically, I just want Powershell to search a folder for files, and move all but the most recently modified (by LastWriteTime) to a backup folder.
I've searched around for solutions to this but every answer I've come across looks for the oldest file or depends on a specific file-naming convention to work.
Basically I want it to look at this this:
E:\ProjectFolder1\EDLs\File1.prproj (modified six days ago)
E:\ProjectFolder1\EDLs\File2.prproj (modified six hours ago)
E:\ProjectFolder1\EDLs\File3.prproj (modified six seconds ago)
Identify File3.prproj as the one that's most up-to-date, and move all the other files in the directory to another folder:
E:\Deep Storage\ProjectFolder1\EDLs\File1.prproj
E:\Deep Storage\ProjectFolder1\EDLs\File2.prproj
I know how to do everything except get it to compare the LastWriteTimes. Is there a way do get PS to do this?
EDIT with code sample
Get-ChildItem $sourceDir -Include $search -Recurse | Sort-Object LastWriteTime -Descending | Select-Object -Skip 1 | `
foreach{
$targetFile = $targetDir + $_.FullName.SubString($sourceDir.Length);
New-Item -ItemType File -Path $targetFile -Force;
Move-Item $_.FullName -destination $targetFile -Force
}
EDIT with functional code:
$sourceDir = "E:\Test1\EDLs\"
$targetDir = "E:\Deep Storage\Test1\EDLs\"
$search = "*.prproj"
Get-ChildItem $sourceDir -Recurse -Directory | ForEach-Object {
$files = $_ | Get-ChildItem -File -Filter $search
if($files.Count -lt 2) {
return
}
$newPath = Join-Path $targetDir -ChildPath $_.FullName.Substring($sourceDir.Length)
$null = New-Item $newPath -ItemType Directory -Force
$files | Sort-Object LastWriteTime -Descending | Select-Object -Skip 1 |
Move-Item -Destination $newPath -Verbose -WhatIf
}
EDIT to show actual syntax for operating environment:
$sourceDir = "E:\Projects\Current\EDLs"
$targetDir = "E:\Deep Storage\Projects\Current\EDLs"
$search = "*.prproj"
Get-ChildItem $sourceDir -Directory | ForEach-Object {
# search only for files only 1 level under this folder
$files = Get-ChildItem $sourceDir -Filter *.prproj
# if there are at least 2 files here
if($files.Count -ge 2) {
# we dont need to create new folder here since these will go directly under
# destination folder so, we can just sort and skip first as in previous logic
$files | Sort-Object LastWriteTime -Descending | Select-Object -Skip 1 |
# then move them
Move-Item -Destination $targetDir
}
Ultimately the answer was a lot simpler than I thought it would be:
$sourceDir="E:\Test1\Test2"
# Where your files are
$targetDir="E:\Deep Storage\Test1\Test2"
# Where you want to send them
$search="*.ext"
# If applicable, what type of file you want to look for
Get-ChildItem -Path $sourceDir -Filter $search | Sort-Object |
Select-Object -SkipLast 1 | Move-Item -Destination $targetDir -Verbose -WhatIf
I use environment variables for my workflow so mine looks a little different, but this should be useful for anyone in the same situation.

Delete X files in each folders and sub folders

I want to delete files in a folder and its subfolders but not X last files again in each folder. For example in C:\Test I want to keep 10 txt files but also in C:\Test\subtest1 and C:\Test\subtest2. Here, X X is the number of files I want to keep.
But it will not keep 10 files in each folder but in the entire arborescence.
My script is not fully functional, but it did it well in the C:\Test directory:
Set-ExecutionPolicy unrestricted $txtfiles = 'C:\Users\MUC8VX\Documents\Test
ForEach-Object { Get-ChildItem $txtfiles | where{ -not $_.PsIsContainer}|
sort LastWriteTime -desc| select -Skip 10 |
Remove-Item -Include *.txt* -Force }
And if I add -Recurse it will not keep 10 files in each folders but in the entire arborescence:
Set-ExecutionPolicy unrestricted
$txtfiles =`C:\Users\MUC8VX\Documents\Test'
ForEach-Object { Get-ChildItem $txtfiles -Recurse | where{ -not
$_.PsIsContainer}| sort LastWriteTime -desc|
select -Skip 10 | Remove-Item -Include *.txt* -Force }
edit :
Thanks a lot Manuel Batsching.
Here's a generic script :
#Insert your parent folder which contains files and subfolders
Get-Childitem -Path C:\Your\Parent\Folder -Recurse |
Group-Object Directory |
ForEach-Object {
$_.Group |
#sort by the last written files
Sort-Object LastWriteTime -Descending |
#Keep the X last files you want to keep
Select-Object -Skip X |
#Remove files according to their extension
Remove-Item -Include *.extension* -Force }
I haven't tested but you can try this.
Set-ExecutionPolicy unrestricted
$txtfiles = 'C:\Users\MUC8VX\Documents\Test'
Get-ChildItem -Directory -Recurse -Path $txtfiles | ForEach-Object {
Get-ChildItem -Path $_.fullname -Include *.txt | Sort-Object LastWriteTime -Descending | Select-Object -Skip 10 | Remove-Item -Force
}
Basically, this code iterate over each folder recursively, for each folder, it find the files, skip 10 and remove all others.
If you are looking for a solution, that also applies to nested subfolders, you can list all files recursively and then group them by the path of their respective parent folder. Then apply your sorting rules and reduce each group to 10 elements.
Get-Childitem *.txt -Recurse |
Group-Object Directory |
ForEach-Object {
$_.Group |
Sort-Object LastWriteTime -Descending |
Select-Object -Skip 10 |
Remove-Item }

Find the oldest file in each subdirectory with Powershell

My company recently moved to outlook365. We are entirely VDI based so our user profiles are stored on a single server. As a result our users all now have 2+ .ost files taking up storage space on the server. I'd like to write a script to find and delete the extraneous .ost files. In addition I'd like to schedule the script to run on a monthly basis to clean up any orphaned .ost's that occur for any other reason.
I've tried a few different solutions but can't seem to find the right syntax to identify just the oldest/original .ost in each subdirectory, all attempts have identified the oldest file from the whole directory or all .ost files in the directory.
$Path = "<path>"
$SubFolders = dir $Path -Recurse | Where-Object {$_.PSIsContainer} | ForEach-Object -Process {$_.FullName}
ForEach ($Folder in $SubFolders)
{
$FullFileName = dir $Folder | Where-Object {!$_.PSIsContainer} | Sort-Object {$_.LastWriteTime} -Descending | Select-Object -First 1
}
Inside of your loop, you could use the following to list the .ost file that has the oldest LastWriteTime value. Just add the -Descending flag to Sort-Object to list the newest file.
$FullFileName = foreach ($folder in $Subfolders) {
$Get-ChildItem -Path $folder -Recurse -File -Filter "*.ost" |
Sort-Object -Property LastWriteTime |
Select-Object -Property FullName -First 1
}
$FullFileName
If there is only one .ost file found in the $folder path, it will still find that file. So you will need logic to not delete when there is only one file. This does not guarantee it is the oldest file. You probably want a combination of the oldest CreationTime and newest LastWriteTime. The following will list the oldest .ost file based on CreationTime.
$FullFileName = foreach ($folder in $Subfolders) {
Get-ChildItem -Path $folder -Recurse -File -Filter "*.ost" |
Sort-Object -Property CreationTime |
Select-Object -Property FullName -First 1
}
$FullFileName
Another issue is setting the $FullFileName variable inside of the foreach loop. This means it will be overwritten through each loop iteration. Therefore, if you retrieve the value after the loop completes, it will only have the last value found. Setting the variable to be the result of the foreach loop output will create an array with multiple values.
To only output an OST file path when there are multiple OST files, you can do something like the following:
$FullFileName = foreach ($folder in $Subfolders) {
$files = Get-ChildItem -Path $folder -Recurse -File -Filter "*.ost" |
Sort-Object -Property LastWriteTime -Descending
if ($files.count -ge 2) {
$files | Select-Object -Property FullName -First 1
}
$FullFileName
This one liner should do the job, keeping the ost file with the newest LastWriteTime
gci -Path $Path -directory | where {(gci -Path $_\*.ost).count -gt 1}|%{gci -Path $_\*.cmd|Sort-Object LastWriteTime -Descending|Select-Object -Skip 1|Remove-Item -WhatIf}
Longer variant follows.
$Path = '<path>'
$Ext = '*.ost'
Get-ChildItem -Path $Path -directory -Recurse |
Where-Object {(Get-ChildItem -Path "$_\$Ext" -File -EA 0).Count -gt 1} |
ForEach-Object {
Get-ChildItem -Path "$_\$Ext" -File -EA 0| Sort-Object LastWriteTime -Descending |
Select-Object -Skip 1 | Remove-Item -WhatIf
}
The first two lines evaluate folders with more than one .ost file
The next lines iterates those folders and sort them descending by LastWriteTime, skips the first (newest) and pipes the other to Remove-Item with the -WhatIf parameter to only show what would be deleted while testing.
You can of course also move them to a backup location instead.

Powershell Delete Files but keep last x version

I have a folder structure with, for example, 100 folders. Each folder has 200 files in it.
I would like to delete (via scheduled task) all files in each folder but keep the last 10 versions of it.
I am trying to upskill in Powershell so I am guessing that this should be pretty simple. I have created this script,
#Delete all files, keep last 10 versions#
$Directory = "D:\Octopus\Packages"
$Keep = "10"
Get-ChildItem $Directory| ?{ $_.PSIsContainer } | Select-Object FullName | Export-Csv $Directory\FolderList.csv
$FolderList = import-csv $Directory\FolderList.csv
ForEach ($row in $FolderList)
{
Get-ChildItem -Recurse | where{-not $_.PsIsContainer}| sort CreationTime -desc | select -Skip $Keep | Remove-Item -Force
}
It appears to be looping through each folder, but keeping the last 10 files for the entire folder structure, not per folder. So some folders have 0 files, some may have 2 files, some may have 8 files.
Any pointers would be appreciated
Thanks !
If you actually need to have that CSV then just modify Get-ChildItem -Recurse to Get-ChildItem $row -recurse. However, if you don't need to be creating the CSV, you can remove of that and just pipe the results of your first Get-ChildItem into the next action.
$Directory = "D:\Octopus\Packages"
$Keep = "10"
Get-ChildItem $Directory| ?{ $_.PSIsContainer } | Select-Object FullName |
ForEach-object {Get-ChildItem $_.fullname -Recurse |
where{-not $_.PsIsContainer}| sort CreationTime -desc |
select -Skip $Keep | Remove-Item -Force }

Powershell find folders, delete files leaving latest 5

We use software called Revit, files are saved as such: filename.rvt
Each time a user edits a file, Revit takes it upon itself to save the old file in the format filename.xxxx.rvt (where xxx is a number).
Over time when files are edited hundreds of times, we have many unnecessary files on the file server.
I am writing a script to:
Locate and folders containing Revit backup files
Delete all but the most recently modified 5 revit backup files
I have tried two approaches below
$searchpath = "e:\"
# Find a unique list of directories that contains a revit backup file (*.*.rvt)
$a = Get-ChildItem -Path $searchpath -Include *.*.rvt -Recurse | Select-object Directory -expandproperty FullName | Get-Unique -AsString
# For each folder that contains a single revit backup file (*.*.rvt)...
# - Sort by modified time
# - Select all except first 5
$a | Get-ChildItem -Include *.*.rvt | Sort-Object LastWriteTime -descending | select-object -skip 5 -property Directory,Name,CreationTime,LastWriteTime | Out-GridView -Title "Old Backups" -PassThru
The issue with this approach is that it only "skips" the first 5 files in the entire search result, not 5 in each folder.
Then I went about it using a loop, and this gets nowhere:
$searchpath = "e:\"
# Find a unique list of directories that contains a revit backup file (*.*.rvt)
$a = Get-ChildItem -Path $searchpath -Include *.*.rvt -Recurse | Select Directory | Get-Unique -AsString
# For each folder that contains a single revit backup file (*.*.rvt)...
# - Sort by modified time
# - Select all except first 5
$a | foreach {
$b += Get-ChildItem -Path $_.Directory.FullName -Include *.*.rvt | Sort-Object LastWriteTime -descending | select-object -skip 5 -property Directory,Name,CreationTime,LastWriteTime
}
$b | Out-GridView -Title "Old Backups" -PassThru
Any thoughts on the correct approach and whats going wrong?
try this:
get-childitem -file -recurse | group Directory | where Count -gt 5 | %{
$_.Group | Sort LastWriteTime -descending | select -skip 5 Directory,Name,CreationTime,LastWriteTime
} | Out-GridView -Title "Old Backups"
If you want delete you can do it (remove what if)
gci -file -recurse | group Directory | where Count -gt 5 | %{
$_.Group | Sort LastWriteTime -descending | select -skip 5 | remove-item -WhatIf
}
The key to do what you seek is to use the Group-Object cmdlet.
In your case, the group you want to create is a group containing all items in the same folder. This will give you something like this:
From there, you can perform actions on each group, such as selecting all the files while skipping the first 5 of each folders and deleting the remaining.
See this simple minimalist example:
$Path = 'C:\__TMP\1'
$Items = Get-ChildItem -Path "$path\*.rvt" -Recurse | Group-Object -Property PsparentPath
Foreach ($ItemsGroup in $Items) {
$SortedFiles = $ItemsGroup.Group | sort LastWriteTime -Descending
$SortedFiles | Select-Object -Skip 5 | % {Write-host "Deleting $($_.FullName)"; Remove-Item $_.FullName}
}
Try something like this:
$searchpath = "E:\"
$number = 5
$directories = Get-ChildItem -Path $searchpath -Include *.*.rvt -Recurse | Where-Object {$_.PsIsContainer}
foreach ($dir in $directories)
{
$files = Get-ChildItem -Path $dir.FullName | Where-Object {-not $_.PsIsContainer}
if ($files.Count -gt $number)
{
$files | Sort-Object CreationTime | Select-Object -First ($files.Count - $number) | Remove-Item -Force
}
}
Change the placeholders accordingly. I just gave you the logical approach.
An alternative solution that doesn't require grouping first and instead processes each directory separately:
& { Get-Item $path; Get-ChildItem -Directory -Recurse $path } | # get all dirs.
ForEach-Object { # for each dir.
Get-ChildItem -File $_.FullName/*.*.rvt | # get backup files in dir.
Sort-Object -Descending LastWriteTime | # sort by last-write time, newest first
Select-Object -Skip 5 | # skip the 5 newest
Remove-Item -Force -WhatIf # delete
}
Note: The -WhatIf common parameter in the command above previews the operation. Remove -WhatIf once you're sure the operation will do what you want.