Здравствуй, уважаемый %user%. Сегодня я расскажу о том, как я написал пользовательскую систему для обеспечения возможности пользователям самим устанавливать требуемые для них принтеры. Немного расскажу о месте где я работаю, чтобы было понятно зачем я это сделал. Заранее скажу, что о существовании компонента роли в Windows Server 2012 R2, которая даёт возможность установить принтер с браузера я знал. Но мне хотелось изобрести свой велосипед, да и ограничения здесь будут наверное в фантазии. Кому стало интересно добро пожаловать под кат. Для не терпеливых сразу скажу, что скриншоты в конце статьи.
Немного о работе
Работаю я в хелпдеске в некой большой организации, которая имеет много филиалов. Имеется более тысячи сотрудников работающие перед компьютерами. Можно догадаться, что есть большой зоопарк сетевых принтеров, есть отдельные работники отвечающие за работоспособность принтеров. Установку принтеров компьютерам осуществляем мы – хелпдеск. Очень часто сотрудники разных отделов переходят из одного рабочего места в другое, приходят или увольняются, или вообще меняют должность и профессию и так далее. Куча всего интересного. Соответственно из-за этого очень часто поступают звонки к нам в хелпдеск о просьбе установить им принтер. Понимаете, попросить пользователя, сказать IP адрес принтера, это все равно что попросить обезьяну сварить суп. Приведу пример диалога (реальный случай):
U – user, Я - я
U- Здравствуйте, это хелпдеск туда я попал?
Я- Да. Правильно. Вы туда попали
U- Помогите мне пожалуйста установить принтер на мой компьютер
Я-Хорошо, скажите мне пожалуйста имя вашего компьютера.
U-Имя моего компьютера comp-01 (имя вымышленное)
Я-Какой принтер вам поставить? (Подключаясь к компьютеру)
U- HP LaserJet 600, он находится в %place%
Я – расположение принтера мне ничего не дает (и правда поменяют его другим принтером так как первый испортился, или поменяют его место). Хорошо, можете ли вы мне сказать ip адрес этого принтера?
U- ….
Пауза на конце трубки. Я аж услышал, как зашевелился у пользователя мозг, пошла кровь из ушей и наконец он спросил
U- Простите что? Айми? Что вам сказать? Айми адрес?
Я – Айпи, айпи адрес.
U- Айпи? А что это такое? Как можно на него посмотреть?
Я –Ладно скажите кроме вас кто-то еще этим принтером пользуется?
U-Ну им пользуется %another_user%
Я-Пожалуйста попросите у него имя компьютера и получите разрешение чтобы я на секундочку к нему подключился
U- Ок, сейчас (и слышу приглушенный голос того как он спрашивает у %another_user% имя компьютера и разрешение на подключение)
Я-…..(думая про себя: айми айми айми, елки палки какую рифму на айми можно придумать)
U-Алё
Я- Да, я вас слушаю
U- Имя компьютера comp-02, можете подключаться
Я- Попросите %another_user% показать мне принтер, которым он постоянно пользуется (подключаюсь к компьютеру)
U- Ок, сейчас (опять приглушенный голос)…… — Вот этот
Я- Ок, хорошо, я понял всё не буду вас задерживать на линии сейчас сам всё поставлю и настрою
U- Хорошо, спасибо. Однако вы мне не сказали, что такое айми.
Я- Наверное, вы имели в виду айпи. Ну сложно будет объяснить сейчас, так как звонков многовато, но в общем это адрес устройства в сети.
Вы слышали когда-нибудь как ломается мозг у гуманитария? Я услышал этот звук, знаете такой глухой звук с брызгами большого количество информации с содержанием одной лишь только «воды».
U – ....(долгая пауза)… Ясно. Я отойду по делам а вы поставьте принтер
Я- Да, хорошо, до свидания
Я- Да. Правильно. Вы туда попали
U- Помогите мне пожалуйста установить принтер на мой компьютер
Я-Хорошо, скажите мне пожалуйста имя вашего компьютера.
U-Имя моего компьютера comp-01 (имя вымышленное)
Я-Какой принтер вам поставить? (Подключаясь к компьютеру)
U- HP LaserJet 600, он находится в %place%
Я – расположение принтера мне ничего не дает (и правда поменяют его другим принтером так как первый испортился, или поменяют его место). Хорошо, можете ли вы мне сказать ip адрес этого принтера?
U- ….
Пауза на конце трубки. Я аж услышал, как зашевелился у пользователя мозг, пошла кровь из ушей и наконец он спросил
U- Простите что? Айми? Что вам сказать? Айми адрес?
Я – Айпи, айпи адрес.
U- Айпи? А что это такое? Как можно на него посмотреть?
Я –Ладно скажите кроме вас кто-то еще этим принтером пользуется?
U-Ну им пользуется %another_user%
Я-Пожалуйста попросите у него имя компьютера и получите разрешение чтобы я на секундочку к нему подключился
U- Ок, сейчас (и слышу приглушенный голос того как он спрашивает у %another_user% имя компьютера и разрешение на подключение)
Я-…..(думая про себя: айми айми айми, елки палки какую рифму на айми можно придумать)
U-Алё
Я- Да, я вас слушаю
U- Имя компьютера comp-02, можете подключаться
Я- Попросите %another_user% показать мне принтер, которым он постоянно пользуется (подключаюсь к компьютеру)
U- Ок, сейчас (опять приглушенный голос)…… — Вот этот
Я- Ок, хорошо, я понял всё не буду вас задерживать на линии сейчас сам всё поставлю и настрою
U- Хорошо, спасибо. Однако вы мне не сказали, что такое айми.
Я- Наверное, вы имели в виду айпи. Ну сложно будет объяснить сейчас, так как звонков многовато, но в общем это адрес устройства в сети.
Вы слышали когда-нибудь как ломается мозг у гуманитария? Я услышал этот звук, знаете такой глухой звук с брызгами большого количество информации с содержанием одной лишь только «воды».
U – ....(долгая пауза)… Ясно. Я отойду по делам а вы поставьте принтер
Я- Да, хорошо, до свидания
А теперь представьте, что каждый третий звонящий в хелпдеск, с просьбой установить принтер в таком же состоянии. Как быть? Данного рода диалоги очень сильно утомляют, да и объяснить юзверю как напечатать конфигурацию устройства, чтобы посмотреть на IP принтера, это что-то сверхтяжелое. Значит нужна какая-то автоматизация. А что если собрать централизованно все принтеры на один отдельный принтер сервер, написать некий простой веб-сайт с дружелюбным интерфейсом. Пару кликов и принтер установился. Вызов принят.
Все не так и сложно как кажется
Попросил системных администраторов выделить отдельный сервер на Windows Server 2012 R2.
На принтер сервере
- Установил роль Printer Sever
- Создал папку scripts$, чтобы хранить скрипты установки принтеров
- Установил XAMPP
- Добавил apache и mysql как службу в автозагрузку, чтобы в случае перезагрузки сервера, веб-сайт не грохнулся
С принтерами:
- Наклеил на все принтеры до которых руки доходят наклейки с их номерами и установил их на принтер сервер. Порядковый номер — это некий идентификатор для принтера. Где бы он не находился юзверь всегда сможет найти его по порядковому номеру. К примеру: «HP LaserJet 600 Printer #id %filial%», где %filial% — имя филиала, #id уникальный порядковый номер принтера. Распечатывается бумажка с порядковым номером и клеится на принтер. Знать пользователю даже имя принтера не обязательно. Совпал номер с тем что на сайте – значит он и есть.
- Договорился с менеджерами филиалов, попросил выделить кого-то знающего в филиале чтобы собрали и отправили мне имена, ip адреса принтеров и наклеили их номера порядковые. Добавил все принтеры по одному на принтер сервер.
Начинаем программировать
Веб-сайт построен на PHP написанием самописного MVC движка, с помощью использования только редактора Notepad++, Google и браузера (да – только по хардкору). Знаниями по какому-то фреймворку не обладаю, изучать лень, использовать javascript фреймворки в 2к17?, да и не слишком ли много для принтеров?
Поставим цель, что нам нужно от этой системы:
- Максимально простая и наглядная для пользователя
- Возможность создания учетных записей администраторов
- Возможность добавлять\удалять принтеры администраторам
- Возможность добавлять\удалять филиалы администраторам
- Наличие какой-либо справочной информации для пользователя
- Смена языка
- Возможность устанавливать принтеры не одним, а несколькими методами (скриптами)
Определим структуру проекта:
- App
- Configs
- Controllers
- Locale
- Views
- Uploads
В корневой папке создадим файл index.php добавим код:
<?php
//стартуем сессию
session_start();
//загружаем все классы
spl_autoload_register(function ($class){
include './app/controllers/' . $class . '.php';
});
//инициализируем главный контроллер. Именно через него будут поступать все запросы
$controller = new Controller($_GET, $_POST);
?>
В папке configs создадим файл database.php:
<?php
define("HOST", "localhost");
define("USER", "root");
define("PASSWORD", "OUR_PASSWORD");
define("DATABASE", "printer");
?>
Зайдем в браузере в phpmyadmin, создадим базу данных printer с использованием кодировки utf8_general_ci. Создадим 3 таблицы с именами branches, printers, users.
Структура branches:
Имя | Тип | Дополнительно |
---|---|---|
Id | int(6) | AUTO_INCREMENT, PRIMARY, UNIQUE |
branch_name | varchar(255) | |
image | varchar(255) |
Структура printers:
Имя | Тип | Дополнительно |
---|---|---|
Id | int(6) | AUTO_INCREMENT, PRIMARY, UNIQUE |
Name | varchar(255) | |
branchid | int(6) | По умолчанию значение: 1 |
description | text | |
ipaddress | varchar(255) | |
image | varchar(255) | |
File1 | varchar(255) | |
File2 | varchar(255) | |
File3 | varchar(255) |
Структура users:
Имя | Тип | Дополнительно |
---|---|---|
Id | int(6) | AUTO_INCREMENT, PRIMARY, UNIQUE |
Login | varchar(128) | |
Token | varchar(128) | |
password | varchar(128) | |
lang | varchar(10) | |
logindate | varchar(255) |
Как видно из таблиц branchid определяет к какому филиалу относится принтер (получив значение его id).
Создадим запись с id равной 1, именем филиала «none» и image значением «none». Эта запись нужна для того чтобы задавать принтеру не установленный филиал. Первая запись захардкожена и не отображается в списках филиалов, как и для юзверей так и в админке. Её удалить нельзя.
Класс Database я нашел в просторах интернета. В нем имеется функция для подключения к нашей базе данных, с использования значений для подключения от файла database.php находящийся в папке configs и функция для осуществления запросов к БД.
Класс İnfo используется для получения, изменения, удаления данных из БД. Именно с использованием этого класса осуществляются все операции в системе.
Одним из важных классов является класс Controller.php. Как видно из названия этот класс является контроллером, который принимает GET и POST запросы в __construct($get, $post) из index.php. Получив определенный запрос он собирает куски страницы во едино соответственно требуемому запросу и выполняет требуемые функции из Info.php.
Класс Views является классом, который загружает куски страницы. Каждая страница имеет свое название соответствующее php файлу в папке Views. Так к примеру, чтобы отобразить главную страницу, следует написать:
$views = new Views;
$views->addView('header', 'header.php');
$views->addView('menu', 'menu.php');
$views->addView('dashboard', 'dashboard.php');
$views->addView('footer', 'footer.php');
В этом же классе определяется текущий язык пользователя, загружается локализация из ini файла и создается переменная $lang, которая уже используется в самом куске страницы. Пример кода главной страницы (dashboard.php):
<div class="dashboard">
<div class="container">
<h1><?php echo $lang['DASHBOARD_HEADER']; ?></h1>
<p><?php echo $lang['DASHBOARD_TEXT']; ?></p>
<br>
<br>
<p><a class="btn btn-primary btn-lg" href="index.php?branches" role="button"><?php echo $lang['DASHBOARD_INSTALL']; ?> »</a></p>
<img class="dashboard-img" src="app/views/img/printer_icon.png">
</div>
</div>
В папке views так же есть файлы css, javascript. За основу CSS стилей я использовал Bootstrap.
Класс Lang определяет текущий язык пользователя. Сперва он пытается получить куки «cookie_lang». Если не обнаруживает его, то ставит по умолчанию русский язык. Функция getLangArray() загружает нужный ini файл и возвращает массив вида «ключ» — > «значение». Именно эта функция используется в классе views.
Что мы получили
Пользователь заходит на главную страницу. Там описание того куда он попал и кнопка, которая открывает список всех филиалов. После того как пользователь выберет филиал, откроется список принтеров. Принтеры отображаются в виде сетки, где их фотографии и их названия. Пользователь выбирает принтер, открывается страница с выбранным принтером, где есть большая кнопка «Установить» (также там информация о принтере, его İP адрес). При нажатии на эту кнопку скачивается bat файл, который в свою очередь открывает vbs скрипт, расшаренный в папке scripts$ на принтер сервере. Проблема в том что если закачивать файл vbs, то вместо того чтобы скачать его, браузер открывает этот файл у себя в новой вкладке. Поэтому пришлось так извращаться. Vbs файлы на шаре папке тоже рассортированы вместе со своим bat файлом по папкам. К примеру, ниже приведена структура папки:
Printer#id
- Printer#id.bat
- Printer#id.vbs
Где #id порядковый номер принтера.
При добавлении нового принтера через админку, заполняются требуемые поля и выбирается нужный bat файл из шары. Файл установки, фотография принтера загружается в папку uploads. В админке сделано так, что мы можем закачать до трех разных файлов установки. Сколько файлов закачано столько и кнопок «Установить». В данном случае обошлось без альтернативных методов установки принтеров. VBS скрипта оказалось достаточно.
VBS script
Vbs скрипт устанавливающий принтер выглядел таким образом:
printerName = "\\prnserver01\HP LaserJet 600 printer1 branch1"
Set WshNetwork = CreateObject("WScript.Network")
WshNetwork.AddWindowsPrinterConnection printerName
WSHNetwork.SetDefaultPrinter printerName
Данный скрипт выполняется в невидимом режиме, какое-то время отображается окно командной строки, затем он исчезает. Данный скрипт обладает минусом того, что не понятно, что происходит. Поэтому подумав немного поменял его в такой вид:
'Задаем путь принтера
printerName = "\\prnserver01\HP LaserJet 600 printer1"
'Создаем окно Internet Explorer
Set objExplorer = CreateObject("InternetExplorer.Application")
'Задаем настройки окна - длину, ширину, позицию на экране
objExplorer.Navigate "about:blank"
objExplorer.ToolBar = 0
objExplorer.StatusBar = 0
objExplorer.Left = 500
objExplorer.Top = 250
objExplorer.Width = 550
objExplorer.Height = 170
objExplorer.Visible = 1
'Задаем заголовок
objExplorer.Document.Title = "Ustanovka printera"
'Задаем html код страницы, со своим крутым дизайном
objExplorer.Document.Body.InnerHTML = "<table style=""width:100%""><tr><td id=""progress"" style=""font-family:Segoe UI;text-align: center;font-size:48px;border-bottom:1px solid black;""> Ustanovka printera: 0%</td></tr><tr><td style=""font-family:Segoe UI;text-align: center;font-size:22px;"">" & printerName & "</td></tr></table>"
'Ждем 500 миллисекунд, находим на странице объект с id progress и меняем его значение. Психологический фактор для юзверя, толку от этого 0
Wscript.Sleep 500
objExplorer.document.getElementById("progress").innerText = " Ustanovka printera: 10%"
'Создаем объект для установки принтера, устанавливаем принтер и ставим его по умолчанию. Скрипт не продолжит выполнятся пока принтер не установится или выйдет ошибка
Set WshNetwork = CreateObject("WScript.Network")
WshNetwork.AddWindowsPrinterConnection printerName
WSHNetwork.SetDefaultPrinter printerName
'Психологический фактор на 200 миллисекунд, хотя принтер уже установился или вышла ошибка
Wscript.Sleep 200
objExplorer.document.getElementById("progress").innerText = " Ustanovka printera: 20%"
'Ещё один психологический фактор на 200 миллисекунд
Wscript.Sleep 200
objExplorer.document.getElementById("progress").innerText = " Ustanovka printera: 40%"
'Ещё один, это же такой кайф когда проценты быстро идут
Wscript.Sleep 200
objExplorer.document.getElementById("progress").innerText = " Ustanovka printera: 60%"
'В этом моменте можно уже откинуться
Wscript.Sleep 200
objExplorer.document.getElementById("progress").innerText = " Ustanovka printera: 80%"
'О свершилось чудо, наконец!
Wscript.Sleep 100
objExplorer.document.getElementById("progress").innerText = " Printer ustanovlen!"
'Получим на 3 секунды еще кайфа от того, что установили принтер и прощаемся
Wscript.Sleep 3000
objExplorer.Quit
Скриншоты
Скриншоты
Исходный код всего этого выложил на github
Теперь звонков по поводу установки принтеров стало намного меньше.
Спасибо за внимание!