# Powershell script to delete oldest files (not relative to current date)

I DO NOT want to "delete files older than X days." If I wanted to do this, I'd just pick one of the thousands of scripts out there that have already been written for that purpose, and I wouldn't bother asking such a trivial question on ServerFault.

I DO want to delete all but the most recent N files in a particular folder.

Background: a remote system is configured to dump a periodic archive files to a share on a Windows server. These files have a naming convention of YYYYMMDD-hhmm.ext. Should the archive process fail, I don't want the purge script to delete the files just because they are older than so-many-days. There is no way to configure the remote system to purge the files as part of the archive process.

So for example, when the script runs there may be 10 files, there may be 7 files, there may be 5 files in the folder. I need to always keep at least the most recent 5, regardless of how old they are.

Can this be done with PowerShell?

EDIT: it would be desirable to ignore files or folders not matching the naming convention. Other files and folders shouldn't be deleted, nor should they confuse the script (resulting in the retention of fewer than 5 archive files).

-
Get a list of files matching a mattered ordered by creation date. blogs.technet.com/b/heyscriptingguy/archive/2009/09/23/…. Then just count the objects in your list. Files in the list past the number of copies you want get deleted. This question should help you select the last/first files. stackoverflow.com/a/9682594/20267 –  Zoredache Jul 25 '13 at 19:02

Usage: yourScript.ps1 -Path path\to\run\against [-NumberToSave N]

param([String]$Path,[Int32]$NumberToSave=5)

$items = Get-ChildItem "$Path\*.ext" |
Where-Object Name -match "\d\d\d\d\d\d\d\d-\d\d\d\d\.ext" |
Sort-Object Name -Descending

if ($NumberToSave -lt$items.Count)
{
$items[$NumberToSave..($items.Count-1)] | Remove-Item }  I don't expect this is really any better than @John's, I just made this to try parameters, regexp, and not using a loop. Doing a regex match does have the possible benefit of ruling out other files with .ext that don't match the format (e.g. 20130504-1200.txt.ext, 20.ext), but it's doubtful that applies. I was bummed to find out that if$NumberToSave = $items.Count then the if statement is needed, otherwise it wouldn't be there. Technically: $items[$numberToSave..$items.Count] | Remove-Item


works fine (PowerShell doesn't seem to error if you reference past the array bounds, it just handles and ignores it?) but that seemed potentially confusing.

-
A friend provided me with a script that does what I want. It doesn't use this method, but it looks like this method will work, therefore it's marked as the answer. –  Jonathan J Jul 31 '13 at 18:39
@JoshuaMcKinnon I prefer your answer for the param and loop reduction. There's probably still a loop in yours with Sort-Object. I love the range notation in the array index too. However, I find mine more readable in that one can fairly easily tell what it does from a quick glance. –  john Jul 31 '13 at 21:00
@john It is a bit less readable with the piping all on one line. I could split it to multiple lines in the shell, but not as a .ps1 file. I could just use temporary variables and then split it into multiple lines in the .ps1 - Sorting most definitely involves a loop of some kind, this was just a personal exercise alternative to ForEach in this specific case, not for any other reason... –  Joshua McKinnon Jul 31 '13 at 22:28
It looks like it works after all, so I've updated with some new-lines :) –  Joshua McKinnon Aug 1 '13 at 17:45
$n = x$items = Get-ChildItem path_to_folder\*.ext

$items | Sort-Object Name -Descending | Select-Object -Last ($items.count - $n) | Foreach-Object { Remove-Item$_ }

-
You can also use a pipe as line continuation. –  jscott Jul 25 '13 at 19:41
If not, you will have to parse the dates from the file names which is going to be more complicated. –  john Jul 25 '13 at 19:45
OK, thanks! Learn something new every day! –  john Jul 25 '13 at 19:52
I <3 powershell for this kind of thing. –  john Jul 25 '13 at 20:03
Parsing dates isn't really important; we should be able to just sort the filenames. –  Jonathan J Jul 25 '13 at 21:36

A friend provided this script:

# Defines how many files you want to keep?
$Keep = 5 # Specifies file mask$FileMask = "*.ext"

# Defines base directory path
$Path = "F:\path\to\backups\" # Creates a full path plus file mask value$FullPath = $Path +$FileMask

# Creates an array of all files of a file type within a given folder, reverse sort.
$allFiles = @(Get-ChildItem$FullPath) | SORT Name -descending

# Checks to see if there is even $Keep files of the given type in the directory. If ($allFiles.count -gt $Keep) { # Creates a new array that specifies the files to delete, a bit ugly but concise.$DeleteFiles = $allFiles[$($allFiles.Count - ($allFiles.Count - $Keep))..$allFiles.Count]

# ForEach loop that goes through the DeleteFile array
ForEach ($DeleteFile in$DeleteFiles) {

# Creates a full path and delete file value
$dFile =$Path + $DeleteFile.Name # Deletes the specified file Remove-Item$dFile
}
}

-
<meta> Not sure why the color formatting is messed up in the code block. –  Jonathan J Jul 31 '13 at 19:07
# Get all the items in C:\Archive
Get-ChildItem -Path 'C:\Archive' |

# Skip directories
Where-Object { -not $_.PsIsContainer } | # Only get files that match YYYYMMDD-HHMM.ext format Where-Object {$_.Name -match '\d{8}-\d{4}.ext' } |

# Sort them by name, from most recent to oldest
Sort-Object -Descending -Property Name |

# Keep the first five items
Select-Object -Skip 5 |

Remove-Item -WhatIf

-

I've done this in Bash before, and you may be able to translate it to PowerShell. Personally, I'm a Linux admin, and don't know enough about Windows/PowerShell to do so myself.

#!/bin/bash

# Directory with backups in need of cleaning

# Make sure that there are at least 7 days worth of backups to avoid removing
#   the only backups left.
if [ "$(/bin/find "${bkps_dir}" -maxdepth 1 -type d | /usr/bin/wc -l)" -lt 8 ]; then
echo "Less than 8 backups found!"
echo "Will not delete anything!"
echo "Exiting..."
exit 1
fi

/bin/find "\${bkps_dir}" -maxdepth 1 -type d -mtime +7 -print0 | /usr/bin/xargs -0 /bin/rm -rf


Sorry that I could not be more helpful.

Edit: I also placed this here in case a Linux admin is in need of similar info.

-
Also, if no one posts a PowerShell option, then you could always install Cygwin and use the script above in Windows. –  Soviero Jul 25 '13 at 19:05
Perhaps a better approach would be for you to ask your own bash-specific question and then post this as a self-answer. It's not often I search for Powershell when I'm looking for bash. –  jscott Jul 25 '13 at 19:51