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

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

Разработчики Microsoft, должно быть, обратили внимание на это ограничение, и, начиная с Windows 2000, ввели возможность добавления на компьютере посистемных подключений принтеров. Посистемное подключение принтеров - это сетевое подключение принтеров, которое видят все пользователи. Данная функция позволяет один раз добавить принтер на компьютер, после чего любой пользователь, регистрирующийся на этом компьютере, имеет возможность работать с добавленным принтером. Нужно только показать пользователям, как выбрать принтер по умолчанию. А в тех случаях, когда принт-сервер заменяется, можно просто удалить старое посистемное подключение и добавить новое посистемное подключение принтеров. При таком раскладе пользователи могут не беспокоиться об удалении или добавлении их собственных принтеров, если нужные принтеры им доступны.

Странным образом у этой полезной функции отсутствует хорошая документация. Более того, единственный способ воспользоваться преимуществами посистемных подключений принтеров – вводить длинные, громоздкие команды. Например, чтобы добавить принтер PS3HP 4600dn как посистемное подключение на компьютер COMPUTER2, требуется набрать команду

rundll32 printui.dll,PrintUIEntry 
/cCOMPUTER2 /ga
/n"PS3HP 4600dn"

Для того чтобы вывести список посистемных подключений на компьютере COMPUTER2, необходимо выполнить команду

rundll32 printui.dll,PrintUIEntry 
/cCOMPUTER2
/ge /f"C:Printers.txt"

Эта команда сохраняет список посистемных подключений принтеров компьютера COMPUTER2 в текстовом файле под именем C:Printers.txt. Но этот файл содержит не только имена принтеров, поэтому его нужно редактировать или разобрать, отделяя нужные фрагменты от ненужных, чтобы иметь возможность извлечь из него одни имена принтеров.

Вы легко можете убедиться, что интерфейс командной строки для управления посистемными подключениями принтеров неудобен. Такое решение меня не устраивало, поэтому я создал встраиваемый модуль PMPMgr, который заметно облегчает управление посистемными подключениями принтеров. Этот модуль состоит из WSC-файла (Windows Script Components) с именем PMPMgr.wsc и сценария с именем PMPMgr.js. Модуль PMPMgr предоставляет простой в использовании интерфейс командной строки для управления посистемными подключениями принтеров. Просмотрите Листинг 1 и Листинг 2. Для использования этого модуля никакой адаптации файлов PMPMgr.wsc и PMPMgr.js не требуется. Нужно просто выполнить следующие четыре шага:

  1. Зарегистрируйте PMPMgr.wsc
  2. Изучите синтаксис команд PMPMgr.js
  3. Выведите список принтеров совместного использования или посистемных подключений принтеров
  4. Добавьте или удалите посистемные подключения принтеров.

Шаг 1: Зарегистрируйте PMPMgr.wsc.

Перед началом использования PMPMgr для управления посистемными подключениями принтеров необходимо зарегистрировать компонент PMPMgr.wsc. Регистрация заключается в добавлении в реестр записей, делающих объект компонента доступным системе. Для того чтобы зарегистрировать компонент, щелкните в Windows Explorer правой кнопкой на файле PMPMgr.wsc и выберите Register. Другой способ регистрации – из командной строки с использованием команды

regsvr32 /i:"[path]PMPMgr.wsc" 
%SystemRoot%system32scrobj.dll

где path - это полный путь к компоненту в вашей системе. В реальном командном окне эту команду следует вводить одной строкой. То же самое касается и всех остальных команд в этой статье. Если вы, наоборот, хотите отменить регистрацию компонента, щелкните правой кнопкой по файлу PMPMgr.wsc и выберите Unregister. Чтобы отменить регистрацию из командной строки, введите команду

regsvr32 /u /n /i:"[path]PMPMgr.wsc" 
%SystemRoot%system32scrobj.dll

Чтобы при выполнении любой из этих команд диалоговое окно с сообщением не отображалось, нужно сразу после regsvr32 добавить ключ /s. Регистрировать и отменять регистрацию компонента могут только члены локальной группы Administrators.

Шаг 2: Изучите синтаксис команд PMPMgr.js

После регистрации компонента PMPMgr.wsc у вас появится возможность использовать сценарий PMPMgr.js. Синтаксис этого сценария командной строки следующий:

[cscript] PMPMgr.js [[@]computer] 
/shared | /list | /exists:[@]printer |
/add:[@]printer | /delete:[@]printer

Вот описание каждого элемента синтаксиса:

  • В начале команды стоит ключевое слово cscript, необходимость в котором возникает только тогда, когда Cscript не является вашим языком сценариев по умолчанию. Чтобы сделать Cscript языком сценариев по умолчанию, следует выполнить команду
    cscript //H:CScript //Nologo //S
  • В аргументе computer указывается имя компьютера. Если используется символ @, то аргумент указывает на текстовый файл, содержащий список имен компьютеров (по одному имени в строке). Если пропустить этот аргумент, сценарий PMPMgr.js применяется к текущему компьютеру.
  • Ключ /shared выводит список принтеров совместного использования тех серверов, которые указаны в аргументе computer.
  • Ключ /list выводит список посистемных подключений принтеров на компьютерах, указанных в аргументе computer.
  • Аргумент printer задает имя принтера в формате UNC (Universal Naming Convention - универсальное соглашение об именах), то есть в виде printserverprintername. Если имя принтера содержит пробелы, нужно заключить его в двойные кавычки (" "). Если используется символ @, то аргумент printer указывает на текстовый файл, содержащий список имен принтеров в формате UNC (по одному имени в строке).
  • Ключ /add добавляет посистемные подключения принтеров, указанные в аргументе printer.
  • Ключ /delete удаляет посистемные подключения принтеров, указанные в аргументе printer.

Шаг 3: Выведите список принтеров совместного использования или посистемных подключений принтеров

С помощью ключа /shared сценария PMPMgr.js выводится список принтеров совместного использования некоторого принт-сервера. Например, следующая команда выводит список принтеров принт-сервера PS1:

PMPMgr.js PS1 /shared

В выходных данных, генерируемых сценарием PMPMgr.js, в качестве разделителя используется символ табуляции; их можно перенаправить в текстовый файл. На Рисунке 1 показан пример выходных данных сценария.

Также можно с помощью одной команды вывести список принтеров совместного использования от нескольких принт-серверов. Поместите список принт-серверов в тестовый файл и укажите имя этого файла сразу после символа @. На Рисунке 2 показан пример текстового файла PrintServers.txt. Следующая команда генерирует список принтеров совместного использования для каждого компьютера, указанного во входном файле PrintServers.txt, и перенаправляет результаты в выходной файл PrintServers.tsv:

PMPMgr.js @PrintServers.txt 
/shared > PrintServers.tsv

  

Чтобы получить список посистемных подключений принтеров, установленных на одном или нескольких компьютерах, вместо ключа /shared в приведенной выше команде используйте ключ /list.

Шаг 4: Добавьте или удалите посистемные подключения принтеров

Чтобы добавить или удалить посистемные подключения принтеров, используйте соответственно ключи /add или /delete сценария PMPMgr.js. Например, следующая команда добавляет принтер PS2HP LaserJet 1300 на компьютер RECEPTION1:

PMPMgr.js RECEPTION1 
/add:"PS2HP LaserJet 1300"

Для того чтобы добавить или удалить сразу несколько принтеров, можно создать текстовый файл, который содержит список принтеров, по одному принтеру в строке. На Рисунке 3 показан пример текстового файла Printers.txt со списком принтеров. Следующая команда добавляет принтеры, перечисленные в файле Printers.txt, на компьютер RECEPTION2:

PMPMgr.js RECEPTION2 
/add:@Printers.txt

  

Нужно иметь в виду, что если пользователь зарегистрировался и находится в системе, когда вы добавляете или удаляете посистемные подключения принтеров, изменения в окне Printers этого пользователя будут видны не сразу, а только при следующем входе в систему. Для того чтобы увидеть изменения немедленно, необходимо перезапустить службу Print Spooler.

Хотя, разрабатывая компонент PMPMgr.wsc и сценарий PMPMgr.js, я старался сделать их невосприимчивыми к ошибкам, необходимо запускать сценарий от имени учетной записи, которая является членом локальной группы Administrators текущего компьютера. Если вы работаете с удаленными компьютерами, вы также должны быть членом локальных групп Administrators каждого удаленного компьютера. Если вы запустите сценарий PMPMgr.js от имени учетной записи с недостаточно высокими полномочиями, то, возможно, увидите диалоговое окно Printer User Interface, которое приостановит выполнение сценария. Для дальнейшего выполнения сценария следует закрыть диалоговое окно.

Посистемные подключения принтеров сэкономили мне массу времени и избавили от множества хлопот. А модуль PMPMgr еще сильнее упрощает использование этих подключений. Скачайте файлы PMPMgr.wsc и PMPMgr.js, и вы сможете воспользоваться преимуществами посистемных подключений принтеров уже сегодня.


Листинг 1. PMPMgr.wsc





description="PMPMgr.wsc: Manages per-machine printers"
progid="Penton.PMPMgr"
version="1.0"
classid="{DEB04939-C581-4342-8CBC-6553434C3226}"
/>
Penton.PMPMgr























Листинг 2. PMPMgr.js
// PMPMgr.js - Written by Bill Stewart (bill.stewart@frenchmortuary.com)
//
// This script uses the Penton.PMPMgr object to manage per-machine
// printers on one or more computers.
//
// Usage: PMPMgr.js [[@]computer] /shared | /list /exists:[@]printer
// | /add:[@]printer /delete:[@]printer
//
// If the computer argument begins with the @ character, the script
// assumes that the argument is a text file, reads the text file into
// an array, and operates on each computer in the array. Otherwise, it
// assumes that the argument is a single computer name. If the computer
// argument is not specified, the script uses the local computer.
//
// If the printer argument begins with @, then the script assumes that
// the argument is a text file and reads it into an array; otherwise, it
// assumes the argument is a single printer name.
//
// The /shared option lists shared printer(s) on the computer(s). This
// is useful for enumerating the printers shared from print servers.
//
// The /list option lists the per-machine printer(s) on the computer(s).
//
// The /exists option lists whether each printer in the printer argument
// exists as a per-machine printer for each computer in the computer
// argument.
//
// The /add option adds each printer in the printer argument as a per-
// machine printer to each computer in the computer argument.
//
// The /delete option deletes each per-machine printer in the printer
// argument from each computer in the computer argument.
//
// The script's output is provided in tab-delimited format.

var SCRIPT_NAME = "PMPMgr.js";
var ERROR_INVALID_PARAMETER = 87;

// Adds the exists method to the array object. It returns true if the
// specified item exists in the array (ignoring case).
Array.prototype.exists = function(item)
{
var n;

for (n = 0; n < this.length; n++)
if (this[n].toLowerCase() == item.toLowerCase())
return true;

return false;
}

// Adds the hex method to the Number object. It returns a hexadecimal
// string representation of the number.
Number.prototype.hex = function()
{
if (this >= 0)
return this.toString(0x10).toUpperCase();
else
return (this + 0x100000000).toString(0x10).toUpperCase();
}

// Adds the trim method to the String object. It uses a regular
// expression to remove leading and trailing spaces from the string.
String.prototype.trim = function()
{
return this.replace(/(^s*)|(s*$)/g, "");
}

// This is the constructor function for the FlexArg object. The FlexArg
// object is used to process a command-line argument. If the first
// character of the argument is "@", the object assumes the argument is
// a text file and populates the list property (an array) with the lines
// from the text file, excluding blank lines and lines beginning with
// ";". The object's listfile property will contain the name of the text
// file. If the first character is not "@", then the object's list
// property will contain only one item (the argument itself). If the
// object encounters an error, the err property will contain the numeric
// error code and the errdesc property will contain the error's
// description.
function FlexArg(argument)
{
var ForReading = 1, ERROR_FILE_NOT_FOUND = 2, ERROR_INVALID_DATA = 13,
ERROR_INVALID_PARAMETER = 87;
var arg, fso, filename, ts, n, line;

// Set sensible initial values for the object's properties.
this.err = 0;
this.errdesc = "";
this.listfile = "";
this.list = new Array();

// Trim leading and trailing spaces from the argument.
arg = argument.trim();

if (arg.charAt(0) == "@") {
// The argument contains only the @ character.
if (arg.length == 1) {
this.err = ERROR_INVALID_PARAMETER;
this.errdesc = "The parameter is incorrect";
return;
}
fso = new ActiveXObject("Scripting.FileSystemObject");
this.listfile = arg.substr(1).trim();
if (! fso.FileExists(this.listfile)) {
this.err = ERROR_FILE_NOT_FOUND;
this.errdesc = "The system cannot find the file specified";
return;
}
try {
ts = fso.OpenTextFile(this.listfile, ForReading);
n = 0;
while (! ts.AtEndOfStream) {
// Trim leading and trailing spaces from the line.
line = ts.ReadLine().trim();
// Add non-blank lines and lines that don't begin with ";".
if ((line.length > 0) && (line.charAt(0) != ";")) {
this.list[n] = line;
n++;
}
}
ts.Close();
}
catch(err) {
this.err = err.number;
this.errdesc = err.description;
}
}
else
this.list[0] = arg;

// The array contains no members.
if (this.list.length == 0) {
this.err = ERROR_INVALID_DATA;
this.errdesc = "The argument contains no data";
}
}

// This is the constructor function for the PrinterMgr object. The
// computers and printers properties should be set to arrays.
function PrinterMgr()
{
// The PrinterMgr object uses the Penton.PMPMgr component.
this.pmpmgr = new ActiveXObject("Penton.PMPMgr");
this.computers = null;
this.printers = null;
}

// The ListSharedPrinters method of the PrinterMgr object lists the
// shared printers for each print server in the computers array.
PrinterMgr.prototype.ListSharedPrinters = function()
{
var i, computer, printers, j;

for (i = 0; i < this.computers.length; i++) {
computer = this.computers[i];
try {
printers = this.pmpmgr.GetSharedPrinters(computer).toArray();
if (printers.length == 0)
WScript.Echo(computer + " No shared printers");
else
for (j = 0; j < printers.length; j++)
WScript.Echo(computer + " " + printers[j]);
}
catch(err) {
WScript.Echo(computer + " 0x" + err.number.hex() + ": "
+ err.description);
}
}
}

// The ListPerMachinePrinters method of the PrinterMgr object lists the
// per-machine printers on each computer in the computers array.
PrinterMgr.prototype.ListPerMachinePrinters = function()
{
var i, computer, printers, j;

for (i = 0; i < this.computers.length; i++) {
computer = this.computers[i];
try {
this.pmpmgr.ComputerName = computer;
printers = this.pmpmgr.GetPrinters().toArray();
if (printers.length == 0)
WScript.Echo(computer + " No per-machine printers");
else
for (j = 0; j < printers.length; j++)
WScript.Echo(computer + " " + printers[j]);
}
catch(err) {
WScript.Echo(computer + " 0x" + err.number.hex() + ": "
+ err.description);
}
}
}

// The ExistPrinters method of the PrinterMgr object lists whether each
// printer in the printers array exists as a per-machine printer on each
// computer in the computers array.
PrinterMgr.prototype.ExistPrinters = function()
{
var i, computer, j, printer;

for (i = 0; i < this.computers.length; i++) {
computer = this.computers[i];
try {
this.pmpmgr.ComputerName = computer;
for (j = 0; j < this.printers.length; j++) {
printer = this.printers[j];
WScript.Echo(computer + " " + printer
+ (this.pmpmgr.ExistPrinter(printer) ? " Exists"
: " Does not exist"));
}
}
catch(err) {
WScript.Echo(computer + " 0x" + err.number.hex() + ": "
+ err.description);
}
}
}

// The AddPrinters method of the PrinterMgr object adds each printer
// in the printers array to each computer in the computers array.
PrinterMgr.prototype.AddPrinters = function()
{
var i, computer, j, printer;

for (i = 0; i < this.computers.length; i++) {
computer = this.computers[i];
try {
this.pmpmgr.ComputerName = computer;
for (j = 0; j < this.printers.length; j++) {
printer = this.printers[j];
WScript.Echo(computer + " " + printer
+ (this.pmpmgr.AddPrinter(printer) ? " Added"
: " Already exists"));
}
}
catch(err) {
WScript.Echo(computer + " 0x" + err.number.hex() + ": "
+ err.description);
}
}
}

// The DeletePrinters method of the PrinterMgr object deletes each
// per-machine printer in the printers array from each computer in the
// computers array.
PrinterMgr.prototype.DeletePrinters = function()
{
var i, computer, j, printer;

for (i = 0; i < this.computers.length; i++) {
computer = this.computers[i];
try {
this.pmpmgr.ComputerName = computer;
for (j = 0; j < this.printers.length; j++) {
printer = this.printers[j];
WScript.Echo(computer + " " + printer
+ (this.pmpmgr.DeletePrinter(printer) ? " Deleted"
: " Does not exist"));
}
}
catch(err) {
WScript.Echo(computer + " 0x" + err.number.hex() + ": "
+ err.description);
}
}
}

// Execute the main function and return its exit code.
WScript.Quit(main());

// Displays the usage message.
function usage()
{
WScript.Echo("Manages per-machine printers on one or more computers. "
+ " "
+ "Usage: " + SCRIPT_NAME + " [[@]computer] /shared | /list | /exists:[@]printer "
+ " | /add:[@]printer | /delete:[@]printer "
+ " "
+ "computer specifies a computer name. If prefixed with @, computer is a text file "
+ "containing a list of computer names (one per line). The current computer is "
+ "assumed if this argument is omitted. "
+ " "
+ "/shared lists shared printers (i.e., enumerate printers on print servers). "
+ " "
+ "/list lists per-machine printers. "
+ " "
+ "printer specifies a printer name, in UNC format (\printserverprinter). If "
+ "the printer name contains spaces, enclose it in double quotes ("). If prefixed "
+ "with @, printer is a text file containing printer names in UNC format (one per "
+ "line). "
+ " "
+ "/add adds per-machine printers. "
+ " "
+ "/delete deletes per-machine printers.");

WScript.Quit(0);
}

// Returns the script host that's executing the script (cscript.exe or
// wscript.exe).
function scripthost()
{
return WScript.FullName.substr(WScript.Path.length + 1).toLowerCase();
}

// Returns a nicely formatted computer name: Uppercase, no leading
// backslashes, and no leading or trailing spaces. If the computer name
// is blank or a single dot (.), it returns the current computer's name.
function formatcomputername(computername)
{
computername = computername.trim().toUpperCase();
if (computername.substr(0, 2) == "\")
computername = computername.substr(2).trim();
if ((computername == "") || (computername == "."))
computername = new ActiveXObject("WScript.Network").ComputerName;
return computername;
}

function main()
{
var validargs, args, computers, result, flexarg, n, named, opt,
printermgr;

// Create an array of valid arguments.
validargs = new Array("add", "delete", "exists", "list", "shared");

args = WScript.Arguments;

if (args.Named.Exists("?") || (args.Named.length == 0))
usage();

if (scripthost() != "cscript.exe") {
WScript.Echo("You must run this script with the CScript host.");
return 1;
}

// Retrieve the first named command-line argument.
named = new Enumerator(args.Named);
opt = named.item().toLowerCase();

// End the script if a valid argument is not specified.
if (! validargs.exists(opt)) {
WScript.Echo("Invalid command-line arguments. Use /? for help.");
return ERROR_INVALID_PARAMETER;
}

// If there are no unnamed arguments, use "." (the current computer).
if (args.Unnamed.length == 0)
computers = new FlexArg(".").list;
else {
// Create a FlexArg object using the first unnamed argument.
flexarg = new FlexArg(args.Unnamed(0));
// Abort with an error message if the FlexArg object has an error.
if (flexarg.err != 0) {
result = flexarg.err;
if (flexarg.listfile != "")
WScript.Echo("Error 0x" + result.hex() + " processing ""
+ flexarg.listfile + "": " + flexarg.errdesc);
else
WScript.Echo("Error 0x" + result.hex() + ": "
+ flexarg.errdesc);
return result;
}
computers = flexarg.list;
}

// Format all of the computer names in the array, and sort it.
for (n = 0; n < computers.length; n++)
computers[n] = formatcomputername(computers[n]);
computers.sort();

// Create a PrinterMgr object, and set the computers property to the
// array of computer names.
printermgr = new PrinterMgr();
printermgr.computers = computers;

// The /shared and /list options use the PrinterMgr object's
// ListSharedPrinters and ListPerMachinePrinters methods.
if (opt == "shared") {
printermgr.ListSharedPrinters();
return 0;
}
else if (opt == "list") {
printermgr.ListPerMachinePrinters();
return 0;
}

// Check if the argument to /exists|/add|/delete is null or empty.
if ((args.Named(opt) == null) || (args.Named(opt).trim == "")) {
WScript.Echo("Invalid command-line arguments. Use /? for help.");
return ERROR_INVALID_PARAMETER;
}

// Create a new FlexArg object to process the printer name (or list of
// printers).
flexarg = new FlexArg(args.Named(opt));
if (flexarg.err != 0) {
result = flexarg.err;
if (flexarg.listfile != "")
WScript.Echo("Error 0x" + result.hex() + " processing ""
+ flexarg.listfile + "": " + flexarg.errdesc);
else
WScript.Echo("Error 0x" + result.hex() + ": "
+ flexarg.errdesc);
return result;
}

// The FlexArg object's list property contains an array of printer
// names. Set the PrinterMgr object's printers property to this array.
printermgr.printers = flexarg.list;

if (opt == "exists") {
printermgr.ExistPrinters();
}
else if (opt == "add") {
printermgr.AddPrinters();
}
else {
printermgr.DeletePrinters();
}

return 0;
}