Назначение скрипта
Данный PowerShell-скрипт предназначен для централизованного сбора детальной информации о заданиях резервного копирования (включая агентские Computer Backup Job) с одного или нескольких серверов Veeam Backup & Replication.
Результат работы — один общий CSV-файл, в который по каждому заданию попадают:
-
Сервер Veeam, с которого собраны данные;
-
Название задания и его тип;
-
Дата и время последнего запуска и его статус (Success/Warning/Failed);
-
Список объектов, которые бэкапятся (включая исключённые; для Backup Copy заданий — источник);
-
Статус каждой машины (или каждого объекта) в рамках последнего запуска;
-
Репозиторий, куда сохраняется бэкап, и его физический путь (в том числе для Scale‑Out и S3‑совместимых репозиториев).
Скрипт ведёт подробный лог выполнения в отдельную папку, автоматически удаляя логи старше заданного количества дней.
Когда это может пригодиться
-
Администраторам, обслуживающим несколько Veeam-серверов и желающим иметь единую сводку по всем заданиям;
-
Для аудита, инвентаризации или миграции;
-
Для быстрой проверки, что все задания успешно завершаются и куда именно пишутся данные;
-
Для автоматической генерации отчёта по расписанию (например, еженедельно) с последующей отправкой по почте.
<#
.SYNOPSIS
Сбор информации о заданиях резервного копирования с одного или нескольких
серверов Veeam Backup & Replication.
.DESCRIPTION
Скрипт по очереди подключается к каждому серверу из списка $VBRServers,
собирает по каждому заданию (включая агентские Computer Backup Job):
- Сервер Veeam, с которого собраны данные
- Название задания
- Дата и время последнего запуска (и статус)
- Что бэкапилось (объекты, включая исключённые; для Backup Copy - источник)
- Машины и статус по каждой из них в последнем запуске
- Куда бэкапилось (репозиторий и физический путь, в т.ч. Scale-Out/S3)
Ведёт подробный лог выполнения в файл и хранит логи не дольше N дней.
Результат выводится на экран и сохраняется в один общий CSV.
#>
# =========================================
# ПЕРЕМЕННЫЕ (настройки)
# =========================================
# Список серверов Veeam Backup & Replication для опроса.
# Для каждого сервера можно (не обязательно) указать свои User/Password,
# если не указаны - используются общие $VBRUser/$VBRPassword ниже.
$VBRServers = @(
@{ Server = "veeam03" }
@{ Server = "veeam04" }
@{ Server = veeam01" }
# @{ Server = "veeam0X"; User = "DOMAIN\svc_veeam_ps"; Password = "пароль" }
)
$OutputPath = ".\VeeamBackupInfo.csv" # Путь к файлу отчёта CSV (общий по всем серверам)
$LogDir = ".\Logs" # Папка для логов выполнения
$LogRetentionDays = 3 # Хранить логи не дольше N дней
# Общие учётные данные по умолчанию (если для сервера в $VBRServers не заданы свои).
# Нужны, например, если на сервере включена MFA - тогда используйте сервисную учётку
# (см. чекбокс "This is a service account" в Veeam Users and Roles).
$VBRUser = "" # например "DOMAIN\svc_veeam_ps"
$VBRPassword = "" # пароль сервисной учётки
# =========================================
# ---------------------------------------------------
# ФУНКЦИЯ ЛОГИРОВАНИЯ
# ---------------------------------------------------
function Write-Log {
param(
[string]$Message,
[ValidateSet("INFO","WARN","ERROR")]
[string]$Level = "INFO"
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$line = "[$timestamp] [$Level] $Message"
switch ($Level) {
"INFO" { Write-Host $line -ForegroundColor Gray }
"WARN" { Write-Host $line -ForegroundColor Yellow }
"ERROR" { Write-Host $line -ForegroundColor Red }
}
Add-Content -Path $script:LogFile -Value $line -Encoding UTF8
}
# ---------------------------------------------------
# ПОДГОТОВКА ПАПКИ ЛОГОВ И РОТАЦИЯ
# ---------------------------------------------------
if (-not (Test-Path $LogDir)) {
New-Item -Path $LogDir -ItemType Directory -Force | Out-Null
}
$oldLogs = Get-ChildItem -Path $LogDir -Filter "VeeamBackupInfo_*.log" -ErrorAction SilentlyContinue |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$LogRetentionDays) }
foreach ($old in $oldLogs) {
Remove-Item $old.FullName -Force -ErrorAction SilentlyContinue
}
$LogFile = Join-Path $LogDir ("VeeamBackupInfo_{0}.log" -f (Get-Date -Format "yyyyMMdd_HHmmss"))
New-Item -Path $LogFile -ItemType File -Force | Out-Null
Write-Log "=== Запуск скрипта сбора информации Veeam (серверов в списке: $($VBRServers.Count)) ==="
Write-Log "Удалено старых лог-файлов: $($oldLogs.Count) (старше $LogRetentionDays дн.)"
# ---------------------------------------------------
# ПОДКЛЮЧЕНИЕ МОДУЛЯ VEEAM
# ---------------------------------------------------
try {
if (-not (Get-Module -Name Veeam.Backup.PowerShell)) {
Write-Log "Импорт модуля Veeam.Backup.PowerShell..."
Import-Module Veeam.Backup.PowerShell -ErrorAction Stop
Write-Log "Модуль успешно загружен."
}
else {
Write-Log "Модуль Veeam.Backup.PowerShell уже загружен."
}
}
catch {
Write-Log "Не удалось загрузить модуль Veeam.Backup.PowerShell: $($_.Exception.Message)" "ERROR"
throw $_
}
# ---------------------------------------------------
# ФУНКЦИЯ ПОДКЛЮЧЕНИЯ К СЕРВЕРУ VBR
# ---------------------------------------------------
function Connect-ToVBR {
param(
[string]$ServerName,
[string]$User,
[string]$Password
)
try {
if ($User -and $Password) {
Write-Log "Подключение к $ServerName под сервисной учёткой $User..."
$secPass = ConvertTo-SecureString $Password -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential($User, $secPass)
Connect-VBRServer -Server $ServerName -Credential $cred -ErrorAction Stop
}
else {
Write-Log "Подключение к $ServerName под текущим пользователем..."
Connect-VBRServer -Server $ServerName -ErrorAction Stop
}
Write-Log "Подключение к $ServerName установлено успешно."
return $true
}
catch {
Write-Log "Не удалось подключиться к серверу Veeam ($ServerName): $($_.Exception.Message)" "ERROR"
Write-Log "Если ошибка про MFA - используйте сервисную учётку (User/Password) с отключённой MFA." "WARN"
return $false
}
}
# ---------------------------------------------------
# ФУНКЦИЯ ОПРЕДЕЛЕНИЯ ПУТИ РЕПОЗИТОРИЯ
# ---------------------------------------------------
function Get-RepoPathInfo {
param($Repo)
if (-not $Repo) { return $null }
# 1) Обычный (Windows/Linux) репозиторий
if ($Repo.PSObject.Properties.Name -contains "FriendlyPath" -and $Repo.FriendlyPath) {
return $Repo.FriendlyPath
}
# 2) Scale-Out Backup Repository - собираем пути всех extent-ов (рекурсивно)
try {
$extents = Get-VBRRepositoryExtent -Repository $Repo -ErrorAction Stop
if ($extents) {
$paths = $extents | ForEach-Object {
$extPath = Get-RepoPathInfo -Repo $_.Repository
"$($_.Repository.Name): $extPath"
}
return ($paths -join "; ")
}
}
catch { }
# 3) Object Storage / S3-совместимый репозиторий
try {
if ($Repo.PSObject.Properties.Name -contains "AmazonOptions" -and $Repo.AmazonOptions) {
$opt = $Repo.AmazonOptions
$bucket = $opt.BucketName
$folder = $opt.FolderName
$point = $null
if ($Repo.PSObject.Properties.Name -contains "ServicePoint") { $point = $Repo.ServicePoint }
elseif ($Repo.PSObject.Properties.Name -contains "ConnectionPoint") { $point = $Repo.ConnectionPoint }
$built = "amazonS3://$point/$bucket/$folder" -replace "/{2,}", "/"
if ($bucket -or $folder) { return $built }
}
}
catch { }
try {
$objRepo = Get-VBRObjectStorageRepository -Id $Repo.Id -ErrorAction Stop
if ($objRepo) {
$folder = $null; $conn = $null
if ($objRepo.PSObject.Properties.Name -contains "Folder") { $folder = $objRepo.Folder }
if ($objRepo.PSObject.Properties.Name -contains "ConnectionPoint") { $conn = $objRepo.ConnectionPoint }
if ($folder -or $conn) { return ("$conn/$folder" -replace "/{2,}", "/") }
}
}
catch { }
# 4) Фолбэк - сырое свойство Path
if ($Repo.PSObject.Properties.Name -contains "Path" -and $Repo.Path) {
return $Repo.Path.ToString()
}
return "не удалось определить путь (тип объекта: $($Repo.GetType().Name))"
}
# ---------------------------------------------------
# ФУНКЦИЯ ПОЛУЧЕНИЯ СТАТУСА КАЖДОЙ МАШИНЫ В ЗАДАНИИ
# ---------------------------------------------------
function Get-JobMachineStatuses {
param($Session)
if (-not $Session) { return "нет данных о сессии" }
try {
$tasks = Get-VBRTaskSession -Session $Session -ErrorAction Stop
if (-not $tasks) { return "нет задач в сессии" }
$list = $tasks | ForEach-Object { "$($_.Name): $($_.Status)" }
return ($list -join "; ")
}
catch {
return "не удалось получить статусы машин: $($_.Exception.Message)"
}
}
# ---------------------------------------------------
# ФУНКЦИЯ ПОЛУЧЕНИЯ ПОСЛЕДНЕЙ СЕССИИ + СТАТУСОВ МАШИН
# с учётом особенностей разных типов заданий
# ---------------------------------------------------
function Get-JobLastSessionInfo {
param($Job)
$info = [PSCustomObject]@{
LastRunTime = "нет данных"
LastRunStatus = "нет данных"
MachineStatuses = "нет данных о сессии"
}
# --- Агентские (EpAgentBackup) задания ---
if ($Job.JobType -eq "EpAgentBackup") {
try {
$compJob = Get-VBRComputerBackupJob -Name $Job.Name -ErrorAction Stop
$sessions = Get-VBRComputerBackupJobSession -ErrorAction Stop |
Where-Object { $_.JobId -eq $compJob.Id } |
Sort-Object CreationTime -Descending
if ($sessions) {
$latest = $sessions | Select-Object -First 1
$info.LastRunTime = $latest.CreationTime
$info.LastRunStatus = $latest.Result
$perMachine = $sessions | Group-Object Name | ForEach-Object {
$s = $_.Group | Sort-Object CreationTime -Descending | Select-Object -First 1
"$($s.Name): $($s.Result)"
}
$info.MachineStatuses = ($perMachine -join "; ")
}
else {
$info.MachineStatuses = "нет сессий (Get-VBRComputerBackupJobSession пуст)"
}
}
catch {
$info.MachineStatuses = "не удалось получить сессии агентского задания: $($_.Exception.Message)"
}
return $info
}
# --- Обычные (VM/NAS) и Backup Copy задания ---
$sess = Get-VBRBackupSession | Where-Object { $_.JobId -eq $Job.Id } |
Sort-Object CreationTime -Descending | Select-Object -First 1
if ($sess) {
$info.LastRunTime = $sess.CreationTime
$info.LastRunStatus = $sess.Result
$info.MachineStatuses = Get-JobMachineStatuses -Session $sess
}
elseif ($Job.JobType -eq "SimpleBackupCopyPolicy" -or $Job.JobType -like "*BackupCopy*") {
try {
$info.LastRunStatus = $Job.GetLastResult()
$info.MachineStatuses = "Backup Copy в непрерывном режиме - постатейный статус машин через PowerShell недоступен (ограничение Veeam), см. консоль"
}
catch { }
}
return $info
}
# ---------------------------------------------------
# ФУНКЦИЯ ПОЛУЧЕНИЯ ОБЪЕКТОВ ЗАДАНИЯ
# ---------------------------------------------------
function Get-JobObjectsList {
param($Job)
if ($Job.JobType -eq "SimpleBackupCopyPolicy" -or $Job.JobType -like "*BackupCopy*") {
try {
$linkedIds = $Job.LinkedJobIds | ForEach-Object { $_.Guid }
if (-not $linkedIds) {
return "Backup Copy задание без привязанного job-источника (источник задан напрямую как backup/репозиторий)"
}
$linkedJobs = Get-VBRJob -WarningAction SilentlyContinue |
Where-Object { $linkedIds -contains $_.Id.Guid }
if (-not $linkedJobs) {
return "не удалось найти задание-источник по LinkedJobIds"
}
$parts = foreach ($lj in $linkedJobs) {
$srcObjects = (Get-VBRJobObject -Job $lj -ErrorAction SilentlyContinue |
ForEach-Object { $_.Name }) -join ", "
if (-not $srcObjects) { $srcObjects = "(объекты источника не определены)" }
"[источник: $($lj.Name)] $srcObjects"
}
return ($parts -join "; ")
}
catch {
return "не удалось определить источник Backup Copy задания: $($_.Exception.Message)"
}
}
try {
$objInfo = Get-VBRJobObject -Job $Job -ErrorAction Stop
$list = $objInfo | ForEach-Object {
$excl = $false
if ($_.PSObject.Properties.Name -contains "IsExcluded") { $excl = $_.IsExcluded }
if ($excl) { "[ИСКЛЮЧЕНО] $($_.Name)" } else { "$($_.Name)" }
}
return ($list -join "; ")
}
catch {
return $null
}
}
# ---------------------------------------------------
# ФУНКЦИЯ СБОРА ВСЕХ ЗАДАНИЙ С ТЕКУЩЕГО ПОДКЛЮЧЁННОГО СЕРВЕРА
# ---------------------------------------------------
function Get-VeeamJobsFromCurrentServer {
param([string]$ServerLabel)
$localResult = @()
# --- Обычные задания (VM, NAS и т.п. через Get-VBRJob) ---
$jobs = Get-VBRJob
Write-Log "[$ServerLabel] Найдено заданий (Get-VBRJob): $($jobs.Count)"
foreach ($job in $jobs) {
Write-Log "[$ServerLabel] Обработка задания: '$($job.Name)' (тип: $($job.JobType))"
$sessInfo = Get-JobLastSessionInfo -Job $job
if ($sessInfo.LastRunTime -ne "нет данных") {
Write-Log " Последняя сессия: $($sessInfo.LastRunTime) - статус $($sessInfo.LastRunStatus)"
}
else {
Write-Log " Сессии запуска не найдены." "WARN"
}
$objectsList = Get-JobObjectsList -Job $job
if ($null -eq $objectsList) {
Write-Log " Get-VBRJobObject не поддерживается для этого задания, пробуем Get-VBRComputerBackupJob..." "WARN"
try {
$compJob = Get-VBRComputerBackupJob -Name $job.Name -ErrorAction Stop
$objectsList = (Get-VBRComputerBackupJobObject -Job $compJob -ErrorAction Stop |
ForEach-Object { $_.Name }) -join "; "
}
catch {
Write-Log " Не удалось получить объекты через Get-VBRComputerBackupJob: $($_.Exception.Message)" "ERROR"
$objectsList = "не удалось получить список объектов"
}
}
$repo = $null
try {
$repo = $job.GetBackupTargetRepository()
}
catch {
Write-Log " Не удалось получить репозиторий через GetBackupTargetRepository(): $($_.Exception.Message)" "WARN"
}
$repoName = if ($repo) { $repo.Name } else { "не удалось определить" }
$repoPath = Get-RepoPathInfo -Repo $repo
Write-Log " Статусы машин: $($sessInfo.MachineStatuses)"
$localResult += [PSCustomObject]@{
"Сервер Veeam" = $ServerLabel
"Название задания" = $job.Name
"Тип задания" = $job.JobType
"Последний запуск" = $sessInfo.LastRunTime
"Статус последнего запуска" = $sessInfo.LastRunStatus
"Что бэкапится (объекты задания)" = $objectsList
"Машины и статус (посл. запуск)" = $sessInfo.MachineStatuses
"Куда бэкапится (репозиторий)" = $repoName
"Путь репозитория" = $repoPath
}
}
# --- Агентские задания (Computer Backup Job), которых нет в Get-VBRJob ---
try {
$compJobs = Get-VBRComputerBackupJob -ErrorAction Stop
$alreadyListed = $localResult."Название задания"
foreach ($cjob in $compJobs) {
if ($alreadyListed -contains $cjob.Name) { continue }
Write-Log "[$ServerLabel] Обработка агентского задания: '$($cjob.Name)'"
$sessions = Get-VBRComputerBackupJobSession -ErrorAction SilentlyContinue |
Where-Object { $_.JobId -eq $cjob.Id } |
Sort-Object CreationTime -Descending
$lastRunTime = "нет данных"
$lastRunStatus = "нет данных"
$machineStatuses = "нет данных о сессии"
if ($sessions) {
$latest = $sessions | Select-Object -First 1
$lastRunTime = $latest.CreationTime
$lastRunStatus = $latest.Result
$perMachine = $sessions | Group-Object Name | ForEach-Object {
$s = $_.Group | Sort-Object CreationTime -Descending | Select-Object -First 1
"$($s.Name): $($s.Result)"
}
$machineStatuses = ($perMachine -join "; ")
}
$objectsList = (Get-VBRComputerBackupJobObject -Job $cjob -ErrorAction SilentlyContinue |
ForEach-Object { $_.Name }) -join "; "
$repo = $null
try { $repo = $cjob.BackupRepository } catch { }
$repoName = if ($repo) { $repo.Name } else { "не удалось определить" }
$repoPath = Get-RepoPathInfo -Repo $repo
$localResult += [PSCustomObject]@{
"Сервер Veeam" = $ServerLabel
"Название задания" = $cjob.Name
"Тип задания" = "ComputerBackup"
"Последний запуск" = $lastRunTime
"Статус последнего запуска" = $lastRunStatus
"Что бэкапится (объекты задания)" = $objectsList
"Машины и статус (посл. запуск)" = $machineStatuses
"Куда бэкапится (репозиторий)" = $repoName
"Путь репозитория" = $repoPath
}
}
}
catch {
Write-Log "[$ServerLabel] Get-VBRComputerBackupJob недоступен или вернул ошибку: $($_.Exception.Message)" "WARN"
}
return $localResult
}
# =========================================
# ГЛАВНЫЙ ЦИКЛ ПО ВСЕМ СЕРВЕРАМ
# =========================================
$allResults = @()
foreach ($srv in $VBRServers) {
$serverName = $srv.Server
$userCur = if ($srv.ContainsKey('User') -and $srv.User) { $srv.User } else { $VBRUser }
$passCur = if ($srv.ContainsKey('Password') -and $srv.Password) { $srv.Password } else { $VBRPassword }
Write-Log "======================================================="
Write-Log "Обработка сервера: $serverName"
Write-Log "======================================================="
$connected = Connect-ToVBR -ServerName $serverName -User $userCur -Password $passCur
if (-not $connected) {
Write-Log "Пропускаем сервер $serverName из-за ошибки подключения." "ERROR"
continue
}
try {
$serverResults = Get-VeeamJobsFromCurrentServer -ServerLabel $serverName
$allResults += $serverResults
Write-Log "[$serverName] Собрано заданий: $($serverResults.Count)"
}
catch {
Write-Log "[$serverName] Ошибка при сборе данных: $($_.Exception.Message)" "ERROR"
}
finally {
try {
Disconnect-VBRServer -ErrorAction SilentlyContinue
Write-Log "Отключились от сервера $serverName"
}
catch { }
}
}
Write-Log "Всего заданий собрано со всех серверов: $($allResults.Count)"
# --- Вывод на экран ---
$allResults | Format-Table -AutoSize -Wrap
# --- Экспорт в общий CSV ---
$allResults | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8 -Delimiter ";"
Write-Log "Общий отчёт сохранён в: $OutputPath"
Write-Host "`nГотово. Отчёт: $OutputPath" -ForegroundColor Green
Write-Host "Лог выполнения: $LogFile" -ForegroundColor Green
Write-Log "=== Завершение скрипта ==="
Similar Posts:
- Как найти битые профили fslogix и найти профили которые не монтировались на сервер
- Как сделать автоматическую проверку и восстановление VHDX‑дисков FSLogix (NTFS) для RDS‑сессий
- Как вывести список пользователей у которых не при монтировался диск fslogix и переименовать FriendlyName
- Как сбросить завсшию службу или когда она в состоянии стоп(stoped)
- Как автоматически архивировать почтовые ящики Exchange и безопасно удалять отключенные учетные записи Active Directory с помощью PowerShell