evgen@spb: ~/2026/08/monitoring-1c-v-prtg-powershell-rac/

Мониторинг 1С в PRTG: PowerShell поверх rac.exe

Задача выглядела скромно: показать в PRTG количество активных сеансов кластера 1С. Всё остальное — процессор, память, диски, службы, метрики MSSQL и PostgreSQL — закрывается штатными сенсорами часа за три.

А сеансов 1С в PRTG нет. И не будет: штатного сенсора не существует, единственный путь — скриптом обернуть rac.exe и отдать результат в формате, который PRTG понимает.

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

Расклад

Один Windows-сервер, на нём одновременно живут сервер приложений 1С:Предприятие 8.3.27, MSSQL и PostgreSQL 17. Ядро PRTG стоит на отдельной машине.

Первое решение, которое стоит принять до всего остального: ставить Remote Probe на сам сервер 1С. Без него WMI, счётчики производительности, SQL-сенсоры и PowerShell будут ходить по сети с ядра, а это лишние права в домене, лишние правила фаервола и регулярные «Unknown» на WMI-сенсорах, когда на той стороне исчерпается квота. Зонд решает всё разом: проверки выполняются локально, наружу уходит только результат.

Служба зонда называется PRTGProbeService, а кастомные сенсоры лежат здесь:

C:\Program Files (x86)\PRTG Network Monitor\Custom Sensors\EXEXML\      — скрипты PowerShell
C:\Program Files (x86)\PRTG Network Monitor\Custom Sensors\sql\mssql\   — SQL-запросы к MSSQL
C:\Program Files (x86)\PRTG Network Monitor\Custom Sensors\sql\postgresql\

Скрипт, положенный в EXEXML, появляется в выпадающем списке при создании сенсора EXE/Script Advanced — но только после перезапуска зонда или обновления списка. Если файла в списке нет, а он на месте — сначала перезапусти службу зонда, потом ищи ошибку.

RAS, RAC и порт, которого не существует

Чтобы получить данные кластера снаружи, нужен RAS — сервер администрирования. Клиент rac.exe ходит в RAS, RAS ходит в агент кластера.

Порты, которые надо держать в голове:

ПортКто
1540ragent — агент сервера
1541менеджер кластера
1545ras — сервер администрирования

Служба RAS у меня уже была зарегистрирована и находилась в состоянии «Выполняется». Порт 1545 слушался, телнет проходил. При этом rac.exe упорно не видел кластер.

Разгадка нашлась в bat-файле регистрации, написанном когда-то до меня: там стояло CtrlPort=1570. Откуда взялась эта цифра — вопрос археологический. Факт в том, что RAS должен смотреть на агент, то есть на localhost:1540, а слушать при этом 1545.

Правильная регистрация службы:

"C:\Program Files\1cv8\8.3.27.1859\bin\ras.exe" cluster --service --port=1545 localhost:1540

Проверка, что всё поднялось — она же основной health-check:

"C:\Program Files\1cv8\8.3.27.1859\bin\rac.exe" cluster list localhost:1545

В ответ приходит блок, начинающийся с cluster : 1e71e7b3-.... Этот UUID понадобится всем последующим командам, так что его сразу стоит вынести в переменную скрипта.

Отдельный вывод, который я бы хотел знать заранее: служба «Выполняется» не означает, что служба работает. Порт слушается, TCP-коннект устанавливается, сенсор по портам зелёный — а данных нет. Единственная честная проверка — выполнить ту операцию, ради которой сервис существует. Для RAS это rac cluster list, и её имеет смысл повесить отдельным сенсором с порогом на время ответа.

Кодировки. Все три раза

Здесь была основная потеря времени, поэтому разберу подробно.

Раз. rac.exe — консольная утилита и пишет вывод в CP866. Читаешь как есть — получаешь мусор вместо имён информационных баз и пользователей.

Два. PRTG ожидает от сенсора EXE/Script Advanced документ XML в UTF-8. То есть внутри одного скрипта нужны две разные кодировки: одна на чтение стороннего процесса, другая на собственный вывод.

Рабочая комбинация — переключать [Console]::OutputEncoding дважды:

# читаем rac.exe в CP866
[Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding(866)
$racOutput = & $RacPath session list --cluster=$ClusterId $RasHost 2>&1

# отдаём результат в PRTG в UTF-8 без BOM
$utf8 = New-Object System.Text.UTF8Encoding($false)
$OutputEncoding = $utf8
[Console]::OutputEncoding = $utf8

Три. Сам файл .ps1 должен быть сохранён в UTF-8 с BOM. Без BOM PowerShell читает кириллицу в именах каналов как ANSI, и в PRTG приезжают кракозябры — при том что в консоли тот же скрипт отрабатывает правильно.

И бонусом — грабля, на которой я потерял отдельный час. Скрипт, скачанный браузером, получает альтернативный поток Zone.Identifier, помечающий файл как пришедший из интернета. PowerShell выполняет такой файл с ограничениями, и результат — снова битые имена каналов. Лечится одной командой:

Unblock-File "C:\Program Files (x86)\PRTG Network Monitor\Custom Sensors\EXEXML\1c_sessions.ps1"

Ключевое: все четыре причины дают ровно один симптом. Пока не проверены все, лечение выглядит как случайность — что-то поменял, стало правильно, почему именно, непонятно. Поэтому при кракозябрах проверять надо списком: BOM у файла, Unblock-File, кодировка чтения, кодировка вывода.

Разбор вывода rac

rac.exe session list отдаёт плоский текст блоками «ключ : значение», разделёнными пустой строкой. Парсер простой, но есть нюансы конкретной версии платформы, которые надо проверять на своём выводе, а не брать из примеров в интернете.

На 8.3.27.1859:

  • running : yes — именно yes, а не 1, как написано в половине найденных примеров;
  • available-perfomance — в платформе это слово с опечаткой, без первой r. Ищешь performance — не находишь ничего и делаешь неверный вывод, что поля нет;
  • строки сеансов и процессов надёжнее матчить регуляркой по UUID, чем по позиции в блоке.

Ещё одно решение, сильно влияющее на осмысленность цифр, — отделить пользовательские сеансы от служебных по полю app-id:

ПользовательскиеСлужебные
1CV8C, 1CV8, WebClientBackgroundJob, SrvrConsole, Designer

Иначе фоновые задания ночного регламента дадут всплеск, который на графике выглядит как наплыв пользователей в три часа ночи. Я держу оба числа отдельными каналами: «Сеансы всего» полезен для сверки с консолью кластера, «Сеансы пользователей» — для порогов.

Скрипт

# 1c_sessions.ps1 — активные сеансы кластера 1С для PRTG (EXE/Script Advanced)
# Файл сохранять в UTF-8 с BOM. После копирования: Unblock-File

$ErrorActionPreference = 'Stop'

$RacPath   = 'C:\Program Files\1cv8\8.3.27.1859\bin\rac.exe'
$RasHost   = 'localhost:1545'
$ClusterId = '1e71e7b3-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
$UserApps  = @('1CV8C', '1CV8', 'WebClient')

function Write-PrtgError([string]$Message) {
    $utf8 = New-Object System.Text.UTF8Encoding($false)
    $OutputEncoding = $utf8
    [Console]::OutputEncoding = $utf8
    Write-Output "<prtg><error>1</error><text>$Message</text></prtg>"
    exit 0
}

try {
    [Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding(866)
    $raw = & $RacPath session list --cluster=$ClusterId $RasHost 2>&1
    if ($LASTEXITCODE -ne 0) { Write-PrtgError "rac.exe exit=$LASTEXITCODE" }
} catch {
    Write-PrtgError ("rac.exe: " + $_.Exception.Message)
}

# блоки, разделённые пустой строкой → список хэш-таблиц
$sessions = @()
$cur = @{}
foreach ($line in $raw) {
    if ($line -match '^\s*$') {
        if ($cur.Count) { $sessions += ,$cur; $cur = @{} }
        continue
    }
    if ($line -match '^\s*([\w\-\.]+)\s*:\s*(.*)$') {
        $cur[$matches[1]] = $matches[2].Trim()
    }
}
if ($cur.Count) { $sessions += ,$cur }

$total   = $sessions.Count
$userCnt = ($sessions | Where-Object { $UserApps -contains $_['app-id'] }).Count
$byDbms  = ($sessions | Where-Object { $_['blocked-by-dbms'] -and $_['blocked-by-dbms'] -ne '0' }).Count
$byLs    = ($sessions | Where-Object { $_['blocked-by-ls']   -and $_['blocked-by-ls']   -ne '0' }).Count

$utf8 = New-Object System.Text.UTF8Encoding($false)
$OutputEncoding = $utf8
[Console]::OutputEncoding = $utf8

@"
<prtg>
  <result>
    <channel>Сеансы всего</channel><value>$total</value>
  </result>
  <result>
    <channel>Сеансы пользователей</channel><value>$userCnt</value>
  </result>
  <result>
    <channel>Ждут СУБД</channel><value>$byDbms</value>
    <LimitMode>1</LimitMode><LimitMaxWarning>1</LimitMaxWarning>
  </result>
  <result>
    <channel>Ждут блокировку 1С</channel><value>$byLs</value>
    <LimitMode>1</LimitMode><LimitMaxWarning>1</LimitMaxWarning>
  </result>
</prtg>
"@

Две детали, без которых сенсор будет капризничать.

Скрипт не должен печатать ничего до XML. Любое предупреждение PowerShell, вылезшее раньше, превращает ответ в «Response not well-formed». Отсюда $ErrorActionPreference = 'Stop', перехват в try, и перенаправление 2>&1 в переменную, а не в вывод.

Ошибки надо отдавать в формате PRTG, а не падать. Функция Write-PrtgError возвращает валидный XML с признаком ошибки и завершается с кодом 0. Так в интерфейсе будет красный сенсор с внятным текстом, а не «сенсор вернул мусор».

Настройки сенсора

При создании EXE/Script Advanced стоит поправить три вещи:

  • Интервал. Дефолтные 60 секунд для rac избыточны: утилита не мгновенная, а сеансы столько не живут. Ставлю 300 секунд.
  • Mutex. Если скриптовых сенсоров к одному кластеру несколько, задать им общий Mutex Name — тогда они не будут дёргать rac одновременно.
  • Security Context. Скрипт должен выполняться от учётки, имеющей право обращаться к кластеру. Если кластер закрыт администратором, логин и пароль передаются параметрами --cluster-user и --cluster-pwd; хранить их в теле скрипта — плохая идея, лучше передавать параметром сенсора.

Тем же способом делается второй полезный сенсор — разбивка памяти сервера между rphost, rmngr и СУБД. Логика та же, отличается только источник данных: счётчики производительности вместо rac.

Пороги: на что их ставить, а на что нет

Соблазн — повесить алерт на количество блокировок объектов. Не надо. На этой нагрузке 150–250 активных блокировок — нормальный фон, и порог будет либо орать круглосуточно, либо стоять так высоко, что бесполезен.

Реальное страдание пользователей видно в других каналах: blocked-by-dbms (сеанс ждёт СУБД) и blocked-by-ls (ждёт управляемую блокировку). Вот там ненулевое значение — уже повод посмотреть, что происходит.

Общее правило, которое стараюсь соблюдать: сначала неделя наблюдения за реальными значениями, потом пороги. Выставленные в день настройки «по ощущениям», через месяц они оказываются либо шумом, либо декорацией.

И отдельно — расписание. Ночной регламент 1С (реиндексация, обновление статистики, бэкапы) закономерно даёт всплески по дискам и блокировкам. Проще завести расписание «01:00–05:00» и повесить его паузой на чувствительные сенсоры, оставив работать проверки доступности служб.

Типовые проблемы

СимптомПричинаРешение
rac возвращает пустотуне запущена служба RASStart-Service "1C:Enterprise 8.3 Remote Server"
RAS «Выполняется», но кластера не видитв регистрации указан не тот порт агентаперерегистрировать на localhost:1540
Кракозябры в именах каналовнет BOM / нет Unblock-File / неверный OutputEncodingпроверить все четыре пункта списком
«Response not well-formed»скрипт печатает что-то до XML$ErrorActionPreference + 2>&1 в переменную
Скрипта нет в списке при создании сенсоразонд не перечитал каталогперезапустить PRTGProbeService
MSSQL-сенсор зелёный, каналы по нулямнет прав на просмотр состоянияGRANT VIEW SERVER STATE TO [учётка]
Deadlocks растут и не падаютканал в режиме Absoluteпереключить Value Mode на Difference
Avg. Disk sec/* всегда 0.00значения в секундах, слишком малымножитель ×1000, единица ms
Счётчиков PhysicalDisk нетотключены дисковые счётчикиdiskperf -Y и перезагрузка
Именованный инстанс MSSQL не находитсядругой префикс счётчиков\MSSQL$ИМЯ:Buffer Manager\...

Мораль

Первое. Зелёная служба и открытый порт не гарантируют ничего. Единственная честная проверка — выполнить операцию, ради которой сервис существует. У RAS это rac cluster list, и она же должна быть отдельным сенсором, иначе о неработающем администрировании кластера вы узнаете в тот день, когда оно срочно понадобится.

Второе. Кодировки в Windows-мониторинге — это не одна проблема, а стек из четырёх независимых, дающих одинаковый симптом. Пока не проверены все, лечение выглядит как везение.

Третье. Парсер чужого текстового вывода надо писать по выводу своей версии платформы, а не по примеру из интернета. Опечатка available-perfomance, живущая в 1С годами, — прекрасное тому подтверждение.

#1c #prtg #powershell #мониторинг #кодировки

evgen@spb: ~/contacts
evgen@spbping -c1 evgen.info
64 bytes from evgen.info: отвечу в течение рабочего дня
evgen@spbcat contacts.txt
mail      ya@evgen.info
telegram  @EvgenOne
github    github.com/onegin
blog      evgen.info/blog
evgen@spbecho $ЗАЧЕМ_ПИСАТЬ
интересная инфраструктурная задача · вопрос по Proxmox или Icinga ·
желание обсудить, почему memtest молчал, а память была битая
evgen@spb
© 2026 Евгений Подолинский · Санкт-Петербург
синий здесь – не тот, о котором вы подумали.