Необходимость
Платформа 1С великолепна, замечательна и просто мощная. Обширный функционал доступен из коробки, а технология внешних компонент делает ее практически безграничной в части расширения возможностей. Но иногда...
Иногда все это не помогает и для достижения результатов, решения задачи - приходиться использовать сторонний софт. И ладно, если бы это были COM-объекты, которые хоть и являются устаревшей технологией, но до сих пор часто используются и даже получили некоторую вторую жизнь с последними событиями. Но есть и другой путь - запуск приложений напрямую из кода встроенного языка.
&НаСервере
Процедура ВыполнитьБесконечныйПинг()
ЗапуститьПриложение("ping 127.0.0.1 -t");
КонецПроцедуры
Сегодня мы поговорим о запуске приложений программным способом. А также о некоторых проблемах и способах их решений под Windows и Linux.
Часть подходов, которые будут описаны ниже, применяются в разработке "Командный интерпретатор для 1С", но скачивать ее для изучения не обязательно. Все есть здесь. Там лишь все это организовано в удобном виде для использования.
Исполни это
Причин, когда такое может понадобиться - много, очень много. Все их рассматривать точно не будем. Остановимся на одном простейшем примере - запуск команды ping, чтобы узнать доступность какого-либо ресурса в сети средствами 1С. Иногда еще ping запускают для эмулирования ожидания (метода Sleep), но мы такое извращение делать не будем :)
Наша задача - запустить какое-либо приложение с параметрами и получить результат его работы. И сделать мы это должны безопасным способом!
Последнее означает, что если запускаемое приложение зависнет, запросит интерактивных действий от пользователя (а на сервере мы ничем ему в этом случае помочь не сможем) или просто будет выполняться дольше выделенного для него времени, то мы должны завершить его работу и продолжить выполнение кода в обычном режиме и обработать исключение. Никому ведь не нужны зависшие сеансы 1С?
Мы рассмотрим несколько решений как для Windows, так и для Linux. И так, поехали.
My Little Windows
По классике, сначала мы сделаем плохо, а потом сделаем хорошо. Прежде чем начать дам несколько служебных функций, которые будут использоваться в примерах ниже.
Решения не идеальные, но простые. Например, там можно найти как сохранить файл в кодировке UTF-8 без BOM, получить путь к файлу PowerShell.exe и др. Решения всегда можно улучшать.
Плохой пример
И так, как обычно выполняется запуск приложений из кода встроенного языка? Правильно - с помощью процедуры "ЗапуститьПриложение()":
ЗапуститьПриложение(<СтрокаКоманды>, <ТекущийКаталог>, <ДождатьсяЗавершения>, <КодВозврата>)
Первым параметром передаем строку команды запуска и параметры по необходимости. Во втором устанавливаем каталог, что не обязательно. Третий параметр позволяет дождаться завершения приложения, а четвертый получить код возврата. Обычно если код возврата не равен 0, значит что-то пошло не так.
Вернемся к нашей задаче. Выполним команду "ping infostart.ru".
// Временный файл для сохранения вывода результата на консоль
ПутьКФайлуРезультата = ПолучитьИмяВременногоФайла("log");;
// Формируем текст команды для запуска. Здесь может быть произвольный текст команды или даже целый скрипт.
КомандаЗапуска = "ping infostart.ru > ""{ПутьКФайлуРезультата}""";
КомандаЗапуска = СтрЗаменить(КомандаЗапуска, "{ПутьКФайлуРезультата}", ПутьКФайлуРезультата);
// Сохраняем команду запуска в файл исполняемого скрипта BAT.
// Это нужно для обхода различных ограничений работы процедуры "ЗапуститьПриложение",
// которая некоторые команды не может обрабатывать корректно
ФайлСкрипта = ПолучитьИмяВременногоФайла("bat");
ТекстовыйДокумент = Новый ТекстовыйДокумент;
ТекстовыйДокумент.УстановитьТекст(КомандаЗапуска);
ТекстовыйДокумент.Записать(ФайлСкрипта, "cp866");
// Запускаем сформированный BAT-скрипт на выполнение
КодВозврата = Неопределено;
ЗапуститьПриложение(ФайлСкрипта,, Истина, КодВозврата);
Если КодВозврата = 0 Тогда
// Если ошибок при выполнении нет, то получаем строку вывода результата
ТекстовыйДокуменнт = Новый ТекстовыйДокумент;
ТекстовыйДокуменнт.Прочитать(ПутьКФайлуРезультата, "cp866");
ТекстРезультат = ТекстовыйДокуменнт.ПолучитьТекст();
Иначе
// Иначе вызываем исключение
ВызватьИсключение "Ошибка выполнения команды. Код возврата: " + КодВозврата;
КонецЕсли;
// Удаляем временные файлы
УдалитьФайлЕслиВозможно(ФайлСкрипта);
УдалитьФайлЕслиВозможно(ПутьКФайлуРезультата);
Вариант рабочий и позволяет запускать большую часть команд и целых скриптов. Комментарии даны исчерпывающие. Используются только штатные возможности платформы 1С для выполнения команд. И это плюс. Но у этого подхода есть и большой минус - низкая надежность и непредсказуемость результата в тех случаях, когда точно не известно, как долго будет команда выполняться и не потребует ли приложение интерактивных действий пользователя. Если интерактивные действия потребуются на стороне сервера, где мы об этом даже и не узнаем, то сеанс 1С может подвиснуть на всегда.
В качестве решения может быть реализация таймаута выполнения команды, но для процедуры "ЗапуститьПриложение" такое реализовать практически невозможно. Можно, конечно, попробовать запустить приложение и ожидать файла-результата какое-то время, но это решит проблему частично. Зависания не будет, но приложение будет запущено и дальше, ожидая внешней команды.
WScript.Shell нас спасет
Мы же в среде Windows. Давайте используем ее средства в виде COM-Объекта "WScript.Shell":
Теперь приложение не зависнет и в случае чего мы сможем завершить его работу принудительно.
Мое имя Power, PowerShell
Пойдем дальше и сделаем наше решение более интересным. Что, если нам нужно запустить не простую команду CMD или BAT'ник, а команду или скрипт PowerShell, но на тех же условиях! Для примера опять же оставим запуск бесконечного пинга :)
Фактически, теперь можно запускать любые скрипты хоть CMD, хоть PowerShell. А если сильно хочется, то можно и GIT Bash под Windows использовать или даже подсистему WSL под Windows 10. Но все это уже другая история. Осталось поговорить про Linux.
*.nix is my own
Под *.nix привычнее всего использовать bash для выполнения команд. Можно ли использовать bash из встроенного языка платформы 1С? Да, можно. Можно запускать как отдельные команды, так и целые скрипты. При этом получать результат и, что самое главное, делать это безопасно как в примерах выше.
В далеком 2015 году на Mista была поднята тема "Запуск файлов *.sh в самой 1с". Возможно, информация ниже будет ответом на вопрос, т.к. точного ответа так там и нет. Но может быть я не прав :)
Запускаем bash-скрипты
Первое, что нужно понять - в Linux нет COM-объектов. Значит придется обойтись штатными средствами платформы 1С. Выглядеть это будет так:
// Задаем параметры
ТаймаутВыполненияСек = 10;
ТекстКомандыДляВыполнения = ТекстКоманды;
// Подготавливаем временные файлы
ФайлСкрипта = ПолучитьИмяВременногоФайла("sh");
ФайлРезультатСкрипта = ПолучитьИмяВременногоФайла("log");
// Подготавливаем файл скрипта
ПереносСтроки = Символы.ПС;
ЗаписьТекста = Новый ЗаписьТекста(ФайлСкрипта, КодировкаТекста.UTF8, ПереносСтроки);
ЗаписьТекста.ЗаписатьСтроку("#!/bin/sh");
ЗаписьТекста.ЗаписатьСтроку("");
ЗаписьТекста.Записать(ТекстКомандыДляВыполнения);
ЗаписьТекста.ЗаписатьСтроку("");
ЗаписьТекста.Закрыть();
// Конвертируем файл скрипта, удаляя все следы "Windows"
ЗапуститьПриложение("dos2unix '" + ФайлСкрипта + "'",, Истина);
// Формируем команды и выполняем с указанным таймаутом в секундах
КомандаЗапускаСкрипта = "timeout " + XMLСтрока(ТаймаутВыполненияСек) + "s /bin/bash '"
+ ФайлСкрипта + "' >> '" + ФайлРезультатСкрипта + "'";
КодВозврата = Неопределено;
ЗапуститьПриложение(КомандаЗапускаСкрипта, , Истина, КодВозврата);
// Получаем результатирующий вывод приложения
ТекстовыйДокумент = Новый ТекстовыйДокумент;
ТекстовыйДокумент.Прочитать(ФайлРезультатСкрипта, КодировкаТекста.UTF8);
РезультатВыполнения = ТекстовыйДокумент.ПолучитьТекст();
// Очистка данных
УдалитьФайлЕслиВозможно(ФайлСкрипта);
УдалитьФайлЕслиВозможно(ФайлРезультатСкрипта);
// Обработка результата выполнения
Если НЕ КодВозврата = 0 Тогда
ВызватьИсключение "Не удалось выполнить команду. Код возврата: " + КодВозврата;
КонецЕсли;
Здесь мы устанавливаем произвольный текст команды для Bash и выполняем его. В чем то прием аналогичен тому, что мы делали для Windows. Комментарии даны полные, но на паре моментов остановимся подробнее.
Некоторые нюансы
Первое, что может показаться интересным - это вызов "dos2unix" для сформированного ранее файла скрипта. Зачем это нужно? В операционной системе Windows по умолчанию для переноса строк в файлах используется последовательность символов "\r\n" (перевод каретки + перенос строки). В Unix-подобных системах используется только символ переноса строки "\n". Для Windows символ перевода каретки был добавлен для того, чтобы в древние времена можно было отправлять на печать текст без каких-либо особых драйверов. Телетайп навсегда! Этот легаси остался и по сей день и никому не мешает, ну почти.
Нам же он может сильно помешать по двум причинам:
- Несмотря на то, что сервер 1С установлен под Linux - все равно стандартный перенос строки платформа формирует как "\r\n". Даже если для таких классов как "ЗаписьТекста" или "ТекстовыйДокумент" устанавливать символ переноса строки другой (например, Симполв.ПС или по коду символа переноса строки равному 10), то платформа все равно использует символ перевода каретки.
- Еще может быть ситуация, когда текст команды для выполнения передается с клиента под управлением Windows на сервер под Linux и символы перевода каретки будут там присутствовать.
Мешать они будут потому что Bash не понимает что с ними делать. Если в файле скрипта будет содержаться символ "\r", то мы получим ошибку:
$'\r': command not found
Вот, например, обсуждение подобной проблемы. Так вот, с помощью команды "dos2unix" можно убрать все символы, несовместимые содержимым скрипта Bash и сделать его корректным. На скриншоте ниже показан пример преобразования содержимого скрипта. (Да, я использую Notepad++ даже в Linux, если Вы его узнали).
Конечно, тут есть минус - пакет "dos2unix" должен быть установлен на сервер. Сделать это проще простого. Вот так это, например, выглядит для Ubuntu:
sudo apt install dos2unix
Но это не единственная особенность. Для того, чтобы ограничить время выполнения команды мы используем штатные возможности - команду timeout. С ее помощью мы можем указать сколько времени выделяется для выполнения команды / скрипта.
timeout 10s bash 'путь к файлу скрипта или команда'
В примере выше мы выполняем некоторый скрипт с таймаутом 10 секунд. Подробнее смотрите мануал :)
Теперь Вы можете запускать приложения, скрипты или команды с помощью Bash из Linux безопасным способом.
Скрытая угроза
Частным случаем запуска процессов небезопасным образом является использование COM-объектов. Не все COM-объекты порождают процессы, которые нужно контролировать. Но, например, всеми любимые Word и Excel, которые часто до сих пор ставят на сервера, делают именно так. Они запускают соответствующий процесс "word.exe" или "excel.exe" и дальше управляют им. Не говоря уже про чистоту использования лицензий (эта тема касалась здесь), это еще и не всегда безопасно с точки зрения работы этих приложений.
Процедура СоздатьИЗаполнитьДокумент(ПутьКШаблону)
ПриложениеWord = Новый COMОбъект("Word.Application");
ОбъектДокумента = ПриложениеWord.Documents.Add(ПутьКШаблону);
// ... тут какие-то действия по заполнению и сохранению ...
// Закрываем приложение
ОбъектДокумента.Application.Quit();
КонецПроцедуры
Тут все просто - в момент создания объекта документа для его обработки как-раз и создается процесс "word.exe". После завершения всех необходимых действий, метод "Quit" закрывает приложение и все работает как надо. Но что, если в момент выполнения алгоритма заполнения произойдет ошибка? Правильно, метод "Quit" не будет выполнен и процесс Word'а останется "висеть". Конечно, можно попытаться обезопасить себя как это рекомендует стандарт разработки 1С и добавить "попытку":
Процедура СоздатьИЗаполнитьДокумент(ПутьКШаблону)
ПриложениеWord = Неопределено;
ОбъектДокумента = Неопределено;
Попытка
ПриложениеWord = Новый COMОбъект("Word.Application");
ОбъектДокумента = ПриложениеWord.Documents.Add(ПутьКШаблону);
// ... тут какие-то действия по заполнению и сохранению ...
// Закрываем приложение
ОбъектДокумента.Application.Quit();
Исключение
Попытка
ОбъектДокумента.Application.Quit();
ОбъектДокумента = Неопределено;
Исключение
ОбъектДокумента = Неопределено;
КонецПопытки;
ПриложениеWord = Неопределено;
КонецПопытки;
КонецПроцедуры
Тут мы пытаемся закрыть приложение и освободить все связанные ресурсы. Часть кейсов это решит, но, к сожалению, не все. Если, например, в момент работы с COM-объектом возникнет не исключение, а падение рабочего процесса, то никакие действия по освобождению ресурсов выполнены не будут и процесс останется висеть в памяти до перезагрузки сервера / компьютера. Ну или пока не будет "убит" принудительно.
К сожалению, при непредвиденном завершении рабочего процесса мы особо ничего не можем сделать для контроля завершения всех запущенных процессов из кода встроенного языка. Все-таки это нештатная ситуация и решать ее можно только нештатными средствами. Почти все "костыли" по этой теме дают сбои в той или иной степени.
А у Вас на сервере бывают ситуации с десятком запущенный процессов "excel.exe"? :)
Альтернативные подходы
В качестве многоточия для этой темы хотел бы упомянуть и альтернативные способы решения проблем с освобождением ресурсов запущенных из 1С процессов:
- Организовать bat/ps/bash скрипт, который будет ночью "убивать" определенные процессы. Например, те же "excel.exe". Костыль? Да. Работает? Да.
- Сохранять в базе 1С (например, в регистре сведений) информацию о запущенных процессах (идентификатор процесса, имя и др.). Ночью или в другое время запускать регл. задание, которое будет проверять завершение этих процессов. Костыль? Да. Работает? Да.
- Перезагружать сервер с периодичностью в несколько дней. О сколько проблем можно решить! :) Костыль? Да. Работает? Да :)))
Список можно продолжать и дальше. Но возможно правильным вариантом будет - поиск решения задач иным способом, а не запуском сторонних процессов. Если такое, конечно, возможно.
Удачи, друзья!
Всем чистого кода, хороших решений и меньше "костылей"! До следующих встреч!
А как Вы работаете со сторонними процессами из 1С?
Другие ссылки
- Командный интерпретатор для 1С - инструмент для выполнения команд CMD / PowerShell из 1С
-
1C linux Centos 7. ЗапуститьПриложение, работа с "1cv8" из 1С на сервере
Авторские разработки
-
Транслятор запросов 1С в SQL - инструмент для трансляции запросов платформы 1С в SQL, а также их диагностики.
-
Просмотр и анализ структуры базы данных (отчет на СКД) - отчет для просмотра и анализа структуры базы данных с поддержкой файловых баз (ограниченный режим), а также баз на SQL Server и PostgreSQL.
-
Просмотр и анализ журнала регистрации (отчет на СКД) - отчет на базе системы компоновки данных (СКД) для просмотра записей журнала регистрации.
-
История работы пользователей (отчет на СКД) - отчет для просмотра истории работы пользователей (СКД, просмотр для любого пользователя).
-
Экспорт журнала регистрации. Набор инструментов (приложения + исходный код) - набор инструментов для экспорта данных журнала регистрации во внешние хранилища для Windows и Linux. Готовые приложения и исходный код.
-
Технические проверки данных регистров бухгалтерии (отчет на СКД) - отчет для технических проверок данных бухгалтерских регистров.
-
Путеводитель по истории релизов - отчет по истории выпуска релизов продуктов фирмы "1С" и анализа информации по обновлениям.
- Помощник работы с идентификаторами объектов - инструмент для расширенного анализа идентификаторов объектов.
-
Информация о пользователях информационной базы (отчет на СКД) - два простых отчета по пользователям информационной базы и информации по ним.
-
Анализ производительности APDEX (бесплатный) - отчет для просмотра и анализа замеров производительности в конфигурациях на базе БСП.
-
Обозреватель криптографии - отчет для просмотра доступных провайдеров и сертификатов криптографии на сервере и клиенте.
-
Пакетная выгрузка / загрузка внешних отчетов и обработок - пакетная выгрузка / загрузка внешних отчетов и обработок для массовый манипуляций с ними.
-
Мастер полнотекстового поиска - набор инструментов для работы с полнотекстовым индексом платформы 1С. Стандартные и расширенные возможности.
-
Командный интерпретатор для 1С - инструмент для выполнения команд CMD / PowerShell из 1С