Назад | Перейти на главную страницу

Попытка использовать PowerShell для выборочного удаления старых файлов резервных копий

Я работаю с диском с тысячами файлов резервных копий, многие из которых просто загромождают пространство.

Программа, которую используют наши сотрудники, создает резервную копию электронной таблицы каждый раз, когда они открывают ее для редактирования. Каждая резервная копия - это новый файл с именем в соответствии с таблицей + дата + время. У нас есть отдельный каталог резервных копий для каждого из наших клиентов, и в каждом каталоге есть несколько электронных таблиц и несколько резервных копий каждой электронной таблицы.

В итоге у нас есть файловая структура, заполненная такими файлами:

Companyx/backup/abssheet_091210_111006.bps    
Companyx/backup/abssheet_091210_133335.bps    
Companyx/backup/xyzsheet_091210_145223.bps    
Companyx/backup/xyzsheet_100803_100332.bps    
Companyx/backup/xyzsheet_100812_111244.bps
Companyy/backup/gnu_sheet_081029_110455.bps
Companyy/backup/gnu_sheet_081029_111233.bps
Companyy/backup/gnu_sheet_081029_112355.bps

Нам нужно сохранить только две самые последние резервные копии любого конкретного листа. Из 8 файлов, которые я перечислил здесь, я хотел бы оставить 6. Дата и время в имени файла не важны, поскольку я могу использовать дату и время из информации о файле. Но имена файлов не могут быть изменены.

Я немного поигрался с powershell, и я уже использовал gci, чтобы переместить их в собственное расположение файла. Я также могу удалить строки даты и времени из имен файлов. Я также нашел сценарий PowerShell для удаления всех файлов, кроме двух новейших, из определенного каталога. Но я не понимаю, как выборочно удалить то, что я хочу.

Пока что я написал / изменил следующий код:

$newlist = New-Object System.Collections.Generic.List[System.String]
$fulllist = gci . | where {-not $_.PsIsContainer} | sort Name
$array = @()

foreach ($object in $fulllist)
{
    $string = $object.name
    $psworiginal = $string.Replace("_"+($string -split "_")[-1]," ")
    $psworiginal2 = $psworiginal.Replace("_"+($psworiginal -split "_")[-1]," ")
    $newlist.Add($psworiginal2)
}

$newlist = $newlist | select -unique

Это дает мне список отдельных таблиц. Но я не уверен, как работать с этим списком, чтобы вернуться к исходному списку и удалить все, кроме двух последних резервных копий каждой электронной таблицы.

В идеале я хотел бы вернуть параметр -Recurse в вызов gci и заставить его проходить через сложную структуру каталогов, чтобы отсеять старые резервные копии в каждом каталоге.

Это приведет к удалению каждого файла на основе «ключа» из каждой папки компании, кроме двух самых новых. Фильтрация основана на LastWriteTimeUTC

В этом коде $local:allFiles = @{}; в Filter-BackupFiles функция - это ключ-значение, где КЛЮЧ является частью имени файла перед 12345_12345.basfileKey именованный выбор в RegEx, в моем примере это все предпоследнее _ символ) и ЗНАЧЕНИЕ - это массив файловых объектов (не имен файлов). Для каждого добавляемого файла (используя Add-Member) атрибут сортировки (в моем случае это LastWriteTimeUTC, вы можете сделать что-нибудь еще в вашем случае)

Затем для каждого ключа (который является префиксом имени файла) я сортирую список файловых объектов и добавляю в список удаления все, кроме двух ($Script:KeepMostRecent = 2) первых файлов (это означает два самых новых файла, потому что они были отсортированы по убыванию даты).

$Script:StartPath = 'D:\Test_1'
$Script:KeepMostRecent = 2;
$Script:BackupSubfolder = 'backup'
$Script:RegexFilter = '^(?<fileKey>.*)_\d+_\d+\.bps$'
$Script:SimulatingMode = $true
#In my case regex is overriden because of file names
$Script:RegexFilter = '^(?<fileKey>.+)_[^_]+\.txt$' #THIS IS DEV OVERRIDE
Function Log-Error {
    Param (
        [Parameter(Mandatory=$true)]
        [String]$LogMessage
    )
    Write-Host -ForegroundColor Yellow "Error: $($LogMessage)";
}
#End of Log-Error

Function Get-Companies {
    Param (
        [Parameter(Mandatory=$true)]
        [String]$SearchBase
    )
    $local:companyDirectoies = @()
    try {
        $local:companyDirectoies = @(Get-ChildItem -Path $SearchBase -Recurse:$false -ErrorAction Stop | Where-Object {$_.psIsContainer -eq $true} -ErrorAction Stop | ForEach-Object {return $_.FullName} -ErrorAction Stop )
    } catch {
        Log-Error -LogMessage $([String]::Format("Error while getting companies list: {0}", $_.Exception.Message))
        return $null
    }
    return $local:companyDirectoies
}

Function Get-BackupFiles {
        Param (
        [Parameter(Mandatory=$true)]
        [String]$CompanyDirectoryPath
    )
    $local:files = @();
    try {
        $local:files = @( Get-ChildItem -Path $CompanyDirectoryPath -Recurse:$false -ErrorAction Stop | Where-Object {$_.psIsContainer -eq $false} -ErrorAction Stop)
    } catch {
        Log-Error -LogMessage $([String]::Format("Error while getting Backup file list for path {0}: {1}", $CompanyDirectoryPath ,$_.Exception.Message))
        return $null
    }
    return $local:files
}

Function Filter-BackupFiles {
    Param (
        [Parameter(Mandatory=$true)]
        [Object[]]$CompanyBackupFiles
    )
    $local:allFiles = @{};
    $local:filesToRemove = @()
    foreach ($local:f in $CompanyBackupFiles) {
        $local:lastFileDate = $local:f.LastWriteTimeUtc
        $local:f | Add-Member -MemberType NoteProperty -Name 'LastDate' -Value $( $local:lastFileDate ) 
        $local:fileName = $local:f.Name
        if ($local:fileName -match $Script:RegexFilter) {
            $local:fileKey = $Matches['fileKey']
            #Use NotCContains ir you need case-sensitive filtering
            if ($local:allFiles.Keys -notcontains $local:fileKey) {
                $local:allFiles[$local:fileKey] = @()
            }
            $local:allFiles[$local:fileKey] += @($local:f)
        } else {
            Log-Error -LogMessage $([String]::Format( "Error - the file name {0} does not match regEx. None will be processed for this list",$local:fileName))
            return $null
        }
    }
    foreach ($local:k in $local:allFiles.Keys) {
        Write-Host -ForegroundColor White "Checking files for key $($local:k)"
        $local:files = @( $local:allFiles[$local:k] | Sort-Object -Property 'LastDate' -Descending  )
        $local:filesToKeep = $Script:KeepMostRecent
        foreach ($local:f in $local:files) {
            $local:filesToKeep--
            Write-Host -ForegroundColor White -NoNewline "$($local:f.FullName)`t$($local:f.LastDate)"
            if ($local:filesToKeep -lt 0) {
                $local:filesToRemove += @($local:f.FullName)
                Write-Host -ForegroundColor Red "`tMARKED TO REMOVE"
            } else {
                Write-Host -ForegroundColor Green "`tMARKED TO LIVE"
            }
        }
    }
    return $local:filesToRemove
}

Function _main {
    $local:AllFilesToRemove = @()
    $local:companiesPathList = @(Get-Companies -SearchBase $Script:StartPath)
    if ($local:companiesPathList.Count -le 0) {
        Log-Error -LogMessage "Companies list is empty"
        return
    }
    forEach ($local:comanyPath in $local:companiesPathList) {
        Write-Host -ForegroundColor White "`r`n`r`nProcessing company on path $($local:comanyPath)"
        $local:companyBackupFolder = ""
        try {
            $local:companyBackupFolder = $( Join-Path -Path $local:comanyPath -ChildPath $Script:BackupSubfolder -ErrorAction Stop )
            $local:allCompanyFiles =  Get-BackupFiles -CompanyDirectoryPath $local:companyBackupFolder -ErrorAction Stop 
        } catch {
            Log-Error -LogMessage "Error getting backup files for company $($local:comanyPath) : $($_.Exception.Message)"
        }
        if (($local:allCompanyFiles.Count -le 0) -or ($local:allCompanyFiles -eq $null)) {
            Log-Error -LogMessage "Company $($local:companyBackupFolder) does not have files in backup. Will ignore it."
            continue
        }
        $local:companyFilesToRemove =  Filter-BackupFiles -CompanyBackupFiles $local:allCompanyFiles 
        if (($local:companyFilesToRemove.Count -le 0) -or ($local:companyFilesToRemove -eq $null)) {
            Write-Host -ForegroundColor Cyan "Company $($local:comanyPath) does not have files to remove. Will ignore it."
            continue
        }
        Write-Host -ForegroundColor White "Company $($local:comanyPath) have $($local:companyFilesToRemove.Count) file to remove"
        $local:AllFilesToRemove += @( $local:companyFilesToRemove )
    }
    Write-Host -ForegroundColor White "Totally we have $($local:AllFilesToRemove.Count) files to remove" 
    foreach ($local:f in $local:AllFilesToRemove) {
        Write-Host -ForegroundColor White "Removing $($local:f)"
        try {
            Remove-Item -Path $local:f -Force -Confirm:$false -WhatIf:$Script:SimulatingMode -ErrorAction Stop
        } catch {
            Log-Error -LogMessage "Error removing file $($local:f) : $($_.Exception.Message)"
        }
    }
}

_main

Таким образом, вывод будет

Processing company on path D:\Test_1\Company1
Checking files for key File1_Custom_Name
D:\Test_1\Company1\backup\File1_Custom_Name_bak1.txt    02/28/2016 07:07:38 MARKED TO LIVE
D:\Test_1\Company1\backup\File1_Custom_Name_Bak2.txt    02/28/2016 07:06:38 MARKED TO LIVE
D:\Test_1\Company1\backup\File1_Custom_Name_Bak3.txt    02/28/2016 07:05:38 MARKED TO REMOVE
Checking files for key File2
D:\Test_1\Company1\backup\File2_Bak1.txt    02/28/2016 07:07:38 MARKED TO LIVE
D:\Test_1\Company1\backup\File2_Bak2.txt    02/28/2016 07:06:38 MARKED TO LIVE
D:\Test_1\Company1\backup\File2_Bak3.txt    02/28/2016 07:05:38 MARKED TO REMOVE
Company D:\Test_1\Company1 have 2 file to remove


Processing company on path D:\Test_1\Company2
Checking files for key File2
D:\Test_1\Company2\backup\File2_Bak3.txt    02/28/2016 07:58:34 MARKED TO LIVE
D:\Test_1\Company2\backup\File2_Bak2.txt    02/28/2016 07:58:31 MARKED TO LIVE
D:\Test_1\Company2\backup\File2_Bak1.txt    02/28/2016 07:58:28 MARKED TO REMOVE
Checking files for key File4
D:\Test_1\Company2\backup\File4_Bak1.txt    02/28/2016 07:59:43 MARKED TO LIVE
D:\Test_1\Company2\backup\File4_Bak3.txt    02/28/2016 07:58:42 MARKED TO LIVE
D:\Test_1\Company2\backup\File4_Bak2.txt    02/28/2016 07:58:39 MARKED TO REMOVE
Checking files for key File1
D:\Test_1\Company2\backup\File1_Bak3.txt    02/28/2016 07:58:25 MARKED TO LIVE
D:\Test_1\Company2\backup\File1_Bak2.txt    02/28/2016 07:58:22 MARKED TO LIVE
D:\Test_1\Company2\backup\File1_bak1.txt    02/28/2016 07:58:17 MARKED TO REMOVE
Company D:\Test_1\Company2 have 3 file to remove


Processing company on path D:\Test_1\Company3
Checking files for key File2
D:\Test_1\Company3\backup\File2_Bak1.txt    02/28/2016 07:07:38 MARKED TO LIVE
D:\Test_1\Company3\backup\File2_Bak2.txt    02/28/2016 07:06:38 MARKED TO LIVE
D:\Test_1\Company3\backup\File2_Bak3.txt    02/28/2016 07:05:38 MARKED TO REMOVE
Checking files for key File4
D:\Test_1\Company3\backup\File4_Bak1.txt    02/28/2016 07:07:38 MARKED TO LIVE
D:\Test_1\Company3\backup\File4_Bak2.txt    02/28/2016 07:06:38 MARKED TO LIVE
D:\Test_1\Company3\backup\File4_Bak3.txt    02/28/2016 07:05:38 MARKED TO REMOVE
Checking files for key File1
D:\Test_1\Company3\backup\File1_bak1.txt    02/28/2016 07:07:38 MARKED TO LIVE
D:\Test_1\Company3\backup\File1_Bak2.txt    02/28/2016 07:06:38 MARKED TO LIVE
D:\Test_1\Company3\backup\File1_Bak3.txt    02/28/2016 07:05:38 MARKED TO REMOVE
Company D:\Test_1\Company3 have 3 file to remove
Totally we have 8 files to remove
Removing D:\Test_1\Company1\backup\File1_Custom_Name_Bak3.txt
Removing D:\Test_1\Company1\backup\File2_Bak3.txt
Removing D:\Test_1\Company2\backup\File2_Bak1.txt
Removing D:\Test_1\Company2\backup\File4_Bak2.txt
Removing D:\Test_1\Company2\backup\File1_bak1.txt
Removing D:\Test_1\Company3\backup\File2_Bak3.txt
Removing D:\Test_1\Company3\backup\File4_Bak3.txt
Removing D:\Test_1\Company3\backup\File1_Bak3.txt