6

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).

1

6 Answers 6

9

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.

4
  • 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, 2013 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, 2013 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... Jul 31, 2013 at 22:28
  • It looks like it works after all, so I've updated with some new-lines :) Aug 1, 2013 at 17:45
5
$n = x

$items = Get-ChildItem path_to_folder\*.ext

$items | 
  Sort-Object Name -Descending | 
  Select-Object -Last ($items.count - $n) | 
  Foreach-Object { Remove-Item $_ }
9
  • You can also use a pipe as line continuation.
    – jscott
    Jul 25, 2013 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, 2013 at 19:45
  • I <3 powershell for this kind of thing.
    – john
    Jul 25, 2013 at 20:03
  • Parsing dates isn't really important; we should be able to just sort the filenames.
    – Jonathan J
    Jul 25, 2013 at 21:36
  • John, it looks like this would delete the first $n files rather than keeping the last $n. We don't know how many files might be there. Also, some regex may be necessary to avoid counting other files that may be present.
    – Jonathan J
    Jul 25, 2013 at 21:38
0
# 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
0

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
    }
}
2
  • <meta> Not sure why the color formatting is messed up in the code block.
    – Jonathan J
    Jul 31, 2013 at 19:07
  • Figured out the formatting issue. Had a trailing left-slash in the sample $path; ServerFault interpreted that as escaping the following double-quote. Removing the slash fixed the formatting. However, the slash is necessary to assemble the full path, so it remains.
    – Jonathan J
    Jan 27, 2022 at 17:39
0

This works for me using powershell v5.1

Get-ChildItem -Path C:\Path\To\Logs\BaseName*.log | Sort-Object -Descending | Select-Object -Skip 5 | Remove-Item
-1

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
bkps_dir="/opt/bkps/hosting.madyork.com"

# 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.

2
  • Also, if no one posts a PowerShell option, then you could always install Cygwin and use the script above in Windows.
    – Soviero
    Jul 25, 2013 at 19:05
  • 1
    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, 2013 at 19:51

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .