<# .SYNOPSIS Скрипт для закрытия Thunderbird и резервного копирования профиля .DESCRIPTION Выполняет поиск процессов Thunderbird, завершает их, ожидает полного закрытия и создает копию папки профиля в указанное место. Автоматически создает папки назначения. .NOTES Author: RobinHood Date: $(Get-Date -Format 'dd.MM.yyyy') #> # === НАСТРОЙКИ === $ThunderbirdProcessName = "thunderbird" # Имя процесса без .exe $SourceProfilePath = "$env:APPDATA\Thunderbird\Profiles" # Стандартный путь к профилям # Настройка пути для резервной копии (папка создастся автоматически) $BackupRoot = "E:\Backups\Thunderbird" # Корневая папка для бэкапов можно использовать сетевую папку "\\server\share\" $BackupDestination = Join-Path -Path $BackupRoot -ChildPath (Get-Date -Format 'yyyy-MM-dd') # Папка с датой $MaxWaitTimeSeconds = 30 # Максимальное время ожидания закрытия Thunderbird (в секундах) $CopyRetryCount = 3 # Количество попыток копирования при ошибках $MinFreeSpaceMB = 500 # Минимальное свободное место на диске назначения (МБ) # ================= # Функция для записи лога function Write-Log { param( [string]$Message, [string]$Type = "INFO" ) $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" # Цвета для разных типов сообщений switch ($Type) { "SUCCESS" { $color = "Green" } "ERROR" { $color = "Red" } "WARNING" { $color = "Yellow" } "INFO" { $color = "Cyan" } default { $color = "White" } } Write-Host "[$timestamp] [$Type] " -NoNewline Write-Host "$Message" -ForegroundColor $color } # Функция для создания папки назначения function New-BackupFolder { param( [string]$FolderPath ) try { if (-not (Test-Path $FolderPath)) { Write-Log "Папка назначения не существует. Создание: $FolderPath" -Type "INFO" # Создаем все папки в пути рекурсивно New-Item -Path $FolderPath -ItemType Directory -Force | Out-Null Write-Log "Папка успешно создана" -Type "SUCCESS" } else { Write-Log "Папка назначения уже существует: $FolderPath" -Type "INFO" } return $true } catch { Write-Log "ОШИБКА при создании папки назначения: $_" -Type "ERROR" return $false } } # Функция для проверки свободного места на диске function Test-FreeSpace { param( [string]$Path, [long]$RequiredSpaceMB = 500 ) try { # Получаем корневой диск из пути $rootPath = Split-Path -Path $Path -Qualifier if ([string]::IsNullOrEmpty($rootPath)) { $rootPath = $Path[0] + ":" } $drive = Get-PSDrive -Name $rootPath[0] -ErrorAction Stop $freeSpaceMB = [math]::Round($drive.Free / 1MB, 2) Write-Log "Свободное место на диске $rootPath : $freeSpaceMB MB" -Type "INFO" if ($drive.Free -lt ($RequiredSpaceMB * 1MB)) { Write-Log "НЕДОСТАТОЧНО МЕСТА! Требуется: $RequiredSpaceMB MB, доступно: $freeSpaceMB MB" -Type "ERROR" return $false } Write-Log "Места достаточно: $freeSpaceMB MB свободно" -Type "SUCCESS" return $true } catch { Write-Log "Не удалось проверить свободное место: $_" -Type "WARNING" return $true # Продолжаем, если не удалось проверить } } # Функция для проверки, запущен ли Thunderbird function Test-ThunderbirdRunning { $processes = Get-Process -Name $ThunderbirdProcessName -ErrorAction SilentlyContinue return ($processes.Count -gt 0) } # Функция для закрытия Thunderbird function Stop-ThunderbirdGracefully { Write-Log "Поиск запущенных процессов Thunderbird..." -Type "INFO" if (Test-ThunderbirdRunning) { $processCount = (Get-Process -Name $ThunderbirdProcessName -ErrorAction SilentlyContinue).Count Write-Log "Найдено процессов Thunderbird: $processCount. Попытка корректного завершения..." -Type "WARNING" # Пытаемся закрыть окна Thunderbird (более мягкий способ) $thunderbirdWindows = Get-Process -Name $ThunderbirdProcessName | Where-Object { $_.MainWindowTitle -ne "" } if ($thunderbirdWindows) { foreach ($proc in $thunderbirdWindows) { $windowTitle = if ($proc.MainWindowTitle) { $proc.MainWindowTitle } else { "Без заголовка" } Write-Log "Закрытие окна: $windowTitle" $proc.CloseMainWindow() | Out-Null } } else { Write-Log "Нет видимых окон Thunderbird, но процессы запущены" -Type "WARNING" } # Даем время на закрытие $waitCounter = 0 Write-Host "" while ((Test-ThunderbirdRunning) -and ($waitCounter -lt $MaxWaitTimeSeconds)) { Start-Sleep -Seconds 1 $waitCounter++ # Простой индикатор прогресса $progress = [math]::Round(($waitCounter / $MaxWaitTimeSeconds) * 100) Write-Host "`rОжидание закрытия Thunderbird: $progress% " -NoNewline } Write-Host "" # Если всё ещё запущен - принудительно завершаем if (Test-ThunderbirdRunning) { Write-Log "Thunderbird не закрылся за отведенное время. Принудительное завершение..." -Type "ERROR" $remainingProcesses = Get-Process -Name $ThunderbirdProcessName -ErrorAction SilentlyContinue foreach ($proc in $remainingProcesses) { try { Stop-Process -Id $proc.Id -Force Write-Log "Процесс PID $($proc.Id) принудительно завершен" -Type "WARNING" } catch { Write-Log "Не удалось завершить процесс PID $($proc.Id): $_" -Type "ERROR" } } Start-Sleep -Seconds 3 } else { Write-Log "Thunderbird успешно закрыт" -Type "SUCCESS" } } else { Write-Log "Thunderbird не запущен" -Type "INFO" } # Финальная проверка if (Test-ThunderbirdRunning) { Write-Log "ВНИМАНИЕ: Некоторые процессы Thunderbird всё еще запущены!" -Type "ERROR" # Показываем оставшиеся процессы Get-Process -Name $ThunderbirdProcessName -ErrorAction SilentlyContinue | ForEach-Object { Write-Log " - PID: $($_.Id), Запущен: $($_.StartTime)" -Type "ERROR" } Write-Log "Продолжаем копирование, но некоторые файлы могут быть заблокированы" -Type "WARNING" return $false } return $true } # Функция для получения размера папки function Get-FolderSize { param([string]$Path) try { if (Test-Path $Path) { $size = (Get-ChildItem $Path -Recurse -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum if ($size) { return [math]::Round($size / 1MB, 2) } } } catch { # Игнорируем ошибки при подсчете размера } return 0 } # Функция для копирования профиля function Copy-ThunderbirdProfile { param( [string]$Source, [string]$Destination ) Write-Log "Начало копирования профиля..." -Type "INFO" Write-Log " Источник: $Source" -Type "INFO" Write-Log " Назначение: $Destination" -Type "INFO" # Проверяем существование исходной папки if (-not (Test-Path $Source)) { Write-Log "Папка профиля НЕ НАЙДЕНА: $Source" -Type "ERROR" return $false } # Получаем размер исходной папки $sourceSizeMB = Get-FolderSize -Path $Source Write-Log "Размер исходной папки: ~$sourceSizeMB MB" -Type "INFO" # Проверяем свободное место $requiredSpace = [math]::Max($MinFreeSpaceMB, $sourceSizeMB + 100) # +100 МБ запас if (-not (Test-FreeSpace -Path $Destination -RequiredSpaceMB $requiredSpace)) { Write-Log "Недостаточно места для копирования. Операция отменена." -Type "ERROR" return $false } # СОЗДАЕМ ПАПКУ НАЗНАЧЕНИЯ (ключевое изменение!) if (-not (New-BackupFolder -FolderPath $Destination)) { return $false } # Получаем список всех профилей в папке Profiles $profiles = Get-ChildItem -Path $Source -Directory -ErrorAction SilentlyContinue if ($profiles.Count -eq 0) { Write-Log "В папке Profiles не найдено профилей!" -Type "WARNING" Write-Log "Проверяем наличие файлов напрямую..." -Type "INFO" # Если нет подпапок, возможно копируем содержимое самой папки Profiles $files = Get-ChildItem -Path $Source -File if ($files.Count -gt 0) { Write-Log "Найдены файлы в корне папки Profiles, копируем их" -Type "INFO" # Пытаемся скопировать содержимое папки Profiles $robocopyArgs = @( "`"$Source`"", "`"$Destination`"", "/E", # Копировать подпапки, включая пустые "/COPY:DAT", # Копировать данные, атрибуты, временные метки "/R:3", # Retry count "/W:5", # Wait time "/NP", # No Progress "/NJH", # No Job Header "/NJS" # No Job Summary ) try { $robocopyResult = Start-Process -FilePath "robocopy.exe" -ArgumentList $robocopyArgs -Wait -PassThru -NoNewWindow if ($robocopyResult.ExitCode -ge 8) { throw "Robocopy завершился с ошибкой. Код: $($robocopyResult.ExitCode)" } Write-Log "Файлы скопированы" -Type "SUCCESS" return $true } catch { Write-Log "Ошибка при копировании: $_" -Type "ERROR" return $false } } else { Write-Log "Папка Profiles пуста" -Type "WARNING" return $false } } # Копируем каждый профиль с повторами при ошибках $attempt = 1 $allCopied = $false while ($attempt -le $CopyRetryCount -and -not $allCopied) { try { Write-Log "Попытка копирования #$attempt из $CopyRetryCount..." $successCount = 0 $failCount = 0 foreach ($profile in $profiles) { $profileName = $profile.Name $sourceProfilePath = $profile.FullName $destProfilePath = Join-Path -Path $Destination -ChildPath $profileName Write-Log " Копирование профиля: $profileName" # Используем Robocopy для надежного копирования $robocopyArgs = @( "`"$sourceProfilePath`"", "`"$destProfilePath`"", "/MIR", # Mirror (точная копия) "/R:3", # Retry count (3 повтора при ошибке) "/W:5", # Wait time between retries (5 сек) "/NP", # No Progress (чтобы не засорять вывод) "/NJH", # No Job Header "/NJS" # No Job Summary ) Write-Log " Команда: robocopy $sourceProfilePath $destProfilePath /MIR /R:3 /W:5" -Type "INFO" $robocopyResult = Start-Process -FilePath "robocopy.exe" -ArgumentList $robocopyArgs -Wait -PassThru -NoNewWindow # Robocopy возвращает коды: 0-7 - успех, 8+ - ошибка if ($robocopyResult.ExitCode -ge 8) { Write-Log " Robocopy завершился с ошибкой. Код: $($robocopyResult.ExitCode)" -Type "ERROR" $failCount++ } else { Write-Log " Профиль $profileName скопирован (код: $($robocopyResult.ExitCode))" -Type "SUCCESS" $successCount++ } } if ($failCount -eq 0) { $allCopied = $true Write-Log "Все профили успешно скопированы! ($successCount профилей)" -Type "SUCCESS" } else { Write-Log "Скопировано: $successCount, Ошибок: $failCount" -Type "WARNING" if ($attempt -lt $CopyRetryCount) { Write-Log "Повторная попытка через 5 секунд..." -Type "INFO" Start-Sleep -Seconds 5 } } } catch { Write-Log "Ошибка при копировании (попытка $attempt): $_" -Type "ERROR" $failCount = $profiles.Count } $attempt++ } return ($allCopied -or ($successCount -gt 0)) } # Функция для создания информационного файла о бэкапе function New-BackupInfoFile { param( [string]$BackupPath, [string]$SourcePath ) try { $infoFile = Join-Path -Path $BackupPath -ChildPath "_backup_info.txt" $computerInfo = @( "# Информация о резервной копии Thunderbird", "Дата создания: $(Get-Date -Format 'dd.MM.yyyy HH:mm:ss')", "Компьютер: $env:COMPUTERNAME", "Пользователь: $env:USERNAME", "Исходная папка: $SourcePath", "Размер исходной папки: $(Get-FolderSize -Path $SourcePath) MB", "---", "Создано скриптом: $($MyInvocation.MyCommand.Path)" ) $computerInfo | Out-File -FilePath $infoFile -Encoding UTF8 Write-Log "Создан информационный файл: _backup_info.txt" -Type "INFO" } catch { Write-Log "Не удалось создать информационный файл: $_" -Type "WARNING" } } # === ОСНОВНАЯ ЧАСТЬ СКРИПТА === Clear-Host Write-Log "===================================================" -Type "INFO" Write-Log " РЕЗЕРВНОЕ КОПИРОВАНИЕ ПРОФИЛЯ THUNDERBIRD" -Type "INFO" Write-Log "===================================================" -Type "INFO" Write-Log "" # 1. Проверяем и создаем корневую папку для бэкапов, если нужно Write-Log "Проверка корневой папки для бэкапов: $BackupRoot" -Type "INFO" New-BackupFolder -FolderPath $BackupRoot | Out-Null Write-Log "" # 2. Проверяем наличие исходной папки if (-not (Test-Path $SourceProfilePath)) { Write-Log "Исходная папка не найдена: $SourceProfilePath" -Type "ERROR" Write-Log "Проверьте путь и права доступа" -Type "INFO" exit 1 } # 3. Закрываем Thunderbird Write-Log "Этап 1: Закрытие Thunderbird" -Type "INFO" Write-Log "------------------------------" -Type "INFO" $closed = Stop-ThunderbirdGracefully Write-Log "" # 4. Небольшая пауза для гарантии Start-Sleep -Seconds 2 # 5. Копируем профили Write-Log "Этап 2: Копирование профилей" -Type "INFO" Write-Log "-----------------------------" -Type "INFO" $copySuccess = Copy-ThunderbirdProfile -Source $SourceProfilePath -Destination $BackupDestination Write-Log "" # 6. Создаем информационный файл (если копирование успешно) if ($copySuccess) { New-BackupInfoFile -BackupPath $BackupDestination -SourcePath $SourceProfilePath # 7. Итог Write-Log "===================================================" -Type "SUCCESS" Write-Log " РЕЗЕРВНОЕ КОПИРОВАНИЕ УСПЕШНО ЗАВЕРШЕНО" -Type "SUCCESS" Write-Log "===================================================" -Type "SUCCESS" Write-Log "Папка с резервной копией: $BackupDestination" -Type "INFO" # Показываем содержимое папки с бэкапом if (Test-Path $BackupDestination) { $items = Get-ChildItem -Path $BackupDestination Write-Log "Содержимое папки с бэкапом:" -Type "INFO" foreach ($item in $items) { $itemSize = if ($item.PSIsContainer) { "<папка>" } else { "$([math]::Round($item.Length / 1KB, 2)) KB" } Write-Log " - $($item.Name) ($itemSize)" -Type "INFO" } } # Предложение открыть папку $openFolder = Read-Host "`nОткрыть папку с резервной копией? (Y/N)" if ($openFolder -eq "Y" -or $openFolder -eq "y") { Invoke-Item $BackupDestination } } else { Write-Log "===================================================" -Type "ERROR" Write-Log " РЕЗЕРВНОЕ КОПИРОВАНИЕ ЗАВЕРШИЛОСЬ С ОШИБКАМИ" -Type "ERROR" Write-Log "===================================================" -Type "ERROR" exit 1 } # Для запуска с параметрами командной строки (раскомментируйте если нужно): # param( # [string]$BackupPath = "D:\Backups\Thunderbird\$(Get-Date -Format 'yyyy-MM-dd')" # ) # $BackupDestination = $BackupPath