Powershell:如何删除目录中除最近文件之外的所有文件

Powershell: How to remove all files in Directory except recent ones

有文件夹备份 SQL 个数据库,名称中有备份日期。 例如C:\备份文件夹.
备份文件示例:
archive_1_01022022.bak
archive_1_02022022.bak
archive_1_03022022.bak
archive_2_01022022.bak
archive_2_02022022.bak
archive_2_03022022.bak
archive_3_01022022.bak
archive_3_02022022.bak
archive_3_03022022.bak

我需要 powershell 脚本从这个目录中删除所有文件但保留最近的文件(例如最近 5 天),但同时我需要保留每个数据库的至少 3 个副本(以防没有超过过去 5 天的备份)。

以下脚本删除所有文件并保留最近 5 天的文件:

$Folder = "C:\Backup"
$CurrentDate = Get-Date 
$DateDel = $CurrentDate.AddDays(-5) 
Get-ChildItem $Folder | Where-Object { $_.LastWriteTime -lt $DateDel } | Remove-Item 

以上是可行的,但如果最近 10 天没有最近的备份,并且如果我 运行 上面的代码那么它将删除 C:\Backup 中的所有文件。对于这种情况,我需要为每个数据库至少保留 3 个备份文件。

如果我使用下面的代码(例如我有 9 个不同的数据库),那么它就可以工作:

$Folder = "C:\Backup"
Get-ChildItem $Folder | ? { -not $_.PSIsContainer } |
Sort-Object -Property LastWriteTime -Descending |  
Select-Object -Skip 27 | 
Remove-Item -Force

但是实现很奇怪。例如,如果我有 9 个数据库的备份,那么我需要提供值为 27 的“Select-Object -Skip”(9 个数据库 x 跳过每个数据库的 3 个文件) .如果我有更多或更少的数据库,那么每次我都需要调整这个数字。如何使“Select-Object -Skip 3”静态值?

在这种情况下,您需要测试文件夹中有多少个日期与参考日期相比更新或相同的文件。如果少于3个,按LastWriteTime属性排序,保留前3个。如果你有足够的新文件,你可以删除旧的:

$Folder  =  "C:\Backup"
$DateDel = (Get-Date).AddDays(-5).Date  # set to midnight

# get a list of all backup files
$allFiles = Get-ChildItem -Path $Folder -Filter 'archive*.bak' -File

# test how many of these are newer than 5 days ago
$latestFiles = @($allFiles | Where-Object { $_.LastWriteTime -ge $DateDel })
if ($latestFiles.Count -lt 3) {
    # if less than three keep the latest 3 files and remove the rest
    $allFiles | Sort-Object LastWriteTime -Descending | Select-Object -Skip 3 | Remove-Item -WhatIf
}
else {
    # there are plenty of newer files, so we can remove the older ones
    $allFiles | Where-Object { $_.LastWriteTime -lt $DateDel } | Remove-Item -WhatIf
}

我已将 -WhatIf 安全开关添加到两个 Remove-Item cmdlet,因此您可以先看看 在实际发生之前发生什么破坏文件。一旦您对控制台显示的内容感到满意,请删除那些 -WhatIf 开关并再次 运行


如果你有9个数据库并且文件名中archive_之后的数字区分了这些数据库备份文件,只需将上面的内容放在一个循环中并调整-Filter:

$Folder  = "C:\Backup"
$DateDel = (Get-Date).AddDays(-5).Date  # set to midnight
# loop through the 9 database files
for ($i = 1; $i -le 9; $i++) {
    # get a list of all backup files per database
    $allFiles = Get-ChildItem -Path $Folder -Filter "archive_$($i)_*.bak" -File

    # test how many of these are newer than 5 days ago
    $latestFiles = @($allFiles | Where-Object { $_.LastWriteTime -ge $DateDel })
    if ($latestFiles.Count -lt 3) {
        # if less than three keep the latest 3 files and remove the rest
        $allFiles | Sort-Object LastWriteTime -Descending | Select-Object -Skip 3 | Remove-Item -WhatIf
    }
    else {
        # there are plenty of newer files, so we can remove the older ones
        $allFiles | Where-Object { $_.LastWriteTime -lt $DateDel } | Remove-Item -WhatIf
    }
}

好的,现在我们知道你给出的示例名称与真实名称没有任何相似之处,代码可以像这样简单:

$dbNames = 'archive', 'master', 'documents', 'rb'  # the names used in the backup files each database creates
$Folder  = "C:\Backup"
$DateDel = (Get-Date).AddDays(-5).Date  # set to midnight
# loop through the database files
foreach ($name in $dbNames) {
    # get a list of all backup files per database
    $allFiles = Get-ChildItem -Path $Folder -Filter "$($name)_*.bak" -File

    # test how many of these are newer than 5 days ago
    $latestFiles = @($allFiles | Where-Object { $_.LastWriteTime -ge $DateDel })
    if ($latestFiles.Count -lt 3) {
        # if less than three keep the latest 3 files and remove the rest
        $allFiles | Sort-Object LastWriteTime -Descending | Select-Object -Skip 3 | Remove-Item -WhatIf
    }
    else {
        # there are plenty of newer files, so we can remove the older ones
        $allFiles | Where-Object { $_.LastWriteTime -lt $DateDel } | Remove-Item -WhatIf
    }
}

假设您的备份具有以下命名约定:DBNAME_ddMMyyyy.bak 其中日期对应于备份日期,我将执行如下操作。


$Params = @{
    MinBackupThresold = 1
    MinBackupDays = 5
    SimulateDeletion = $False # Set to true to perform a Remove-Item -WhatIf deletion}


$Folder = "C:\temp\test"
$CurrentDate = Get-Date 
$DateDel = $CurrentDate.AddDays($Params.MinBackupDays).Date # set to midnight


$Archives = Foreach ($File in  Get-ChildItem $Folder ) {
    # -13 come from assuming naming convention DBName_8CharBackupDate.ext (eg: Db1_01012022.bak)
    $DbNameEndIndex = $File.Name.Length - 13
    # +1 since our naming convention have an underscore between db name and date.
    $RawDateStr = $File.Name.Substring($DbNameEndIndex + 1 , 8)
    [PSCustomObject]@{
        Path = $FIle.FullName
        LastWriteTime = $File.LastWriteTime
       DBName        = $File.Name.Substring(0, $DbNameEndIndex)
       BackupDate    = [datetime]::ParseExact( $RawDateStr, 'ddMMyyyy', $null)
    }
}

#Here we group archives by their "dbname" so we can make sure to keep a min.  backups for each.
$GroupedArchives = $Archives | Group DBName
Foreach ($Db in $GroupedArchives) {

    if ($Db.Count -gt $Params.MinBackupThresold) {
        $Db.Group | Sort BackupDate | Select-Object -Skip $Params.MinBackupThresold | Where-Object { $_.BackupDate -lt $DateDel } | % { Remove-Item -Path $_.Path -Force -WhatIf:$Params.SimulateDeletion }
    } else {

        # You could include additional checks to verify last backup, alert you if there should be more in there, etc...
    }

}

注意:使用从文件名中提取的日期会比lastwritetime更准确,可能因其他原因更新(既然我们有,不妨使用它.)

注 2:$params 中添加了 WhatIf,因此您可以轻松地在实际移除和模拟之间切换(Theo 的回答让我想到了提供该切换)和他 .Date 以确保日期设置为午夜而不是当前时间。