Массовая печать в Windows

  • Tutorial
Иногда нужно быстро распечатать много картинок с котиками документов, а открывать для этого каждый файл совсем не хочется. Первым делом напрашивается использование контекстного меню проводника, но у этого способа есть свои ограничения и нюансы. Поэтому пришлось искать альтернативу. За подробностями — прошу под кат.

Анализируем ситуацию и собираем данные


Тема пакетной печати не раз освещалась в трудах великих учёных интернет-статьях. Например, в этой и этой.

Мы же начнем с выяснения того, какой функционал нужен конечным пользователям. В результате общения с коллегами получися такой список:

  • печатать нужно только XML-файлы;
  • форматирование для XML не требуется;
  • на бумаге, помимо содержимого, должно быть указано и имя печатаемого файла;
  • файлы должны быть отсортированы по имени, чтобы было удобно подшивать бумажные листы в архив.

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

Но у такого способа есть, как минимум, два недостатка:

  1. нельзя напечатать больше 15 файлов за раз;
  2. файлы печатаются в случайном порядке (возможно, логика все же есть, но я ее не нашел), а не так, как они отсортированы в проводнике.

Первый недостаток легко устраним твиком реестра. Для решения второго есть рекомендации в виде танцев с бубном, но в нашей среде боги суровы и эти обряды не помогли.
Есть готовые сторонние решения (ссылки на статьи с информацией о них даны выше). Но при коммерческом использовании за эти продукты придется заплатить, к тому же всегда приятно забить элегантный костыль и изобрести очередной велосипед сделать что-то своими руками.

Выбираем инструмент и разрабатываем решение


Примечание. Чтобы не переводить бумагу, на этапе подготовки и тестирования скрипта удобно использовать виртуальный принтер. Меня устроил штатный Microsoft XPS Document Writer, но есть еще PDF24 Creator, doPDF, CutePDF Writer — как говорится, на вкус и цвет…

В качестве языка был выбран PowerShell. В базовой комплектации скрипт выглядит так:

Вариант 0
$FolderToPrint = "\\server\share\Folder"
$FileMask = "*.xml"
$FolderToPrint | Get-ChildItem -File -Filter $FileMask | Sort-Object Name | ForEach-Object {
    Write-Output ("Печать файла `"" + $_.FullName + "`"")
    Start-Process -FilePath notepad -ArgumentList ("/P `"" + $_.FullName + "`"") -Wait
}


Печать выполняется средствами штатного блокнота Windows (чтоб не простаивал без дела).
Как видно из 3-й строки, сортировка в примере происходит по имени файла (Name). Вместо этого можно взять за основу размер (Length) или дату изменения (LastWriteTime). Если вам требуется что-то более экзотичное, можно зайти сюда.

Для сортировки в обратном порядке у командлета Sort-Object есть ключ -Descending.

В этом варианте печать идет на принтер по умолчанию, и нас такое поведение устроило. Если же нужно печатать на принтер, отличный от дефолтного, у блокнота есть параметр /PT.

Соответственно, код примет следующий вид:

<...>
$PrinterName = "\\server2\Network Printer"
<...>
    Start-Process -FilePath notepad -ArgumentList ("/PT  `"$PrinterName`"  `"" + $_.FullName + "`"") -Wait
<...>

Аналогично вместо блокнота можно поэксплуатировать любую другую программу в зависимости от того, какой формат файлов нужно печатать. Главное — чтобы она поддерживала печать через интерфейс командной строки.

Примечание. Если будете приручать Adobe Reader, имейте в виду этот старый баг. В нашем окружении он все еще проявляется, возможно, вам повезет больше. А еще есть хорошая статья, посвященная печати PDF из PowerShell.

Если же вам на выходе нужен только «голый» текст из обычного текстовика, то 5-я строка варианта 0 примет такой вид:

      Get-Content $_.FullName | Out-Printer -Name $PrinterName

Для печати на дефолтный принтер параметр -Name нужно опустить.

Для нашей задачи требовалась печать файлов из нескольких расположений. Немного дополнив вариант 0, получаем

Вариант 1
$FolderToPrint = @(
    "\\server1\share\Folder1",
    "\\server1\share\Folder2",
    "\\server1\share\Folder3"
)
$FileMask = "*.xml"
$ErrorActionPreference = "Stop"
Try {
    $FolderToPrint | Get-ChildItem -File -Filter $FileMask | Sort-Object Name | ForEach-Object {
        Write-Output ("Печать файла `"" + $_.FullName + "`"")
        Start-Process -FilePath notepad -ArgumentList ("/P `"" + $_.FullName + "`"") -Wait
    }
}
Catch {
    Write-Host "При выполнении операции возникла ошибка:"
    Write-Host $Error[0] -ForegroundColor Red
    Read-Host "Нажмите ENTER для завершения"
}


Для приличия добавлена функция обработки исключений. И в случае, если, например, папка, из которой печатаются файлы, стала недоступной, то выполнение печати прервется и пользователю будет выведено соответствующее уведомление. Кстати, замечено, что блокнот возвращает в exit-коде 0 даже при попытке распечатать несуществующий/недоступный файл, но в GUI при этом ругается.

Опробовав вариант 1, пользователи попросили дать возможность выбора папки и конкретных файлов в ней, поэтому было добавлено немного интерактивности в виде диалогового окна выбора файлов. Так появился

Вариант 2
Add-Type -AssemblyName System.Windows.Forms | Out-Null
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.InitialDirectory = "\\server\share"
$OpenFileDialog.Multiselect = $True
$OpenFileDialog.Filter = "XML-файлы (*.xml)|*.xml|Все файлы (*.*)|*.*"
$OpenFileDialog.ShowHelp = $true
$OpenFileDialog.ShowDialog() | Out-Null
$FilesToPrint = $OpenFileDialog.FileNames | Sort-Object
ForEach ($FullFileName in $FilesToPrint) {
    Write-Output "Печать файла `"$FullFileName`""
    Start-Process -FilePath notepad -ArgumentList ("/P `"$FullFileName`"") -Wait
}


Теперь при запуске получаем привычное окно проводника Windows с удобным выбором нужных файлов:

клик


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

Обработка исключений во втором варианте была убрана, т.к. интерактивное информирование пользователя было отдано на откуп проводнику и блокноту.

При запуске кода из ISE диалоговое окно выбора файлов выводится на заднем плане (Ctrl+Tab в помощь), но из командной строки все работает как положено. Также обратите внимание, что свойство ShowHelp должно быть $true, чтобы обойти этот баг.

Еще хотелось бы обратить внимание на свойство InitialDirectory. Кэп подсказывает, что оно определяет путь к папке, которая будет выбрана по умолчанию при запуске скрипта. Но, учитывая, что проводник «запоминает» последнее выбранное расположение, которое было указано пользователем в диалоге выбора файлов, InitialDirectory может пригодиться только при первом запуске скрипта.

Вариант 2 полностью подошел нашим пользователям, поэтому на нем мы и остановились. Но если вам нужен вариант с дамами и преферансом интерактивностью и сортировкой, отличной от имени (например, по дате изменения), это тоже реализуемо. Получаем

Вариант 3
Add-Type -AssemblyName System.Windows.Forms | Out-Null
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.InitialDirectory = "\\server\share"
$OpenFileDialog.Multiselect = $True
$OpenFileDialog.Filter = "XML-файлы (*.xml)|*.xml|Все файлы (*.*)|*.*"
$OpenFileDialog.ShowHelp = $true
$OpenFileDialog.ShowDialog() | Out-Null
$SelectedFiles = $OpenFileDialog.FileNames
#Если ничего не выбрано, завершаем работу
If (!($SelectedFiles)) {
    Break
}
#На основании полного имени выбранного файла определяем выбранную папку
$SelectedDir = (Split-Path -Parent $OpenFileDialog.FileName)
#Получаем список всех файлов в выбранной папке
$FilesToPrint = Get-ChildItem -Path $SelectedDir -Force |
    #отбираем только те из них, которые были выбраны в диалоговом окне
    Where-Object {$_.FullName -in $OpenFileDialog.FileNames} |
    #и сортируем
    Sort-Object -Property LastWriteTime
ForEach ($File in $FilesToPrint) {
    $FullFileName = $File.FullName
    Write-Output "Печать файла `"$FullFileName`""
    Start-Process -FilePath notepad -ArgumentList ("/P `"$FullFileName`"") -Wait
}


Т.к. из объекта $OpenFileDialog нельзя напрямую извлечь такие параметры, как размер или дату создания файла, то мы с помощью командлета Get-ChildItem получаем список всех файлов в папке, выбранной пользователем, а потом оставляем только те из них, которые были выбраны пользователем, и сортируем их в нужном нам виде.

Отдаем в продакшн


Убедившись, что всё работает как надо, кладем скрипт в сетевую папку и выводим пользователям ярлык на рабочий стол.

А чтобы наш маленький беззащитный скрипт не обижали злые Execution Policies, прячем его в такую скорлупу:

powershell.exe -NoLogo -ExecutionPolicy Bypass -File "\\server\share\Scripts\BulkPrint.ps1"

клик


Или можно обернуть в теплый ламповый батник.

Среди прочего, в корпоративной среде запуску скрипта могут мешать суровые Software Restriction Policies и безжалостный AppLocker, а также другое защитное ПО, но это уже выходит за рамки статьи.

Можно добавить лоска, установив красивый значок для ярлыка. Я выбрал такой:

клик


Если пользователей нашего скрипта много, можно массово раздать ярлык с помощью предпочтений групповой политики.

Итог




Такое бывает, если выкатить без предварительного тестирования.

А у нас будет вот так:



И крамольная мысль напоследок: а что, если подумать в другом направлении и вместо всего описанного выше пообщаться с начальством и перестроить рабочий процесс?

Share post
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 11

    0
    И крамольная мысль напоследок: а что, если подумать в другом направлении и вместо всего описанного выше пообщаться с начальством и перестроить рабочий процесс?


    При этом отказаться от перевода бумаги и хранить всю информацию в электронном виде?
    Очень правильное было бы решение.
    (в последние годы замечаю, что бумажные экземпляры документов никто не читает, а документы с подписями/печатями сканируют и прикладывают к другим документам в электронном виде. Ключевое слово при этом — «поиск»)
      0

      С точки зрения технаря и пользователя — да.
      Но при этом надо учитывать и финансовую составляющую. В одной компании, где я работал, когда мы посчитали стоимость внедрения и обслуживания ЭДО с контрагентами, оказалось, что по деньгам выигрыш будет небольшим, да и затраты отобьются не так быстро, как хотелось бы руководству.

        0
        когда мы посчитали стоимость внедрения и обслуживания ЭДО с контрагентами, оказалось, что по деньгам выигрыш будет небольшим,


        Если у вас не режимная организация и не бюрократический монстр — ЭДО можно внедрить «малой кровью».
        Можете рассказать — что конкретно делает в вашем случае ЭДО столь затратным?
        Ниже список того, от чего мы избавились при внедрении ЭДО:
        1. Помещения для хранения бумажных копий (аренда), отопление, ремонт.
        2. Мебель, обслуживание (от уборки до противопожарных мероприятий)
        3. Проблема с быстрым поиском нужного документа
        4. Проблема с быстрым поиском нужного места в документе
        5. Проблемы с регулярной утилизацией документов, срок хранения которых истек.
        6. Экономия на обслуживании печатающих устройств (ремонт, заправка, ограничение доступа)
        7. Экономия на расходных материалах и бумаге, на переплетах.
          0

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

      0

      О господи, зачем может понадобиться массово печатать XML?

        0

        К сожалению, есть определенные бюрократические процедуры, выполнение которых обязательно, несмотря на связанную с этим рутину.

        0
        Если не секрет, почему выбрана печать через блокнот, а не через out-printer?
          0
          Вообще секрет, но Вам расскажу.
          Среди нужного пользователям функционала был такой пункт:
          на бумаге, помимо содержимого, должно быть указано и имя печатаемого файла;

          Можно, конечно, через Get-Content, а потом, прикрутив к тексту имя файла в начале, сделать через Out-Printer, но блокнот делает это «из коробки», да ещё и страницы нумерует.
            0
            благодарю, в глаза под вечер видать долблюсь:)
            единственное — я б наверное делал скриптом — ну или проверял бы настройки блокнота. вдруг они изменены (это HKEY_CURRENT_USER\Software\Microsoft\Notepad, параметры szHeader и szTrailer)
              0
              Тогда уж лучше через Group Policy Preferences, ИМХО
                +1
                зависит от прочих условий, если GPP подходят — то конечно.
                условный пример когда не подойдут — если я такой весь постоянно печатаю из блокнота, отключая колонтитулы, и периодически надо печатать массово скриптом.
                конечно при таких раскладах лучше в скрипте менять настройки, запоминая предыдущие, выполнять работу, откатывать настройки.

        Only users with full accounts can post comments. Log in, please.