При организации автоматической отправки отчетов и печатных форм из 1С 7.7 по электронной почте в терминальной среде (Remote Desktop Services) разработчики часто сталкиваются с нестабильным поведением: процесс срабатывает через раз, под учетной записью администратора все функционирует без сбоев, а у рядовых пользователей файл вложения не сохраняется во временную директорию или не прикрепляется к письму.
Разберем по шагам, почему возникает эта ошибка, как локализовать проблему и какими способами можно гарантировать надежную отправку корреспонденции.
Проанализируем ключевые факторы, из-за которых процесс сохранения и отправки дает сбой у непривилегированных пользователей:
%TEMP% для каждого сеанса принимает вид %TEMP%\1, %TEMP%\2 и так далее. Если 1С или внешняя компонента (например, Dialmail.dll) вычисляет путь один раз или обращается к общему профилю без учета номера текущей терминальной сессии, пути сохранения и прикрепления расходятся.C:\Temp или в системную папку платформы BIN у обычного пользователя срабатывает перенаправление VirtualStore либо возникает отказ в доступе. Администратор при этом имеет полные права, поэтому под его учетной записью сбоев не возникает.Dialmail.dll при включенном протоколировании событий без явного указания пути пытается создать лог-файл в рабочем каталоге 1С (обычно Program Files). Отсутствие прав на запись в эту папку приводит к сбою инициализации почтового клиента.Выясним, успевает ли файл сформироваться на диске до момента его отправки. Для этого организуем программный контроль наличия файла с помощью встроенного объекта ФС и метода СуществуетФайл перед обращением к почтовому объекту.
Посмотрим на пример организации ожидания готовности файла:
// Формирование файла вложения
ИмяВложения = КаталогВременныхФайлов + "Счет_" + СокрЛП(НомерДок) + ".pdf";
Таб.Напечатать(0); // или экспорт в файл
// Ожидание физического создания файла на диске
МаксОжидание = 10; // секунд
Счетчик = 0;
Пока (ФС.СуществуетФайл(ИмяВложения) = 0) И (Счетчик < МаксОжидание) Цикл
// Пауза через пустой цикл или системный вызов
глПауза(1);
Счетчик = Счетчик + 1;
КонецЦикла;
Если ФС.СуществуетФайл(ИмяВложения) = 0 Тогда
Сообщить("Ошибка: файл вложения не сформирован за отведенное время!", "!");
Возврат;
КонецЕсли;
Такой подход полностью исключает ошибку, когда почтовый модуль пытается прикрепить еще не созданный или заблокированный на запись файл.
Чтобы избежать путаницы с путями %TEMP% в различных сессиях RDS, рекомендуется сохранять файлы в каталог пользователя 1С. К этой папке у терминального пользователя гарантированно настроены полные права NTFS, и путь не зависит от смены номеров сеансов терминала.
Используем встроенную функцию КаталогПользователя():
ПутьККаталогу = КаталогПользователя();
ИмяФайла = ПутьККаталогу + "Отчет.xls";
// Сохраняем таблицу
Таб.Записать(ИмяФайла, "XLS");
Если ФС.СуществуетФайл(ИмяФайла) = 1 Тогда
// Передаем сформированный путь в компоненту отправки
ОтправитьПисьмо(ИмяФайла);
Иначе
Сообщить("Не удалось сохранить файл в каталоге: " + ИмяФайла, "!");
КонецЕсли;
Если для работы требуется использование именно системной переменной %TEMP%, отключим создание раздельных папок сеансов в политиках сервера:
gpedit.msc) на терминальном сервере.gpupdate /force в командной строке.После этого путь к временной папке для каждого пользователя станет постоянным и будет указывать строго на C:\Users\<ИмяПользователя>\AppData\Local\Temp.
Если внешняя библиотека Dialmail.dll продолжает работать нестабильно в среде терминального сервера, заменим ее вызовом стандартных почтовых классов .NET Framework через COM-обертку. Это решает проблему совместимости со сторонними DLL и обеспечивает корректную работу с путями.
Рассмотрим реализацию функции подготовки вложения и процедуры отправки:
Функция ПолучитьАтачмент(file, Врап)
data = Врап.СоздатьОбъект("System.Net.Mail.Attachment", file);
disposition = data.ContentDisposition;
IO_File = Врап.ПолучитьТип("System.IO.File");
disposition.CreationDate = IO_File.GetCreationTime(file);
disposition.ModificationDate = IO_File.GetLastWriteTime(file);
disposition.ReadDate = IO_File.GetLastAccessTime(file);
Возврат data;
КонецФункции
Процедура ОтправитьПисьмо(СписокФайлов, ТемаПисьма, smtp, Кому, ОтКого, login, password, ПолноеИмяОтКого)
врап = СоздатьОбъект("NetObjectToIDispatch45");
врап.УстЭтоСемерка();
port = врап.ChangeType("System.Int32", 25);
mail = врап.СоздатьОбъект("System.Net.Mail.SmtpClient", smtp, port);
истина = врап.ChangeType("System.Boolean", "true");
ложь = врап.ChangeType("System.Boolean", "false");
mail.UseDefaultCredentials = ложь;
mail.DeliveryMethod = врап.ПолучитьТип("System.Net.Mail.SmtpDeliveryMethod").Network;
Message = врап.СоздатьОбъект("System.Net.Mail.MailMessage");
MailAddressType = врап.ПолучитьТип("System.Net.Mail.MailAddress");
Message.From = врап.СоздатьОбъект(MailAddressType, ОтКого, ПолноеИмяОтКого);
Message.To.Add(врап.СоздатьОбъект(MailAddressType, Кому));
Message.Subject = ТемаПисьма;
Message.IsBodyHtml = истина;
Message.Body = "";
Для сч = 1 По СписокФайлов.РазмерСписка() Цикл
Файл = СписокФайлов.ПолучитьЗначение(сч);
Message.Attachments.Add(ПолучитьАтачмент(Файл, врап));
КонецЦикла;
Попытка
mail.Send(Message);
Message.Dispose();
mail.Dispose();
Сообщить("Письмо успешно отправлено!");
Исключение
Сообщить("Ошибка отправки: " + ОписаниеОшибки(), "!");
КонецПопытки;
КонецПроцедуры
Для корректного формирования временного пути в .NET используем класс System.IO.Path:
врап = СоздатьОбъект("NetObjectToIDispatch45");
Patch = врап.ПолучитьТип("System.IO.Path");
Путь = Patch.GetTempPath();
СпзФайлов = СоздатьОбъект("СписокЗначений");
Файл = Patch.Combine(Путь, "Прайс.xls");
// Формируем отчет и сохраняем
Таб.Записать(Файл, "XLS");
СпзФайлов.ДобавитьЗначение(Файл);
ОтправитьПисьмо(СпзФайлов, "Прайс-лист", "smtp.server.ru", "client@mail.ru", "info@company.ru", "user", "pass", "Компания");
// Удаляем временный файл после отправки
ФС.УдалитьФайл(Файл);
Если нет возможности регистрировать сторонние библиотеки или использовать .NET-врапперы, отправку почты из 1С 7.7 можно организовать через системный компонент Windows CDO.Message. Он присутствует на всех версиях Windows Server и не требует наличия прав администратора:
Почта = СоздатьОбъект("CDO.Message");
Конфиг = СоздатьОбъект("CDO.Configuration");
Поля = Конфиг.Fields;
Схема = "http://schemas.microsoft.com/cdo/configuration/";
Поля.Item(Схема + "sendusing").Value = 2; // Отправка по сети через SMTP
Поля.Item(Схема + "smtpserver").Value = "smtp.yourserver.ru";
Поля.Item(Схема + "smtpserverport").Value = 25;
Поля.Item(Схема + "smtpauthenticate").Value = 1; // Базовая аутентификация
Поля.Item(Схема + "sendusername").Value = "login";
Поля.Item(Схема + "sendpassword").Value = "password";
Поля.Update();
Почта.Configuration = Конфиг;
Почта.To = "receiver@domain.com";
Почта.From = "sender@domain.com";
Почта.Subject = "Документы из 1С";
Почта.TextBody = "Во вложении высылаем документы.";
Почта.AddAttachment(ИмяФайла);
Попытка
Почта.Send();
Сообщить("Сообщение успешно отправлено через CDOSYS.");
Исключение
Сообщить("Ошибка при отправке: " + ОписаниеОшибки(), "!");
КонецПопытки;
Если причина сбоя остается неясной, запустим утилиту Process Monitor (ProcMon) под учетной записью администратора и настроим фильтр по процессу 1cv7s.exe:
Process Name is 1cv7s.exe.Result is ACCESS DENIED или NAME NOT FOUND.Журнал утилиты покажет точный путь к файлу, к которому обращается программа, и зафиксирует событие блокировки прав доступа или попытку записи по несуществующему пути каталога.