Powershell find folders, delete files leaving latest 5 - powershell

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.

Related

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

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
}

Find the last modified files with different file names in PowerShell

I have a folder full of SQL backups from different DBs. I need to isolate the last modified from each DB and delete the rest.
I can find the last modified of them all and delete the rest but that would delete the last backup from the other 2 DBs.
$files = Get-ChildItem -Path $path -Recurse |
Where-Object {-not $_.PsIsContainer}
$keep = 1
if ($files.Count -gt $keep) {
$files | Sort-Object CreationTime |
Select-Object -First ($files.Count - $keep) |
Remove-Item -Force
}
This will just Keep the newest and remove the rest.
So if I have
db1.bak
db1.bak
db1.bak
db2.bak
db2.bak
db2.bak
db3.bak
db3.bak
db3.bak
how do I keep the last modified of each then delete the rest?
Group the files by name, skip the most recent file from each group, and remove the rest.
Get-ChildItem -Path $path -Recurse |
Where-Object {-not $_.PsIsContainer}
Group-Object Name |
ForEach-Object {
$_.Group |
Sort-Object LastWriteTime -Desc |
Select-Object -Skip 1 |
Remove-Item -Force
}

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 script deleting files despite -Exclude switch

I have the following script where I'm trying to delete all the SQL .bak files except for the last two. When I run it it wipes out everything in the folder. Does -Exclude not work with array values?
$excludefile=get-childitem D:\TempDB | sort lastwritetime | select-object -Last 2 | select-object -Property Name | select-object -expandproperty Name
foreach ($element in $excludefile)
{
$element
remove-item -Path D:\TempDB -Exclude ($element) -Force
}
Is this what you're looking for?
Get-ChildItem D:\TempDB |
Sort-Object LastWriteTime -Descending |
Select-Object -Skip 2 |
Remove-Item -WhatIf
Of course, you can remove -WhatIf if this is what you need.

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 }