Удаление старых файлов на сервере Windows (версия 2)

21.02.2017

В процессе работы сервера или компьютера на дисках накапливаются различные файлы, которые занимают полезное пространство, а иногда даже могут привести к остановке работы операционной системы из-за нехватки свободного места. По этой причине время от времени необходимо очищать папки с временными и журнальными файлами. Чтобы избежать ручной работы, я рекомендую использовать скрипты, которые можно поставить в планировщик Windows и проводить автоматическую чистку.

В статье "Примеры скриптов для администрирования" я уже публиковал VBS-скрипт, который вычищает папки на серверах Windows Server 2008. В этой заметке я приведу пример Powershell-скрипта для очистки папок на сервере Windows Server 2012 (хотя на этой версии операционной системы старый VBS-скрипт работает так же, как и на старой).

# Powershell 3.0 or later is required
# Does not work at SYSTEM account in Windows 2008 R2

# --- Declare variables ---
$FoldersToPurge = New-Object System.Collections.ArrayList

# --- Input data ---

[void] $FoldersToPurge.Add(
 @{
   "Path" = 'C:\Windows\Temp';
   "Subfolders" = $True;
   "Mask" = '*';
   "Exclude" = $Nothing;
   "DaysOld" = 60
 }
)

[void] $FoldersToPurge.Add(
 @{
   "Path" = 'D:\RDSProfiles\RDS_SLQTools';
   "Subfolders" = $False;
   "Mask" = '*';
   "Exclude" = @("UVHD-template.vhdx");
   "DaysOld" = 90
 }
)

# --- Functions ---

Function Write-ScriptLog {
 Param(
   [CmdletBinding()]
   [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
   [String]$Message,
   [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
   [String]$LogFile
 )
 Process {
   $LogMessage = Get-Date -uformat "%d.%m.%Y %H:%M:%S"
   $LogMessage += "`t"
   $LogMessage += $Message
   $LogMessage | Out-File -FilePath $LogFile -Append
 }
}#End Function

Function Get-FolderSize {
 Param(
   [CmdletBinding()]
   [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
   [String]$Path
 )
 Begin {
   $oFSO = New-Object -comobject Scripting.FileSystemObject
 }
 Process{
   $oFolder = $oFSO.GetFolder($Path)
   Return ($oFolder.Size)
 }
} # End Function

Function Purge-Folder {
 Param(
   [CmdletBinding()]
   [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
   [String]$Path,
   [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
   [Boolean]$Subfolders,
   [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
   [String]$Mask,
   [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
   [String[]]$Exclude,
   [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
   [int]$DaysOld
 )
 Process {
   If ($DaysOld -lt 1) {
     Write-ScriptLog -LogFile $LogFile -Message ("Error: " + $DaysOld + " days is set for folder " + $Path)
     Return
   }
   If (Test-Path $Path -PathType Container) {
     Write-ScriptLog -LogFile $LogFile -Message ("Purging " + $Path + `
       " folder by deleing content which is older then " + $DaysOld + " days")
     # Получение коллекции файлов
     $Files= Get-ChildItem ($Path+"\*") -Force -File -Include $Mask
     # Обработка каждого файла из коллекции
     ForEach ($CurrentFile in $Files) {
       $CurrentFileWriteDate = $CurrentFile.LastWriteTime
       $CurrentFileCreateDate = $CurrentFile.CreationTime
       $CurrentFileOld_c = ((Get-Date) - $CurrentFileCreateDate).Days
       $CurrentFileOld_w = ((Get-Date) - $CurrentFileWriteDate).Days
       # Проверка, является ли файл устаревшим
       If (($CurrentFileOld_c -gt $DaysOld) -and ($CurrentFileOld_w -gt $DaysOld)) {
         $SkipFlag = $False
         ForEach ($ExcludeFile in $Exclude) {
           If ($CurrentFile.Name -eq $ExcludeFile) {
             $SkipFlag = $True
           }
         }
         If (-Not($SkipFlag)) {
           Write-ScriptLog -LogFile $LogFile -Message ("--> Delete " + $CurrentFile.FullName + `
             " (Created " + $CurrentFileCreateDate.ToString("dd.MM.yyyy") + ")")
           # Удаление устаревшего файла
           Try {
             Remove-Item $CurrentFile.FullName -Force -ErrorAction Stop
           } Catch {
             Write-ScriptLog -LogFile $LogFile -Message ("----> Error: " + $_.Exception.Message)
           }
         } Else {
           Write-ScriptLog -LogFile $LogFile -Message ("--> Skip " + $CurrentFile.FullName + `
             " (Created " + $CurrentFileCreateDate.ToString("dd.MM.yyyy") + ")")
         }

         Start-Sleep -Milliseconds 20
       }
     }
     If ($Subfolders) {
       # Получение коллекции подпапок
       $Folders= Get-ChildItem $Path -Force -Directory
       # Обработка каждой подпапки
       ForEach ($CurrentFolder in $Folders) {
         # Рекурсивный вызов процедуры удаления старых файлов - подпрограмма вызывает сама себя
         Purge-Folder -Path $CurrentFolder.FullName -Subfolders $True -Mask $Mask -Exclude $Exclude `
           -DaysOld $DaysOld
         # Проверка размера папки
         $CurrentFolderSize = Get-FolderSize -Path $CurrentFolder.FullName
         $CurrentFolderCreationDate = $CurrentFolder.CreationTime
         $CurrentFolderOld_c = ((Get-Date) - $CurrentFolderCreationDate).Days
         If (($CurrentFolderSize -eq 0) -and ($CurrentFolderOld_c -gt $DaysOld)) {
           Write-ScriptLog -LogFile $LogFile -Message ("--> Delete " + $CurrentFolder.FullName + `
             " (Created " + $CurrentFolderCreationDate.ToString("dd.MM.yyyy") + ")")
           # Удаление пустой папки
           Try {
             Remove-Item $CurrentFolder.FullName -Force -Confirm:$False -ErrorAction Stop
           } Catch {
             Write-ScriptLog -LogFile $LogFile -Message ("----> Error: " + $_.Exception.Message)
           }
         }
       }
     }
   } Else {
     Write-ScriptLog -LogFile $LogFile -Message ("Error: " + $Path + " folder does not exist")
     Return
   }
 }
} # End Function

# --- Start ---

$ScriptName =  $MyInvocation.MyCommand.Name
$ScriptFolder = $MyInvocation.MyCommand.Path.SubString(0,($MyInvocation.MyCommand.Path.Length - `
 $MyInvocation.MyCommand.Name.Length))
$LogFile = $ScriptFolder + 'Logs\' + (Get-Date -format yyyy_MM_dd) + "_" + `
 $MyInvocation.MyCommand.Name.SubString(0,($MyInvocation.MyCommand.Name.Length - 4)) + ".log"
$WindowsFolder = $env:windir
$WinTempFolder = $WindowsFolder + "\Temp"

# Log-file creation
If (-not(Test-Path ($ScriptFolder + 'Logs') -PathType Container )) {
 New-Item -ItemType Directory -Path ($ScriptFolder + 'Logs')
}
Out-File -FilePath $LogFile

Write-ScriptLog -LogFile $LogFile -Message ("Start " + $ScriptName)
Write-ScriptLog -LogFile $LogFile -Message ("===================================")

# Purge folders
ForEach ($CurrentFolderToPurge in $FoldersToPurge) {
 Write-ScriptLog -LogFile $LogFile -Message ("===== Processing " + `
   $CurrentFolderToPurge.Path + " folder =====")
 Purge-Folder -Path $CurrentFolderToPurge.Path -Subfolders $CurrentFolderToPurge.Subfolders `
   -Mask $CurrentFolderToPurge.Mask -Exclude $CurrentFolderToPurge.Exclude `
   -DaysOld $CurrentFolderToPurge.DaysOld
}

Write-ScriptLog -LogFile $LogFile -Message ("===================================")
Write-ScriptLog -LogFile $LogFile -Message ("Stop " + $ScriptName)

Использование сценария: в разделе --- Input data --- необходимо ввести секцию добавления элемента к массиву FoldersToPurge, где указать путь к очищаемой папке Path, флаг обработки подпапок Subfolders, маску обработки файлов Mask, список исключений Exclude (одномерный массив из имен файлов), период устаревания DaysOld в днях.

Если флаг обработки подпапок Subfolders установлен в $True, то удаление происходит по рекурсивному методу - сценарий просматривает дату создания каждого файла в папке и удаляет старые файлы, а так же обрабатывает все подпапки текущей папки по тому же принципу.

Указанный скрипт рекомендуется поставить в планировщик Windows на ежедневное выполнение в ночные часы, когда на серверах нет пользователей. Запуск задания нужно выполнять от имени системы (от имени пользователя SYSTEM).