Перечень установленных принтеров в 1С 8.3: COM, WMI, реестр и управляемые формы

· 9 мин

Содержание

Получить список принтеров в 1С проще всего через COM-объект Windows: Новый COMОбъект("WScript.Network").EnumPrinterConnections() отдаёт имена всех подключённых принтеров, а WMI-запрос к классу Win32_Printer добавляет драйвер, порт, признак принтера по умолчанию и состояние. Код выполняется на клиенте под Windows. В веб-клиенте COM недоступен.

Задача общая для платформы 1С:Предприятие 8.3 и не зависит от конфигурации: код одинаково работает во внешней обработке, в расширении и в собственной форме Бухгалтерии, ЗУП или УТ. Нужен он, когда документы печатаются без диалога на заранее выбранный принтер: этикетки, чеки склада, пачки накладных.

Что 1С умеет с принтерами сама, без COM

Встроенный язык работает с принтером через табличный документ. У объекта ТабличныйДокумент есть свойство ИмяПринтера и метод Напечатать(). Платформа берёт список принтеров из Windows и показывает его в стандартном диалоге печати.

Отдельной функции, которая вернула бы перечень принтеров массивом, в привычных релизах платформы нет. Поэтому разработчики берут список у операционной системы. Если у вас свежая платформа, наберите слово «принтер» в поиске синтакс-помощника: так вы сразу увидите, что доступно именно в вашем релизе.

Что даёт платформа без внешних объектов:

  • 🖨️ диалог печати со списком всех принтеров пользователя;
  • 📝 свойство ИмяПринтера — куда печатать, если диалог не показывается;
  • ⚙️ свойства копий, ориентации и размера страницы у табличного документа;
  • 🔁 печать без диалога через Напечатать(РежимИспользованияДиалогаПечати.НеИспользовать).

Получается, что выбрать принтер пользователь может сам в диалоге, а программно перечислить принтеры — только через Windows. Ниже три способа это сделать.

СпособЧто возвращаетКогда братьОграничения
WScript.Networkимя и портнужен только список имённет драйвера и статуса
WMI, Win32_Printerимя, драйвер, порт, «по умолчанию», офлайннужна фильтрация и подробностислужба WMI должна работать
Реестр, reg query, PowerShellимена из настроек пользователяCOM запрещён политикойразбор текста, кодировка

Список принтеров через объект WScript.Network

Объект WScript.Network входит в Windows Script Host и есть в любой современной Windows. Метод EnumPrinterConnections() возвращает коллекцию, где элементы идут парами: сначала порт, затем имя принтера. Поэтому имена лежат на нечётных позициях: 1, 3, 5 и так далее.

&НаКлиенте
Функция СписокПринтеровWSH()

	Результат = Новый Массив;

	#Если НЕ ВебКлиент Тогда
	Сеть = Новый COMОбъект("WScript.Network");
	Подключения = Сеть.EnumPrinterConnections();

	// элементы идут парами: [0] — порт, [1] — имя принтера
	Для Индекс = 0 По Подключения.Count() / 2 - 1 Цикл
		Результат.Добавить(Подключения.Item(Индекс * 2 + 1));
	КонецЦикла;
	#КонецЕсли

	Возврат Результат;

КонецФункции

Проверим индексы на примере. В системе три принтера, Count() вернёт 6. Цикл пройдёт значения 0, 1, 2 и возьмёт элементы 1, 3 и 5 — ровно три имени.

У этого объекта есть ещё два полезных метода:

  1. SetDefaultPrinter("Имя") — назначает принтер по умолчанию для текущего пользователя Windows.
  2. AddWindowsPrinterConnection("\\Сервер\Принтер") — подключает сетевой принтер.

Смена принтера по умолчанию действует на все программы пользователя, не только на 1С. Делайте это только с его согласия.

Как получить список принтеров через WMI-запрос

WMI даёт гораздо больше, чем имя. Класс Win32_Printer хранит драйвер, порт, сетевой ли принтер, отключён ли он и выбран ли по умолчанию. Запрос пишется на языке WQL, он похож на SQL.

&НаКлиенте
Функция ТаблицаПринтеровWMI()

	Результат = Новый Массив;

	#Если НЕ ВебКлиент Тогда
	WMI = ПолучитьCOMОбъект("winmgmts:\\.\root\cimv2");
	Выборка = WMI.ExecQuery("SELECT Name, DriverName, PortName, Default, Network, WorkOffline FROM Win32_Printer");

	Для Каждого Принтер Из Выборка Цикл
		Описание = Новый Структура;
		Описание.Вставить("Имя", Принтер.Name);
		Описание.Вставить("Драйвер", Принтер.DriverName);
		Описание.Вставить("Порт", Принтер.PortName);
		Описание.Вставить("ПоУмолчанию", Принтер.Default);
		Описание.Вставить("Сетевой", Принтер.Network);
		Описание.Вставить("Отключен", Принтер.WorkOffline);
		Результат.Добавить(Описание);
	КонецЦикла;
	#КонецЕсли

	Возврат Результат;

КонецФункции

Фильтр можно поставить прямо в запрос. Несколько готовых условий:

  • 📌 только принтер по умолчанию — WHERE Default = TRUE;
  • 🌐 только сетевые — WHERE Network = TRUE;
  • 🚫 убрать отключённые — WHERE WorkOffline = FALSE;
  • 🏷️ только термопринтеры конкретной модели — WHERE DriverName LIKE '%Zebra%'.

Последний фильтр полезен на складе. Виртуальные «принтеры» вроде Microsoft Print to PDF, OneNote и факса тоже попадают в Win32_Printer. Отсекайте их по драйверу или по порту: у PDF-принтера порт PORTPROMPT:, у реальных устройств — USB, IP или сетевой путь.

Тот же запрос можно отправить на другой компьютер, например на сервер печати. Для этого вместо точки в строке подключения укажите имя машины: winmgmts:\\PRINTSRV\root\cimv2. Удалённый WMI требует прав на целевом компьютере и открытого доступа в брандмауэре, поэтому для обычного пользователя такой вызов чаще всего завершится отказом в доступе. Для учёта парка принтеров это удобно, для повседневной печати не нужно.

Проверить запрос до того, как писать код, удобно в PowerShell: Get-CimInstance Win32_Printer | Select-Object Name, DriverName, PortName, Default. Если там список пустой, 1С тоже ничего не получит.

Список принтеров в управляемом приложении: где выполнять код

В управляемых формах главный вопрос — на какой стороне работает процедура. Принтеры, которые видит пользователь, установлены на его компьютере. Значит, код получения списка должен идти с директивой &НаКлиенте.

Если вызвать тот же код &НаСервере, вы получите принтеры сервера 1С. В клиент-серверном варианте служба сервера работает под отдельной учётной записью, и у неё принтеров пользователя нет. Отсюда классическая жалоба: «в файловой базе список был, на сервере пустой».

Где запущен кодЧто вернётКомментарий
Тонкий или толстый клиент, Windowsпринтеры пользователяосновной сценарий
Сервер 1С на Windowsпринтеры учётной записи службыпочти всегда не то, что нужно
Веб-клиентничегоCOMОбъект недоступен, нужна директива #Если НЕ ВебКлиент
Клиент под Linux или macOSничего через COMиспользуйте команды ОС, см. следующий раздел
Терминальный сервер (RDP)принтеры сеанса, включая перенаправленныеимена меняются между сеансами

Вывод списка в форму и выбор принтера

Список удобно показать пользователю через СписокЗначений и немодальный выбор. Так код работает и при отключённой модальности:

&НаКлиенте
Процедура ВыбратьПринтер(Команда)

	Список = Новый СписокЗначений;
	Для Каждого Имя Из СписокПринтеровWSH() Цикл
		Список.Добавить(Имя);
	КонецЦикла;

	Оповещение = Новый ОписаниеОповещения("ВыборПринтераЗавершение", ЭтотОбъект);
	Список.ПоказатьВыборЭлемента(Оповещение, "Выберите принтер");

КонецПроцедуры

&НаКлиенте
Процедура ВыборПринтераЗавершение(ВыбранныйЭлемент, ДополнительныеПараметры) Экспорт

	Если ВыбранныйЭлемент = Неопределено Тогда
		Возврат;
	КонецЕсли;
	ИмяПринтера = ВыбранныйЭлемент.Значение; // реквизит формы

КонецПроцедуры

Терминальный сервер и перенаправленные принтеры

При работе через RDP Windows добавляет к имени локального принтера пользователя хвост вроде (перенаправлено 3). Номер меняется при каждом входе. Если сохранить полное имя, завтра печать уйдёт в никуда.

Решение — сохранять основу имени без хвоста, а при печати искать в текущем списке принтер, имя которого начинается с этой основы. Функция СтрНачинаетсяС() подходит для этого лучше всего.

Реестр Windows и командная строка как запасной путь

Бывает, что WScript.Network запрещён групповой политикой, а WMI на машине сломан. Тогда список берут из реестра или из вывода системных команд.

Где Windows хранит данные о принтерах:

  • 👤 HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Devices — все принтеры текущего пользователя, имя каждого принтера записано как имя параметра;
  • 🖥️ HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Printers — локальные принтеры компьютера, по разделу на принтер;
  • ⭐ HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Windows, параметр Device — принтер по умолчанию в виде «Имя,winspool,порт».

Принтер по умолчанию читается одной строкой через WScript.Shell:

Оболочка = Новый COMОбъект("WScript.Shell");
Значение = Оболочка.RegRead("HKCU\Software\Microsoft\Windows NT\CurrentVersion\Windows\Device");
ПринтерПоУмолчанию = Лев(Значение, СтрНайти(Значение, ",") - 1);

Полный перечень удобнее получить командой и прочитать из файла. Пример через PowerShell, который вернёт по одному имени на строку:

&НаКлиенте
Функция СписокПринтеровPowerShell()

	ИмяФайла = ПолучитьИмяВременногоФайла("txt");
	Команда = "powershell -NoProfile -Command ""Get-Printer | ForEach-Object { $_.Name } | Out-File -Encoding UTF8 '"
		+ ИмяФайла + "'""";
	ЗапуститьПриложение(Команда, , Истина);

	Текст = Новый ТекстовыйДокумент;
	Текст.Прочитать(ИмяФайла, КодировкаТекста.UTF8);

	Результат = Новый Массив;
	Для НомерСтроки = 1 По Текст.КоличествоСтрок() Цикл
		Имя = СокрЛП(Текст.ПолучитьСтроку(НомерСтроки));
		Если ЗначениеЗаполнено(Имя) Тогда
			Результат.Добавить(Имя);
		КонецЕсли;
	КонецЦикла;

	УдалитьФайлы(ИмяФайла);
	Возврат Результат;

КонецФункции

Командлет Get-Printer есть в Windows 8, Windows Server 2012 и новее. Вывод в файл с явной кодировкой UTF-8 спасает русские имена принтеров: без этого кириллица превращается в «кракозябры». Если используете reg query, учтите, что консоль Windows пишет в кодировке cp866.

На клиенте под Linux тот же приём работает с командой lpstat -a: она выводит принтеры CUPS, которые принимают задания. Принтер по умолчанию показывает lpstat -d.

Третий параметр ЗапуститьПриложение — ждать завершения программы. Без него 1С начнёт читать файл раньше, чем команда его запишет. В новых релизах платформы есть и асинхронный вариант этого метода.

Печать на выбранный принтер без диалога

Список принтеров почти всегда нужен ради одного: печатать сразу на нужное устройство. После выбора имя подставляют в табличный документ:

ТабДок.ИмяПринтера = ИмяПринтера;
ТабДок.КоличествоЭкземпляров = 1;
ТабДок.Напечатать(РежимИспользованияДиалогаПечати.НеИспользовать);

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

Чтобы пользователю не выбирать принтер каждый раз, сохраните его выбор в серверной процедуре через ХранилищеОбщихНастроек.Сохранить(), а при открытии формы читайте через ХранилищеОбщихНастроек.Загрузить(). Перед печатью сверяйте сохранённое имя с текущим списком: принтер могли удалить или переименовать.

Перед печатью без диалога
Выполнено: 0 / 5

Ошибки при получении списка принтеров

Большинство сбоев видно по тексту сообщения. Разберите свой случай:

Диагностика: выберите симптом

Отдельно о сетевых принтерах. Принтер, подключённый через \\Сервер\Принтер, попадает в список, даже если сервер печати сейчас выключен. Проверяйте свойство WorkOffline из WMI до отправки задания, иначе пользователь узнает о проблеме только когда пойдёт за пустым лотком.

Частые вопросы

Можно ли получить список принтеров в веб-клиенте 1С?
Нет. Браузер не даёт сайту доступ к принтерам компьютера, а COM в веб-клиенте недоступен. Пользователь выбирает принтер в диалоге печати браузера.
Нужны ли права администратора, чтобы прочитать список?
Нет. И WScript.Network, и запрос к Win32_Printer работают с правами обычного пользователя. Администратор нужен только для установки драйверов и проверки репозитория WMI.
Почему в списке есть «Microsoft Print to PDF» и «Fax»?
Windows считает их принтерами, потому что они ставятся через тот же механизм драйверов. Отфильтруйте их по имени драйвера или порту, если нужна только физическая печать.
Как узнать, какой принтер стоит по умолчанию?
Через WMI: условие WHERE Default = TRUE в запросе к Win32_Printer. Без WMI — прочитать параметр Device в разделе реестра текущего пользователя.
Работает ли этот код во внешней обработке?
Да. Код клиентский и от конфигурации не зависит. Если в базе включён безопасный режим для внешних обработок, создание COM-объектов может быть запрещено, тогда обработку нужно подключить без ограничений.