Oтзывы и предложения для stranichko.org.ua

автоподключение USBIP устройств на windows-сервере

Поднимаю избитую, изжеванную, но так толком и не освещенную в интернете тему автоматического подключения проброшенных посредством USBIP устройств, в особенности — капризных принтеров. Тут уже и технология Spice на подходе, а виндовый драйвер usbip все еще нулевой версии (0.2), и ни гуёв тебе, ни автоматизации, ни уверенности в отсутствии завтрашнего BSOD’а никакой. Поэтому, по беглому напутствию с форумов «пропиши скриптом автоподключение» приходится извращаться, дабы превратить из бесплатного решения некоторое подобие платного (USB over IP, например, где есть все от блекджека до сами знаете чегокого).

В большинстве случаев проще вообще не заморачиваться usbip для проброса принтеров на терминальный сервер, а использовать возможности штатного клиента сервера терминалов. Но данный подход обязывает устанавливать драйвера для принтера на клиентскую станцию. Все хорошо и понятно, но нужно учитывать тот факт, что принтеров в сети может быть огромное количество, в том числе и злосчастный Canon, который, мягко говоря, забил большой жирный болт на всяких там пингвинов и чёртиков. Также ситуация может усугубляться тем, что системы на клиенте вообще никакой может и не быть, и даже жесткого диска, и вообще, образ линукса грузится по сети, при чем образ один для всех. В таком случае, мало того, что все необходимые драйвера должны быть установлены на самом сервере терминалов, так нам еще придется создавать нагромождение глюковдрайверов уже в образе линукса, что будет крутится на тонких клиентах. Учитывая, что принтеров много, и вряд ли получится подружить всех их с линуксом, а также то, что все-равно придется как-то расшаривать всякие там флешки, токены/смарткарты для клиент-банкинга, донглы для всяких там Лиг, и видео-наблюдений я все-же остановился на пробросе устройств посредством USBIP.

Но бесплатный сыр сами знаете где. Хотя будь я даже директором крупного холдинга, платить по 100 вечнозеленых за проброс каждой usb-фенечки вряд ли бы согласился, не то что текущее руководство, которое хочет все и сразу, и обязательно забесплатно.

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

Итак, согласно инструкции все просто прекрасно — даем команду:

usbip -a [IP_машины] [идентификатор устройства, присвоенный на той стороне]

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

Проблема №1. Если связь с usbip-сервером (машиной, на которой расшарено устройство) была ВНЕЗАПНО прервана, т.е. тонкий клиент могли просто выключить — то usbip-клиент виснет намертво, и хорошо, если он был просто запущен из командной строки, можно нажать CTRL+C. А если он был запущен из скрипта, да еще от имени системной учетной записи, т.к. скрипт запущен как служба? Вроде бы решение назревает само-собой — все время пингуем станцию, если пинг пропадает — убиваем процесс usbip, вот как описано здесь. Но как оказалось, не все так просто. Во первых, решение, описанное по предыдущей ссылке у меня адекватно не заработало при проброшенном десятке устройств, собственно из-за чего и пишется данная статья. Во-вторых, если убить процесс usbip из скрипта, то его бездыханная тушка еще долго будет конвульсивно трепыхаться в памяти, прежде чем действительно завершится. Именно завершать процессы usbip я решил по причине того, что если связи с тонким клиентом все также нету, то у меня usbip при подключении вис, не помню уже точно, но вроде надолго.

Решение было принято кардинальное — в цикле все так же пингуем станцию, если нет связи, убиваем процесс usbip, но убиваем хитро, а именно закрываем его хэндлы. Сначала хэндл \Device\0000003a, после чего процесс завершается если уже был произведен проброс устройства. Также закрываем хэндл \Device\Afd в таком случае процесс завершится гарантированно, даже если он в этот момент просто ломился на тонкий клиент. С запуском usbip с параметром -d я морочиться не стал, ибо непонятно, как себя поведет USBIP в том случае, если у нас проброшено несколько устройств с одинаковым идентификатором. Итак нам понадобятся такие утилиты от Марка Руссиновича:

Название утилиты handle говорит само за себя, она нужна нам для того, чтобы управлять дескрипторами приложения. А вот Process Explorer нужен для того, чтобы наглядно посмотреть номера нужных нам дескрипторов (номера в каждой системе вроде как отличаются) да и просто для того, чтобы было удобно наблюдать за процессом работы скриптов. Но прошу обратить внимание на то, что драйвер Process Explorer’a procexp141.sys свалил мой сервер терминалов в BSOD, благо, это было в нерабочее время. Так что настоятельно рекомендую крайне осторожно устанавливать какие-либо драйвера, утилиты и тому подобное на сервер, в котором работают десятки пользователей и отказоустойчивость которого должна быть превыше всего.

UPD 19.10.11. В процессе использования обнаружилось, что синий экран появляется все-таки из-за утилиты handle (или от совокупности handle и process explorer, при отображении в последнем открытых дескрипторов) в том случае если скрипт работает несколько суток подряд. На данный момент активно ищется решение данной проблемы. Возможными решениями являются включение файла подкачки а также запуск скриптов только в нужное нам время. В любом случае — следите за обновлениями статьи.

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

Итак, создадим скрипт с условным названием _pinger.vbs (подчеркивание нужно для выразительности из сотни скриптов, об этом позже) и таким содержимым:

Do While True

	for count = 1 to 100

		wshell.Run "cmd /c c:\USBIP\ping.exe 192.168.0." & count & " _
-n 1 -l 1 -4 -w 1 > pinger.output", 1, True

		if objFSO.FileExists("c:\USBIP\pinger.output") then

			set f = objFSO.GetFile("c:\USBIP\pinger.output")
			set ts = f.OpenAsTextStream(ForReading, 0)

			Do While ts.AtEndOfStream <> True

				TextLine = ts.ReadLine

				if Instr(TextLine, "Lost = 1") then 

					Set colProcessList = objWMIService.ExecQuery ("SELECT * FROM _
Win32_Process WHERE Name = 'usbip.exe' AND CommandLine LIKE '%192.168.0." & count & " %'")

					For Each objProcess in colProcessList

						wshell.Run "handle -p " & objProcess.ProcessId & " -c 128 -y", 1, True '\Device\0000003a
						wshell.Run "handle -p " & objProcess.ProcessId & " -c EC -y", 1, True '\Device\Afd

					Next

				end if

			Loop

			ts.Close
				
		end if

		WScript.Sleep 50

	next

	'WScript.Sleep 1000

Loop

Итак, в бесконечном цикле «Do While True … Loop» выполняем такие команды:

1. Цикл от одного до 100, что будет означать перебор всех IP-адресов 192.168.0.1 до 192.168.0.100.

2. Далее выполняем пинг каждого адреса с параметрами ‘-n 1 -l 1 -4 -w 1’ и выводом в файл pinger.output, который мы будем далее парсить. Конструкцию предваряет «cmd /c». Без этой конструкции не будет работать команда вывода в файл «… > file.txt». Параметры ping означают, что число отправляемых запросов будет равно одному (-n 1), размер буфера также будет равен единице (-l 1), используемым протоколом будет IPv4 (-4), и, наконец, минимальный таймаут, в 1 миллисекунду (-w 1). Такие параметры позволят нам сэкономить время на определении, есть ли станция на связи, или нет. Конечно, данный подход требует безупречного функционирования локальной сети.

3. После этого открываем для чтения файл с результатами пинга станции, и ищем там конструкцию Lost = 1, что подразумевает отсутствие станции на связи. Если это произошло, то ищем процесс «usbip.exe» запущенный с параметром в виде нужного нам IP-адреса, узнаем его process-id и передаем этот параметр утилите handle, которая и начнет закрывать дескрипторы данного процесса, что приведет к его закрытию. Не ошибитесь при построении запроса запущенных процессов. В конструкции

...AND CommandLine LIKE '%192.168.0." & count & " %'" 

важен пробел перед закрывающим процентом %, который означает инструкцию «все остальное не имеет значения». Без этого пробела поиск процесса, запущенного с параметрами 192.168.0.1 найдет также и 192.168.0.10, 192.168.0.11 и т. д.

Здесь, и далее по статье конструкция wshell.Run запускается с параметрами «1, True», что означает: стандартно отобразить окно приложения и ждать его завершения, это не даст скрипту обрабатывать последующие команды до завершения текущей.

Проблема №2. Данная проблема разветвляется сразу на три: во-первых: если удалить неактивный принтер, после того, как тонкий клиент был выключен, то при следующем переподключении некоторые принтеры уже не появляются сами по себе и нужно вручную зайти в Диспетчер устройств и нажать там кнопку «Обновить конфигурацию оборудования». Во вторых: в некоторых случаях не помогает даже обновление конфигурации в Диспетчере устройств. В таком случае нужно вручную удалить устройство «Устройство поддержки USB-принтера» и обновить конфигурацию оборудования, или же устанавливать принтер вручную, вручную указав действительный порт USB0xx. В третьих: порядок подключения устройств к серверу нам заранее не известен, а драйвер usbip вешает каждое устройство на любой свободный порт, который нумеруется как USB001, USB002 и так далее. Таким образом сейчас у нас принтер может висеть на порту USB002, а завтра уже на порту USB006. Соответственно, даже если не удалять неактивный принтер, то в случае изменения номера порта будет создан новый принтер, с таким же названием, но уже активный и пригодный для работы. Количество вновь создаваемых принтеров будет возрастать пропорционально количеству всех проброшенных USB-устройств.

Решил я эту проблему таким образом: в цикле подключаем устройство командой usbip -a [ip] [id] и ждем ее завершения, после чего удаляем все задания печати для данного принтера, удаляем сам принтер и удаляем ветку реестра, которая характеризует устройство поддержки USB-принтера. Если же есть проблемные принтеры, которые не устанавливаются автоматически как обычные PnP-устройства, то параллельным скриптом в цикле сканируем наличие в системе устройства с именем принтера, и, если таковое имеется — устанавливаем его посредством командной строки с помощью команды «rundll32 printui.dll,PrintUIEntry …»

Для реализации задуманного нам понадобятся такие утилиты:

Утилита Devcon нам понадобится для выполнения функций Диспетчера устройств, только из командной строки. Так как ветку реестра, отвечающую за устройства нам просто так не удалить, не дав соответствующие права нужным пользователям — используем утилиту Regperm, с помощью которой можно из командной строки установить необходимые права на любую ветку реестра.

Текст скрипта, который автоматически подключает принтер, и, если он был отключен, чистит реестр и удаляет сам принтер:

Const HKEY_CLASSES_ROOT  = &H80000000
Const HKEY_CURRENT_USER  = &H80000001
Const HKEY_LOCAL_MACHINE = &H80000002
Const HKEY_USERS         = &H80000003

strComputer = "." 
Set objWMIService = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2") 
Set objFSO = CreateObject("Scripting.FileSystemObject")
set wshell = WScript.CreateObject("Wscript.Shell")
Set objRegistry = GetObject("winmgmts:\\" & strComputer & "\root\default:StdRegProv") 

wshell.CurrentDirectory = "C:\USBIP"

Do While True

	wshell.Run "usbip -a 192.168.0.16 2-1", 1, True

	Set colInstalledPrinters = objWMIService.ExecQuery ("Select * from Win32_Printer") 

	For Each objPrinter in colInstalledPrinters 
		if Instr(objPrinter.Name, "сервисный отдел") then

			objPrinter.CancelAllJobs()
			objPrinter.Delete_

			strKeyPath = "SYSTEM\CurrentControlSet\Enum\USBPRINT\SamsungML-1210"
			wshell.Run "regperm.exe /A:Администраторы:F /K " & chr(34) & "HKEY_LOCAL_MACHINE\" _
& strKeyPath & chr(34) & " /f /i", 1, True
			DeleteSubkeys HKEY_LOCAL_MACHINE, strKeypath 

		end if
	Next 

	WScript.Sleep 4000

Loop

Sub DeleteSubkeys(HKEY_LOCAL_MACHINE, strKeyPath) 
	objRegistry.EnumKey HKEY_LOCAL_MACHINE, strKeyPath, arrSubkeys 
	If IsArray(arrSubkeys) Then 
		For Each strSubkey In arrSubkeys 
			DeleteSubkeys HKEY_LOCAL_MACHINE, strKeyPath & "\" & strSubkey 
		Next 
	End If 
	objRegistry.DeleteKey HKEY_LOCAL_MACHINE, strKeyPath 
End Sub

Тут все также в бесконечном цикле пытаемся подключить устройство, а после его отключения ищем принтер с названием, которое выделяет его среди всех остальных. В данном примере — «сервисный отдел». Методами CancelAllJobs, и Delete_ (подчеркивание обязательно) очищаем очередь печати и удаляем принтер. Далее, изменяем разрешения на ветку реестра описывающую устройство USB-принтера, которая обычно названа именем принтера и находится по пути HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USBPRINT\, а именно — даем группе Администраторы полные права командой «regperm /A:Администраторы:F … /f /i», где параметр «/f» означает присвоение аналогичных разрешений дочерним веткам, а параметр «/i» указывает на дополнительное наследование прав от родительских веток, дабы не удалить права для системной учетной записи «SYSTEM». После этого рекурсивно удаляем данную ветку. Здесь кроется большая проблема, которую я пока так и не смог решить — мы удаляем устройства всех принтеров с таким названием, что создаст проблемы, если в сети есть принтеры с одинаковыми названиями. Буду признателен, если кто-нибудь выскажет свои идеи на этот счет. Отмечу также, что если мы дали права изменение ветки группе администраторов, то и запуск скрипта (или службы, об этом ниже) должен производится от имени администратора.

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

Const ForReading = 1, ForWriting = 2, ForAppending = 3
strComputer = "."
Set objWMIService = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set objFSO = CreateObject("Scripting.FileSystemObject")
set wshell = WScript.CreateObject("Wscript.Shell")

wshell.CurrentDirectory = "C:\USBIP"

Do While True

	Set colInstalledPrinters = objWMIService.ExecQuery ("Select * from Win32_Printer where _
Name = 'сервисный отдел'") 

	if colInstalledPrinters.Count > 0 then
		WScript.Sleep 10000
	else	
		
		wshell.Run "cmd /c devcon findall *SamsungML-1210* > c:\usbip\ivanov.p.p.output"

		if objFSO.FileExists("c:\usbip\ivanov.p.p.output") then

			set f = objFSO.GetFile("c:\usbip\ivanov.p.p.output")

			set ts = f.OpenAsTextStream(ForReading, 0)

			Do While ts.AtEndOfStream <> True

				TextLine = ts.ReadLine

				if Instr(TextLine, "USBPRINT\SAMSUNGML-1210") then 

					PrtName = mid(TextLine, Instr(TextLine, "USB0"), 6)

					wshell.Run "rundll32 printui.dll,PrintUIEntry /if /z /b " & chr(34) & "сервисный отдел" _
& chr(34) & " /u /r " & chr(34) & PrtName & chr(34) & " /f " & chr(34) _
& "%windir%\ML-1210\SPLV1.inf" & chr(34) & " /m " & chr(34) _
& "Samsung ML-1200 Series" & chr(34), 1, True

					exit do

				end if

			Loop

			ts.Close

		end if

		WScript.Sleep 5000

	end if

Loop

В этом скрипте мы проверяем наличие нужного на принтера, если он есть — делаем небольшую паузу и начинаем все заново, если нету — проверяем наличие нужного устройства. Наличие устройства будем проверять утилитой devcon, которая укажет нам порт, на котором висит принтер, будь то USB001 или USB084. Порт нам нужен для того, чтобы вручную, с помощью командной строки установить принтер. В этом есть своя положительная сторона — принтер мы создаем сразу с нужными драйверами и нужным, удобочитаемым названием. Итак, по порядку.

Сначала выполняем команду «devcon findall *SamsungML-1210*», вывод которой направляем в файл ivanov.p.p.output Тут следует отметить, что названия скриптам, и результирующим файлам я давал в соответствии с доменными логинами, для более легкого ориентирования в большом количестве скриптов а также предварял подчеркиванием общие скрипты типа _pinger.vbs, _set_online_printers.vbs и прочих. Если устройство найдено, то в файле ivanov.p.p.output появится строка следующего вида:

USBPRINT\SAMSUNGML-1210\2&2451A4D6&0&USB006                 : SamsungML-1210 

где USB006 и есть порт, на котором сейчас висит принтер. Командой

mid(TextLine, Instr( TextLine, "USB0" ), 6) 

вырезаем это название из строки и после этого уже даем команду на установку принтера ‘rundll32 printui.dll,PrintUIEntry /if /z /b «сервисный отдел» /u /r USB006 /f «%windir%\ML-1210\SPLV1.inf» /m «Samsung ML-1200 Series»‘ где параметр /if указывает на то, что будет использоваться конкретный *.inf файл, параметр /z указывает на то, что давать общий доступ принтеру не нужно, /b задает имя принтера, /u означает использование уже существующего драйвера, если таковой имеется. Это поможет нам в том случае, если драйвер не подписанный, и из скрипта принтер не установится, даже при отключенном предупреждении о подписях драйверов. Поэтому нужно один раз установить принтер вручную, подтвердить использование не подписанного драйвера, а уже в дальнейшем установка будет проходить без вопросов. Параметр /r задает порт, который мы определили ранее, /f указывает путь к *.inf-файлу, и, наконец, параметр /m указывает модель принтера, по которой будет вестись поиск в *.inf-файле. Отмечу, что параметры данной команды регистрозависимы, так что будьте внимательны! Подробнее о возможных параметрах можно почитать здесь.

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

set wshell = WScript.CreateObject("Wscript.Shell")

wshell.CurrentDirectory = "C:\USBIP"

Do While True

	wshell.Run "usbip -a 192.168.0.3 1-4", 1, True

	WScript.Sleep 1000

Loop

Для идеальной работы нам еще понадобятся такие скрипты:

  • скрипт переименования принтеров в удобочитаемые названия. Этот скрипт предназначен для самоустанавливающихся при подключении принтеров:
  • strComputer = "." 
    
    Do While True
    
    	Set objWMIService = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2") 
    	Set objFSO = CreateObject("Scripting.FileSystemObject")
    	Set colInstalledPrinters = objWMIService.ExecQuery ("Select * from Win32_Printer") 
    
    	For Each objPrinter in colInstalledPrinters 
    
    		if Instr(objPrinter.Name, "1606") then
    
    			objPrinter.RenamePrinter "бухгалтерия"
    
    		end if
    
    		if Instr(objPrinter.Name, "1510") then
    
    			objPrinter.RenamePrinter "инженеры"
    
    		end if
    
    	Next 
    
    	WScript.Sleep 1000
    
    Loop
    
  • скрипт установки оперативного режима принтеров. Предназначен для принтеров, которые подключены по LPT и не всегда при переподключении становятся активными (работающими в оперативном режиме):
  • Set Shell = CreateObject("Shell.Application")
    
    Do While True
    
    	Set objFolder = Shell.NameSpace(4)
    	For each printer in objFolder.Items
    
    		if Instr(printer, "бухгалтерия") then 
    
    			If objFolder.GetDetailsOf(printer, 2)="Не подключен" then
    
    				printer.InvokeVerbEx("&Использовать принтер в оперативном режиме")
    
    			end if
    
    		end if
    
    	Next
    
    	WScript.Sleep 60000
    
    Loop
    

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

  • и наконец, инициализирующий остальные скрипт:
  • set wshell = WScript.CreateObject("Wscript.Shell")
    wshell.CurrentDirectory = "C:\USBIP"
    
    wshell.Run "_pinger.vbs"
    wshell.Run "_rename_printers.vbs"
    wshell.Run "_set_online_printers.vbs"
    
    wshell.Run "petrenko.e.s.vbs"
    wshell.Run "vasilenko.e.a.1.vbs"
    wshell.Run "vasilenko.e.a.2.vbs"
    wshell.Run "ivanenko.l.d.vbs"
    wshell.Run "adminenko.s.e.1.vbs"
    wshell.Run "adminenko.s.e.1.1.vbs"
    wshell.Run "directorenko.a.v.1.vbs"
    wshell.Run "directorenko.a.v.1.1.vbs"
    

Данный скрипт будет запускаться в качестве службы. Для этого нам понадобятся такие утилиты, входящие в состав Windows Resource Kits:

  • srvany
  • instsrv

Для запуска скрипта как сервиса нам необходимо скопировать утилиту srvany в папку c:\WINDOWS\system32\ и выполнить команду «instsrv.exe USBIP c:\WINDOWS\system32\srvany.exe». Эта команда создаст службу с названием USBIP. После этого нам необходимо прописать, что конкретно эта служба будет запускать. Для этого идем в реестр по пути HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\USBIP и добавляем раздел «Parameters», в котором добавляем REG_SZ-параметр с именем «Application» и значением «cscript c:\usbip\_start.vbs», где _start.vbs и есть название нашего инициализирующего скрипта. В данном примере также нужно зайти в свойства данной службы и назначить ее запуск от имени Администратора, т.к. права на модифицирование веток реестра мы даем группе администраторов. Также, для того, чтобы утилита handle запустилась под системной учетной записью, нам нужно «принять лицензионное соглашение» под ней. Но так как само окно с лицензионным соглашением мы не увидим, даже запустив процесс под системной учеткой, мы поступим проще, а именно создадим в реестре такой ключ «HKEY_USERS\.DEFAULT\Software\Sysinternals\Handle» с DWORD-параметром EulaAccepted и значением 1.

Также советую установить на запуск каждому пользователю вот такой небольшой, но не менее полезный скрипт, который делает ближайший принтер принтером по-умолчанию:

On Error Resume Next

strComputer = "." 

WScript.Sleep 5000

Do While True

	Set objWMIService = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2") 
	Set objFSO = CreateObject("Scripting.FileSystemObject")
	Set colInstalledPrinters = objWMIService.ExecQuery ("Select * from Win32_Printer") 

	For Each objPrinter in colInstalledPrinters 

		if Instr(objPrinter.Name, "менеджеры") then

			objPrinter.SetDefaultPrinter

		end if
	Next 

	WScript.Sleep 10000
Loop

Обращаю внимание на конструкцию On Error Resume Next, которая указывает серверу сценариев не обрабатывать ошибки и продолжать работу скрипта в случае возникновения оных. Дело в том, что команда objPrinter.SetDefaultPrinter не всегда отрабатывает корректно, поэтому, будем повторять попытку установки принтера по-умолчанию даже в случае ошибки.

Также хочу заметить, что во время всей этой эпопеи я столкнулся с тем, что под некоторые устройства начисто отсутствуют драйвера под x64 систему. Для этого пришлось параллельно подымать домен (виртуальную машину в терминологии гипервизора Xen) с Windows XP Pro SP3 на борту. Но имейте в виду, что лично у меня под Windows XP usbip.exe второй версии категорически отказался запускаться. Помог только откат до версии v0.1, при чем достаточно заменить только исполняемый файл.

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

Преимуществом использования vbs-скриптов перед auto-it программированием в данном случае является меньшее потребление памяти. В нашем случае выделяется память только под процессы usbip, сам процесс скрипта занимает намного меньше памяти нежели скомпилированный auto-it скрипт. Данное преимущество становится ключевым при подключении большого количества устройств. Также в случае с vbs-скриптами все прозрачно и понятно, и при необходимости быстро модифицируется.

Ну что ж, подытожим. Для корректной работы всей связки нам нужны следующие утилиты:

  • devcon
  • handle
  • ping
  • regperm
  • instsrv
  • srvany
  • и, собственно сам usbip

Необходимые скрипты:

  • _start.vbs — инициализирующий скрипт
  • _set_online_printers.vbs — скрипт, устанавливающий оперативный режим LPT-принтерам
  • _rename_printers.vbs — скрипт, переименовывающий принтера в удобоваримые названия
  • _pinger.vbs — средство для контроля за наличием рабочих станций на связи
  • пользователь1.1.vbs — пример скрипта подключения обычного usb-устройства
  • пользователь1.2.vbs — пример скрипта подключения второго usb-устройства
  • пользователь2.1.vbs — пример скрипта подключения обычного принтера и последующего удаления его следов при отключении
  • пользователь3.1.vbs — пример скрипта подключения проблемного принтера, который не устанавливается автоматически
  • пользователь3.1.1.vbs — пример скрипта, который отслеживает подключение устройства проблемного принтера и устанавливает его

скачать одним архивом: scripts.zip (408 Загрузок)

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

VN:F [1.8.8_1072]
Rating: 5.0/5 (1 vote cast)
автоподключение USBIP устройств на windows-сервере5.051
30 270 просмотров

23 комментария на “автоподключение USBIP устройств на windows-сервере”

  • Syscomua:

    Спасибо, Товарищ. Хороший выход нашли из положения. Еще учитывая что это работает в режиме нон стоп то просто Браво. Я сейчас занимаюсь подобным маразмом. Огромная проблема с глухим зависанием утилиты.

    • Всегда пожалуйста! В дальнейшем хотелось бы узнать Ваш результат внедрения usbip в энтерпрайз.

      • Syscomua:

        Ну вот уже 4 месяц все работает таким образом. На трминале под Ubuntu работает скрипт, который отслеживает вывод usbip —l. Если там отсутствует строка 4-2:1.0 -> usbip , то заново выполняем который выполняет тупо выполняет сначала комманду отключения девайса и подключения (usbip -d 1 и -a) и остается в бесконечном цикле пока USBIP выполняется. При его завершении перезапускается скрипт. Проблемы: иногда устройство на виндовс машине монтируется в 2 порт и никакой «деататч» не помогает 2 — проблема: иногда при отключении устройства зависает UsbIP в терминале и приходится убивать демон — процесс. Я с линуксами дружу крайне мало и со скриптовіми язіками знаком слабо. Вот опять сел искать решения

        • спасибо за репорт! подтверждаю. У меня также все это работает в энтерпрайзе с сентября 2011 — уже 9 месяцев nonstop, на нескольких серверах. Проблемы с выпадами в bsod из-за частого закрытия дескрипторов я решил пока что в лоб, а именно — убрал из сценария утилиту handle вообще. В итоге, если устройство отпало — через некоторое время процесс usbip отваливается сам по себе. Хотел было разработать свой аналог утилиты, которая более аккуратно (с меньшими телодвижениями) закрывала бы хэндлы висящего usbip, но уперся тогда в некоторые особенности разработки под x64-архитектуру. Теперь ни времени ни желания продолжать нету. Также оказалось, что драйвер одного из принтеров под win2003 x64 был сильно глючным, именно из-за него чаще всего сервер терминалов и выпадал в синий экран. Решил проблему вынесением принтера на другой сервер, где он цепляется таким же скриптом usbip и расшаривается для основного сервера. Мда, костыль, ничего не скажешь, но тем не менее… Я к тому, что, возможно, следует все-же попытаться убивать процессы usbip утилитой handle, как и описано в статье.

          по поводу скрипта в бубунте. В моем случае написан один скрипт, запускающийся на каждом LTSP-клиенте. В бесконечном цикле делаем команду листинга устройств, потом по mac-адресу сетевухи определяем, что за компьютер, на котором выполняемся. Далее в файле /tmp/usbip ищем id девайса, выдираем порт, на котором этот девайс висит (ведь его могут воткнуть в любой из портов 😉 ) и пробрасываем. Никаких отключений девайса я не делаю. Вот примерный текст скрипта:

          while true; do

          usbip_bind_driver —list2 > /tmp/usbip

          #ivanov.a.a
          if [ -n «$(ifconfig | grep -o ff:ff:ff:ff:ff:ff)» ]; then
          if [ -n «$(cat /tmp/usbip | grep 8888:8888 | grep usblp)» ]; then
          BUSID=`awk ‘-F#’ ‘$2==»usbid=8888:8888″ {print $1}’ /tmp/usbip > /tmp/busid`
          USBIP=`awk ‘-F=’ ‘$1==»busid» {print $2}’ /tmp/busid`
          usbip_bind_driver —usbip $USBIP
          fi
          if [ -n «$(cat /tmp/usbip | grep 9999:9999 | grep usb-storage)» ]; then
          BUSID=`awk ‘-F#’ ‘$2==»usbid=9999:9999″ {print $1}’ /tmp/usbip > /tmp/busid`
          USBIP=`awk ‘-F=’ ‘$1==»busid» {print $2}’ /tmp/busid`
          usbip_bind_driver —usbip $USBIP
          fi
          fi

          sleep 5

          done

          где поиск вхождения строк usblp или usb-storage подразумевает то, что они на данный момент не проброшены.

          Из проблем: некоторые флешки, после проброса наглухо вешают демон usbipd. Никакие перезапуски демона не помогают, только перезагрузка станции. Также при выдергивании флешки она больше не увидится демоном до перезапуска системы, чего не скажешь, например, про принтеры.

  • aka:

    «Но данный подход обязывает устанавливать драйвера для принтера на клиентскую станцию.» — не знаю, откуда пошла эта теория, но с сожалением признаю, что я сам ее везде повторял. Так вот, это НЕ правда. Сейчас WTware научилась перенаправлять принтеры через RDP и логика там в точности такая же, как и у эмуляции принтсервера lp_server. Работают практически все принтеры кроме кэнонов. Никаких драйверов на клиенте мы для этого не ставили, принтеро-зависимая там только заливка прошивок в принтеры типа HP1020.

    По теме: в дистрибутив wtware входит наша виндовая служба wtusbip. На стороне виндовса она заменяет usbip.exe и решает проблему1, просыпаясь и обращаясь к терминалу по команде самого терминала. Интерфейс управления ею с терминала простой, можно спросить у нас на форуме, можно посмотреть сниффером. С проблемой2 разбираемся, есть надежда решить ее красиво и опять же безо всяких вешних костылей. Наши серверные службы работают бесплатно 🙂

    • все принтеры кроме кэнонов

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

      в дистрибутив … входит наша виндовая служба wtusbip … Наши серверные службы работают бесплатно

      а какой смысл, если она входит в дистрибутив, и соответственно, поставив его — компания попадает под лицензионную ответственность? Насколько я понял — отдельного дистрибутива нету.

      С проблемой2 разбираемся

      а теперь смысл Вашего комментария (кроме саморекламы, конечно) ??

      • aka:

        > А если у меня подавляющее большинство этих злопринтеров?

        То вам придется использовать USBIP. Я только хотел отметить, что утверждение «данный подход обязывает устанавливать драйвера для принтера на клиентскую станцию» про проброс принтера через RDP — распространенное заблуждение.

        > соответственно, поставив его – компания попадает под лицензионную ответственность?

        Хм. Технических средств лицензирования у наших серверных служб нет, «купить лицензию» на них невозможно: лицензируются только терминалы. Но формально вы правы. Мне эта мысль в голову не приходила, даже когда я чинил наш tftp по жалобе «с него синстейшн не загружется».

        > Насколько я понял – отдельного дистрибутива нету.

        Где надо написать «использование наших серверных служб бесплатно, установка wtware на сервер с целью использования серверных служб разрешена без заключения договора и оплаты», чтобы люди по этому поводу не волновались?

        > а теперь смысл Вашего комментария

        В статье написано:

        > Вот такой вот монструозный скрипт получился…
        > Буду рад любым … более изящным решениям.

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

        По проблеме2 пришли положительные результаты от пользователей. Достаточно обучить определенное устройство на определенном терминале при каждом переподключении виснуть на один и тот же порт, и принтеры больше не размножаются. Это нельзя сделать через штатную usbip.exe, но драйвер usbip это умеет делать. Можно дописать и пересобрать usbip.exe, можно (реклама зачеркнута) 🙂

        • Можно дописать и пересобрать usbip.exe, можно (реклама зачеркнута) 🙂

          ну что ж, спасибо за идею. Действительно было бы изящнее, но реализовать это сложнее, нежели наклепать с десяток скриптов.

          Где надо написать «использование наших серверных служб бесплатно, установка wtware на сервер с целью использования серверных служб разрешена без заключения договора и оплаты», чтобы люди по этому поводу не волновались?

          конечно, ибо порой нужно читать EULA даже к какому-нибудь драйверу. Таковы уж обязанности системного администратора — контроль за лицензионной чистотой.

  • Andry:

    Может кому пригодится. Есть сервер UNIX с вирт.машинами. Столкнулся с проблемой: при перезагрузке вирт.машины(клиента usbip) HASP ключи не подключаются повторно через usbip. Решение нужно корректно отключить их перед перезагрузкой или выключением, небольшим скриптом GPO. В виндовом оболочке команда в linux с сервером usbip: ПУТЬ_ДО_ФАЙЛА\plink.exe -ssh -pw ПАРОЛЬ_ОТ_КОМПА_С_USBIP_СЕРВЕРОМ [email protected]ИП_СЕРВЕРА usbip_bind_driver —other ПОРТ_ДЛЯ_ОТКЛЮЧЕНИЯ

    • спасибо за совет. Но меня вот больше интересует, что делать с проброшенной с тонкого клиента флешкой, которая была вытащена без предупреждения из того же тонкого клиента. На клиенте USBIP (т.е. там, куда пробрасываем флешку) она больше не появится и заново «расшарить» на тонком клиенте эту флешку БЕЗ ПЕРЕЗАГРУЗКИ тонкого клиента уже не получится. Перезапуск usbip_bind_driver никак не помогает.

  • Andry:

    У меня получилось, в VBS создал цикл на 2 порта и отключал на горячую. Все нормально подключалось. Правда постоянно скрипт висит в оперативе, но нагрузки нету.

    Пример:

    do while i<1
    wshell.Run "c:\usbip\usbip1\usbip.exe -a 192.168.0.1 1-1",0,true
    wshell.Run "c:\usbip\usbip2\usbip.exe -a 192.168.0.1 1-2",0,true
    loop

  • Спасибо за проделанный труд. С большим интересом почитал. Не все понятно.
    Наверное что я сам весь слаб в VBS скриптах.

    Есть вопрос такой.
    Как выяснить, какой ID присвоен нужному нам принтеру на стороне сервера — понятно:
    usbip -l 192.168.0.1

    А вот как вывод этой команды (вернее именно номер BUSID) передать потом команде
    usbip -a 192.168.0.1 — не понятно.
    Я столкнулся с тем, что программа usbip.exe при ключе -l не выводит в stdout. Соответственно не удается завернуть этот вывод в файл.
    usbip -l 192.168.0.1 > 1.txt приводит к наличию файла 1.txt нулевой длины.
    Хотя usbip -h >help.txt отрабатывает нормально и файл help.txt наполняется.

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

  • Здравствуйте, Евгений. Дело в том, что список устройств (ихние BUSID), которые можно пробросить выдает демон usbip_bind_driver, запущенный с параметром -l на стороне сервера (машины, с которой пробрасываем устройство)

    usbip_bind_driver -l

    и выдает эта команда примерно такой результат:

    List USB devices
    — busid 1-4 (04a9:1904)
    1-4:1.0 -> none

    также существует команда сокращенного вывода списка устройств для последующей обработки выведенного текста скриптами:

    usbip_bind_driver —list2

    и вывод у данной команды будет таков:

    busid=1-4#usbid=04a9:1904#1-4:1.0=none#

    у себя я реализовал так: на сервере скрипт запускает данную команду и если находит в ней определенный идентификатор устройства (то что в данном примере 04a9:1904) то смотрит, на каком BUSID висит устройство (в данном примере 1-4), далее скрипт запускает уже саму команду проброса:

    usbip_bind_driver —usbip 1-4

    на стороне клиента же (windows-машинки, на которую пробрасываем устройство) запущенны скрипты, которые в бесконечном цикле пытаются подключить ВСЕ устройства 1-1, 1-2, 1-3, 1-4 и т.д . (командами usbip -a 192.168.0.1 1-x) с данной машинки. Таким образом, в какой порт не воткнул бы пользователь устройство — оно с одинаковым успехом пробросится на windows-машинку.

    вот примерный вид скрипта, что крутится на сервере (машинке, с которой пробрасываем):

    #!/bin/sh
    #
    while true; do
    usbip_bind_driver —list2 > /tmp/usbip
    if [ -n «$(cat /tmp/usbip | grep -o 04a9:1904)» ]; then
    BUSID=`awk ‘-F#’ ‘$2==»usbid=04a9:1904″ {print $1}’ /tmp/usbip > /tmp/busid`
    USBIP=`awk ‘-F=’ ‘$1==»busid» {print $2}’ /tmp/busid`
    usbip_bind_driver —usbip $USBIP
    exit
    fi
    sleep 5
    done

    вывод же списка устройств на windows машинке можно перенаправить в файл, но делать это нужно так:

    usbip -l 192.168.0.1 2> 1.txt

    обращаю внимание, что в данном случае вместо операнда «>» мы используем «2>»

    • Спасибо большое.
      usbip -l 192.168.0.1 2> 1.txt очень помогло.

    • так же хочу отметить, у меня нет в сборке usb_bind_driver.
      для просмотра — кто на usb я использую
      # usbip list -l
      Local USB devices
      =================
      — busid 4-1 (04e8:344f)
      4-1:1.0 -> unknow
      4-1:1.1 -> usbip-host

      для запуска
      modinfo usbip-host
      modinfo usbip-core
      usbip bind -b 4-1

      возможно у нас разные версии…

  • DimonW:

    ВНИМАНИЕ!! подозрение на то, что по ссылке вирус!! (прим. автора блога)

    как все скорее всего знают, очень большой недостаток клиента usbip. он при обрыве связи виснет, и если у нас таких клиентов много, а вылетел только один, как определить что это нужный? фактически не реально … это переписанный .ехе . с возможностью вешать наши девайсы на определенный порт. к примеру
    «> usbip-p.exe -а 10.10.1.1 2-2 1»
    и наш принтер «повесится» на порт 1 и при обрыве связи, мы просто и безболезненно можем его «деатачить» «> usbip.exe -d 1» .

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

    Пользуемся 🙂 тестируем…

    • это переписанный .ехе . с возможностью вешать наши девайсы на определенный порт

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

      он скорее всего работать перестанет.

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

  • DimonW:

    Исходники взяты с http://usbip.svn.sourceforge.net/ и заменены несколько переменных, по сути это тот же usbip.exe . С некоторыми поправками, по этому если у вас подозрение на вирь,советую проверить оригинальный, то же =будет :). Уверяю на 1000% .
    А по поводу то что перестанет работать, там реализована куча проверок на правильность ввода и прочее. По этому некоторые функции отвалились , на пример «-l». По этому её я юзаю через оригинальный .exe . А само подключение непосредственно через модифицированный.

    • советую проверить оригинальный, то же =будет . Уверяю на 1000% .

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

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

    • Вы меня в конец запутали. Сначала Вы пишете:

      если у вас есть «веселый велосипед» основанный на родном .exe то он … работать перестанет.

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

      А по поводу то что перестанет работать, там реализована куча проверок на правильность ввода и прочее. По этому некоторые функции отвалились

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

      P.S. Вы добились своей цели, вызвали во мне бурление говн. Трололо.

  • DimonW:

    Ну в принципе не претендую на премию по грамматике .. всегда с русским беда была.
    По поводу того, что нарушена логическая цепочка .. пока не показали, не заметил/
    Да, не будет велосипед с модифицированным .exe работать.

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

    • если уж просто выложили бинарник, то и так же просто будет выложить исходники, я полагаю. Иначе и тему можно закрыть, как не несущую какой-либо ценности. Спасибо, за попытку помочь (возможно).

Оставить комментарий

(обязательно)