В этой статье я поделюсь решением, которое использую для автоматического создания резервных копий информационных баз «1С:Предприятие» в клиент-серверном режиме. Я расскажу, как запустить службу RAS, как работает мой PowerShell-скрипт, и почему я вынес все настройки в обычный bat-файл.
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. Как работает скрипт
- Скрипт подключается к RAS и определяет GUID кластера по порту агента (например, 1541). RAS-порт вычисляется автоматически:
clusterPort + 4. - Для каждой базы из списка, разделённого запятыми:
- ищется GUID базы в кластере;
- проверяется свободное место на диске (оценка делается по размеру предыдущего dt‑файла, а для новой базы резервируется 3 ГБ);
- включается блокировка сеансов с заданным кодом разрешения (регламентные задания тоже приостанавливаются);
- все активные сессии принудительно завершаются;
- через
1cv8.exe DESIGNERс ключом/UCи кодом разрешения выполняется выгрузка dt‑файла; - блокировка снимается;
- старые dt‑файлы удаляются, остаётся только заданное в
KEEP_COUNTколичество последних копий.
- Все действия протоколируются в
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. Как я развернул и запустил всё по расписанию
- Сначала запустил bat-файл для создания службы RAS и убедился, что она работает.
- Создал папку
C:\1C_Backup_Scripts, поместил тудаbackup_1c.ps1иrun_backup.bat. Они должны лежать вместе в одном каталоге. - Убедился, что учётная запись, от которой работает планировщик, имеет доступ к
rac.exe, право на выполнение PowerShell и запись вD:\1C_Backups. - В планировщике Windows создал задачу: запуск
cmd.exe /c "C:\1C_Backup_Scripts\run_backup.bat"с ежедневным расписанием (например, в 2:00 ночи). - Для другого сервера 1С я просто сделал копию bat-файла, исправил в нём параметры
ONEC_SERVER,RAC_PATH,BASESи т.д., а ps1-скрипт остался тем же.
7. Заключение
Описанный подход проверил и начал использовать в реальной работе. Ключевые преимущества, которые я для себя выделил:
- Полная автоматизация с корректной блокировкой и завершением сессий.
- Единый PowerShell-скрипт, не требующий правок при добавлении новых серверов — все настройки вынесены в bat-файлы.
- Безопасная работа с блокировками благодаря коду разрешения: база выгружается, когда она закрыта для обычных пользователей.
- Ротация копий не даёт бесконтрольно расти занимаемому месту.
- Подробный лог помогает быстро найти причину, если что-то пошло не так.
Донаты принимаются на кошельки:
Yoomoney:
4100118091867315
Карта Т-Банк (бывший Тиньков):
2200 7017 2612 2077
Карта Альфа-Банк:
2200 1539 1357 2013


