Версия для печати
Вторник, 19 Май 2026 13:42

Автоматическое резервное копирование баз 1С Предприятие через RAS и PowerShell

Автор
Оцените материал
(2 голосов)

В этой статье я поделюсь решением, которое использую для автоматического создания резервных копий информационных баз «1С:Предприятие» в клиент-серверном режиме. Я расскажу, как запустить службу RAS, как работает мой PowerShell-скрипт, и почему я вынес все настройки в обычный bat-файл.

Все скрипты, которые вы увидите ниже, были разработаны с помощью DeepSeek. Все компоненты проверены на реальном сервере, и я пошагово покажу путь от запуска RAS до получения dt-файлов с ротацией.

1. Вступление и постановка задачи

Передо мной стояла задача организовать регулярное резервное копирование нескольких баз 1С, развёрнутых на одном сервере. Я выбрал штатный механизм выгрузки в *.dt через конфигуратор. Требовалось:

  • автоматически, по расписанию, создавать dt‑файлы для каждой базы;
  • блокировать сеансы пользователей перед выгрузкой;
  • поддерживать ротацию — хранить только последние N копий;
  • работать с кодом разрешения блокировки (permission code);
  • сделать решение легко переносимым на несколько серверов.

В результате был сгенерирован PowerShell-скрипт, который выполняет всю логику, а параметры для каждого сервера задаются в bat‑файле. Ключевыми элементами стали служба RAS и утилита rac.exe.

2. Для чего нужна служба RAS

RAS (Remote Administration Server) — это компонент сервера 1С, предоставляющий интерфейс для удалённого администрирования кластера. Именно к нему обращается rac.exe, когда нужно получить список кластеров, баз, управлять блокировками и сеансами. Если служба RAS не запущена, автоматизация через rac.exe невозможна. Поэтому начинаю с того, что настраиваю и запускаю RAS на том же хосте, где работает агент сервера 1С. В моём примере агент слушает порт 1540, а RAS — 1545.

3. Запуск службы RAS

Этот bat-файл, который формирует команду запуска, пересоздаёт службу Windows и стартует её. Вот он:

@echo off

:: ============================================
:: Настройки
:: ============================================

:: Версия платформы
set Ver1C=8.3.27.1936

:: Путь к bin (добавлен обратный слеш)
set BinDir=C:\Program Files\1cv8\%Ver1C%\bin

:: Пользователь службы
set SrvUserName=.\USR1CV8
set SrvUserPwd=123mypass

:: Порты
set CtrlPort=1540
set RASPort=1545
set MonitorPort=1555

:: Агент на этой же машине
set AgentName=localhost

:: Имя службы (с пробелами – будем кавычить везде)
set SrvcName=1C:Enterprise 8.3 Remote Server %CtrlPort% %Ver1C%

:: ============================================
:: Формирование команды запуска
:: ============================================

:: Экранируем путь к ras.exe (он содержит пробелы) и все аргументы
set "BinPath=\"%BinDir%\ras.exe\" cluster --service --port=%RASPort% %AgentName%:%CtrlPort% --monitor-port=%MonitorPort%"

:: ============================================
:: Пересоздание службы
:: ============================================

echo Stopping old service...
sc stop "%SrvcName%" 2>nul

echo Deleting old service...
sc delete "%SrvcName%" 2>nul

timeout /t 2 >nul

echo Creating new service...
sc create "%SrvcName%" binPath= "%BinPath%" start= auto obj= "%SrvUserName%" password= %SrvUserPwd% DisplayName= "%SrvcName%"

echo Starting service...
sc start "%SrvcName%"

echo.
echo ===== DONE =====
pause

Запускаю его с правами администратора, предварительно подставив свои значения версии платформы, учётной записи и портов. После этого служба работает постоянно, обеспечивая доступ для rac.exe.

4. Архитектура bat + ps1 и алгоритм резервного копирования

4.1. Почему я разделил bat и PowerShell

Скрипт на PowerShell (backup_1c.ps1) содержит всю логику: поиск кластера, блокировку, выгрузку, ротацию. Все параметры (сервер, список баз, учётные данные, путь к rac.exe и т.д.) он принимает через аргументы командной строки. Чтобы не редактировать длинный ps1-файл для каждого сервера, был создан bat-файл, в котором задаются переменные и вызывается PowerShell. Такой подход даёт мне ряд преимуществ:

  • Один и тот же backup_1c.ps1 использую для любого количества серверов — достаточно создать bat с нужными настройками.
  • Все параметры собраны в одном месте, их легко менять.
  • Планировщик задач Windows запускает bat-файл без лишних сложностей.

4.2. Как работает скрипт

  1. Скрипт подключается к RAS и определяет GUID кластера по порту агента (например, 1541). RAS-порт вычисляется автоматически: clusterPort + 4.
  2. Для каждой базы из списка, разделённого запятыми:
    • ищется GUID базы в кластере;
    • проверяется свободное место на диске (оценка делается по размеру предыдущего dt‑файла, а для новой базы резервируется 3 ГБ);
    • включается блокировка сеансов с заданным кодом разрешения (регламентные задания тоже приостанавливаются);
    • все активные сессии принудительно завершаются;
    • через 1cv8.exe DESIGNER с ключом /UC и кодом разрешения выполняется выгрузка dt‑файла;
    • блокировка снимается;
    • старые dt‑файлы удаляются, остаётся только заданное в KEEP_COUNT количество последних копий.
  3. Все действия протоколируются в backup.log, который лежит в папке с бэкапами.

Код разрешения — это пароль, который задаётся при настройке блокировки. Пользователи, знающие его, могут войти в заблокированную базу (например, я, чтобы сделать выгрузку). В скрипте этот код используется и для блокировки, и для снятия, и в ключе /UC.

5. Полный код моего решения

Оба файла сгенерированы с помощью DeepSeek. Ниже привожу их полностью.

5.1. PowerShell-скрипт backup_1c.ps1

#requires -Version 5.1

param(
    [Parameter(Mandatory = $true)]
    [string]$OneCServer,

    [Parameter(Mandatory = $true)]
    [string]$Bases,

    [Parameter(Mandatory = $true)]
    [string]$BackupPath,

    [Parameter(Mandatory = $true)]
    [int]$KeepCount,

    [Parameter(Mandatory = $true)]
    [string]$InfobaseUser,

    [Parameter(Mandatory = $true)]
    [string]$InfobasePassword,

    [Parameter(Mandatory = $true)]
    [string]$RacPath,

    [Parameter(Mandatory = $true)]
    [string]$PermissionCode
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8

# ---------- ФУНКЦИИ ----------
function Write-Log {
    param([string]$Message)
    $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
    $line = "$timestamp : $Message"
    Add-Content -Path $LogFile -Value $line -Encoding UTF8
    Write-Host $line
}

# ---------- ПРОВЕРКИ ----------
if (-not (Test-Path $RacPath)) {
    throw "rac.exe не найден: $RacPath"
}

$PlatformBin = Split-Path $RacPath -Parent
$OneCPath = Join-Path $PlatformBin '1cv8.exe'
if (-not (Test-Path $OneCPath)) {
    throw "1cv8.exe не найден в каталоге $PlatformBin"
}

$BaseList = $Bases -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ }
if ($BaseList.Count -eq 0) {
    throw "Список баз пуст."
}

if (-not (Test-Path $BackupPath)) {
    New-Item -Path $BackupPath -ItemType Directory -Force | Out-Null
}
$LogFile = Join-Path $BackupPath 'backup.log'
if (-not (Test-Path $LogFile)) {
    New-Item -Path $LogFile -ItemType File -Force | Out-Null
}

if ($OneCServer -notmatch '^(?.+):(?\d+)$') {
    throw "Неверный формат OneCServer. Ожидается сервер:порт, например 1cerp-app.local:1541"
}
$ServerHost = $Matches.Host
$ClusterPort = [int]$Matches.Port
$RasPort = $ClusterPort + 4
$RasServer = "$($ServerHost):$RasPort"

Write-Log "Запуск резервного копирования. Сервер: $OneCServer, RAS: $RasServer, баз: $($BaseList -join ', ')"

$MinFreeSpaceForNewBase = 3GB
$rxGuid = '\w{8}-\w{4}-\w{4}-\w{4}-\w{12}'

# ---------- ПОИСК GUID КЛАСТЕРА ----------
$clusterList = & $RacPath cluster list $RasServer 2>&1
if ($LASTEXITCODE -ne 0) {
    throw "Ошибка получения списка кластеров: $clusterList"
}
$clusterMatch = $clusterList | Select-String -Pattern "$ClusterPort" -Context 2,0 | Select-Object -First 1
if (-not $clusterMatch) {
    throw "Не найден кластер с портом $ClusterPort"
}
$clusterContextLine = $clusterMatch.Context.PreContext[0]
$clustID = ([regex]::Match($clusterContextLine, $rxGuid)).Value
if (-not $clustID) {
    throw "Не удалось извлечь GUID кластера"
}
Write-Log "GUID кластера: $clustID"

# ---------- ЦИКЛ ПО БАЗАМ ----------
foreach ($base in $BaseList) {
    Write-Log "===== Обработка базы: $base ====="
    try {
        # 1. поиск GUID базы
        $baseSummary = & $RacPath infobase summary list --cluster=$clustID $RasServer 2>&1
        if ($LASTEXITCODE -ne 0) {
            throw "Ошибка получения списка ИБ: $baseSummary"
        }
        $basePattern = "[^:]+:\s*$([regex]::Escape($base))$"
        $baseMatch = $baseSummary | Select-String -Pattern $basePattern -Context 1,0 | Select-Object -First 1
        if (-not $baseMatch) {
            throw "База с именем '$base' не найдена в кластере"
        }
        $baseContextLine = $baseMatch.Context.PreContext[0]
        $baseId = ([regex]::Match($baseContextLine, $rxGuid)).Value
        if (-not $baseId) {
            throw "Не удалось извлечь GUID базы '$base'"
        }
        Write-Log "GUID базы '$base': $baseId"

        # 2. проверка свободного места
        $driveRoot = [System.IO.Path]::GetPathRoot($BackupPath)
        $drive = [System.IO.DriveInfo]::GetDrives() | Where-Object { $_.Name -eq $driveRoot }
        if (-not $drive) {
            throw "Не удалось определить диск для пути '$BackupPath'"
        }
        $lastDt = Get-ChildItem -Path $BackupPath -Filter "$($base)_*.dt" -ErrorAction SilentlyContinue |
                  Sort-Object LastWriteTime -Descending |
                  Select-Object -First 1
        $requiredFree = if ($lastDt) {
            $lastDt.Length * 2
        } else {
            $MinFreeSpaceForNewBase
        }
        if ($drive.AvailableFreeSpace -lt $requiredFree) {
            throw "Недостаточно свободного места. Требуется: $([math]::Round($requiredFree/1GB,2)) ГБ, доступно: $([math]::Round($drive.AvailableFreeSpace/1GB,2)) ГБ"
        }
        Write-Log "Свободного места достаточно (требуется $([math]::Round($requiredFree/1MB,0)) МБ)"

        # 3. включение блокировки сеансов с кодом разрешения
        $lockFrom = (Get-Date).ToString('yyyy-MM-ddTHH:mm:ss')
        $lockTo   = (Get-Date).AddYears(1).ToString('yyyy-MM-ddTHH:mm:ss')
        Write-Log "Устанавливаем блокировку сеансов (код $PermissionCode)..."
        $lockArgs = @(
            'infobase', 'update',
            "--cluster=$clustID",
            '--denied-from', $lockFrom,
            '--denied-to', $lockTo,
            "--permission-code=$PermissionCode",
            '--sessions-deny=on',
            '--scheduled-jobs-deny=on',
            "--infobase=$baseId",
            "--infobase-user=$InfobaseUser",
            "--infobase-pwd=$InfobasePassword",
            $RasServer
        )
        & $RacPath @lockArgs 2>&1 | Out-Null
        if ($LASTEXITCODE -ne 0) {
            throw "Не удалось установить блокировку сеансов"
        }

	# 4. завершение активных сессий
        Write-Log "Завершаем активные сессии..."
        $sessionList = & $RacPath session list --cluster=$clustID --infobase=$baseId $RasServer 2>&1
        $sessionMatches = $sessionList | Select-String -AllMatches -Pattern "session[^:]+:\s*($rxGuid)"
        if ($sessionMatches -and $sessionMatches.Matches.Count -gt 0) {
            # Сохраняем текущий режим ошибок и временно переключаем на Continue
            $prevErrorAction = $ErrorActionPreference
            $ErrorActionPreference = 'Continue'

            foreach ($m in $sessionMatches.Matches) {
                $sessionId = $m.Groups[1].Value
                Write-Log "Завершаем сессию $sessionId"
                $termResult = & $RacPath session terminate --cluster=$clustID --session $sessionId --error-message 'Резервное копирование' $RasServer 2>&1
                if ($LASTEXITCODE -ne 0) {
                    Write-Log "Ошибка при завершении сессии $sessionId : $termResult"
                }
                Start-Sleep -Seconds 2
            }

            # Возвращаем исходный режим
            $ErrorActionPreference = $prevErrorAction
        } else {
            Write-Log "Активных сессий нет"
        }

        # 5. выгрузка DT через 1cv8.exe с ключом /UC
        $timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
        $dtFile = Join-Path $BackupPath "$($base)_$timestamp.dt"
        Write-Log "Запускаем выгрузку DT в $dtFile ..."
        $designerArgs = @(
            'DESIGNER',
            '/S', "$ServerHost\$base",
            '/N', $InfobaseUser,
            '/P', $InfobasePassword,
            "/UC$PermissionCode",
            '/DumpIB', $dtFile
        )
        $proc = Start-Process -FilePath $OneCPath -ArgumentList $designerArgs -Wait -NoNewWindow -PassThru
        if ($proc.ExitCode -ne 0 -or -not (Test-Path $dtFile)) {
            throw "Ошибка выгрузки DT. Код возврата: $($proc.ExitCode)"
        }

        $fileSize = (Get-Item $dtFile).Length
        Write-Log "DT-файл успешно создан: $dtFile ($([math]::Round($fileSize/1MB,2)) МБ)"

        # 6. снятие блокировки (используем тот же код разрешения)
        Write-Log "Снимаем блокировку сеансов..."
        $unlockArgs = @(
            'infobase', 'update',
            "--cluster=$clustID",
            '--sessions-deny=off',
            '--scheduled-jobs-deny=off',
            "--permission-code=$PermissionCode",
            "--infobase=$baseId",
            "--infobase-user=$InfobaseUser",
            "--infobase-pwd=$InfobasePassword",
            $RasServer
        )
        & $RacPath @unlockArgs 2>&1 | Out-Null
        if ($LASTEXITCODE -ne 0) {
            throw "Не удалось снять блокировку сеансов"
        }

        # 7. очистка старых DT
        if ($KeepCount -gt 0) {
            Write-Log "Оставляем последние $KeepCount копий для базы '$base'..."
            $existing = Get-ChildItem -Path $BackupPath -Filter "$($base)_*.dt" -ErrorAction SilentlyContinue |
                        Sort-Object LastWriteTime -Descending
            $toDelete = $existing | Select-Object -Skip $KeepCount
            foreach ($f in $toDelete) {
                Remove-Item $f.FullName -Force
                Write-Log "Удалён старый DT: $($f.Name)"
            }
        }

        Write-Log "===== База '$base' обработана успешно ====="
    }
    catch {
        Write-Log "ОШИБКА при обработке базы '$base': $_"
        # попытка снять блокировку при ошибке
        try {
            & $RacPath infobase update --cluster=$clustID --sessions-deny=off --scheduled-jobs-deny=off --permission-code=$PermissionCode --infobase=$baseId --infobase-user=$InfobaseUser --infobase-pwd=$InfobasePassword $RasServer 2>&1 | Out-Null
        } catch {}
        Write-Log "Переход к следующей базе..."
    }
}

Write-Log "--------------------Резервное копирование завершено.---------------------------------"

5.2. Bat-файл для запуска

@echo off
setlocal EnableExtensions

:: ========== SETTINGS ==========
set ONEC_SERVER=localhost:1541
set BACKUP_PATH=D:\1C_Backups
set KEEP_COUNT=5
set IB_USER=backup
set IB_PASS=123mypass
set BASES=test-buh,zup-test,buhtest1
set RAC_PATH=C:\Program Files\1cv8\8.3.27.1936\bin\rac.exe
set PERMISSION_CODE=12345679
:: ==============================

powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0backup_1c.ps1" ^
    -OneCServer "%ONEC_SERVER%" ^
    -Bases "%BASES%" ^
    -BackupPath "%BACKUP_PATH%" ^
    -KeepCount %KEEP_COUNT% ^
    -InfobaseUser "%IB_USER%" ^
    -InfobasePassword "%IB_PASS%" ^
    -RacPath "%RAC_PATH%" ^
    -PermissionCode "%PERMISSION_CODE%"

Поясню, что задаётся в bat-файле:

  • ONEC_SERVER — адрес агента сервера 1С в формате хост:порт. Именно этот порт (1541) используется для поиска кластера, а порт RAS вычисляется как порт + 4 (т.е. 1545).
  • BASES — имена баз в кластере, разделённые запятыми.
  • BACKUP_PATH — каталог для бэкапов и лога.
  • KEEP_COUNT — сколько последних копий оставлять.
  • IB_USER и IB_PASS — учётная запись с правами администратора базы для выполнения выгрузки.
  • RAC_PATH — полный путь к rac.exe, должен соответствовать версии платформы на сервере.
  • PERMISSION_CODE — код разрешения блокировки, который я установил. Он же используется в /UC при запуске конфигуратора.

6. Как я развернул и запустил всё по расписанию

  1. Сначала запустил bat-файл для создания службы RAS и убедился, что она работает.
  2. Создал папку C:\1C_Backup_Scripts, поместил туда backup_1c.ps1 и run_backup.bat. Они должны лежать вместе в одном каталоге.
  3. Убедился, что учётная запись, от которой работает планировщик, имеет доступ к rac.exe, право на выполнение PowerShell и запись в D:\1C_Backups.
  4. В планировщике Windows создал задачу: запуск cmd.exe /c "C:\1C_Backup_Scripts\run_backup.bat" с ежедневным расписанием (например, в 2:00 ночи).
  5. Для другого сервера 1С я просто сделал копию bat-файла, исправил в нём параметры ONEC_SERVER, RAC_PATH, BASES и т.д., а ps1-скрипт остался тем же.

7. Заключение

Описанный подход проверил и начал использовать в реальной работе. Ключевые преимущества, которые я для себя выделил:

  • Полная автоматизация с корректной блокировкой и завершением сессий.
  • Единый PowerShell-скрипт, не требующий правок при добавлении новых серверов — все настройки вынесены в bat-файлы.
  • Безопасная работа с блокировками благодаря коду разрешения: база выгружается, когда она закрыта для обычных пользователей.
  • Ротация копий не даёт бесконтрольно расти занимаемому месту.
  • Подробный лог помогает быстро найти причину, если что-то пошло не так.

Донаты принимаются на кошельки:

Yoomoney:
4100118091867315

Карта Т-Банк (бывший Тиньков):
2200 7017 2612 2077

Карта Альфа-Банк:
2200 1539 1357 2013

Прочитано 251 раз Последнее изменение Пятница, 29 Май 2026 20:50
Николай

Последнее от Николай

Похожие материалы (по тегу)