Автоматическое подключение сетевых МФУ с возможностью сканирования [Часть 2]
Как и обещал в первой части, за которую я успешно получил инвайт в песочнице, в этой заметке я покажу как подключить сетевые МФУ Kyocera M2035dn, Xerox WorkCentre 3615 и 6505DN, а в конце статьи добавлю небольшой бонус с которым любой скрипт связанный с сетью становится лучше.
Как бы небыли прекрасны гомогенные инфраструктуры, пусть даже в части принтеров и мфу, реальность зачастую ставит свои условия. В то время как пользователи сами в полный рост подключали и успешно сканировали с некогда проблемных МФУ HP, в компанию приехал японский гость — Kyocera M2035dn.
Приехал как всегда не в мое уютное админское логово, а сразу на объект и как и мфу от HP, в глаза его я, если честно, даже не видел.Первым делом качаем драйвер и смотрим содержимое… ба, знакомые все люди:
Есть пометка о том, что подключение сетевое (network) и есть ID! Попробуем подключить сканер через devcon, подобно тому как мы подключали МФУ от HP в первой части:.\devcon.exe /r install C:\Drivers\Scanners\2035dnscan\kmwiadrv.inf «KM_WC_ECOSYS_M2035dn_N_WIA»
Сканер подключился, прописываем в реестр адрес сканера параметром ScannerAddress и запускаем сканирование. Приложение сканирования показало отсканированный лист, все работает отлично. Казалось бы победа, но запуск второй, используемой у нас программы для сканирования, поубавил радости — сканер в ней не отображался.
Оказывается разработчики Kyocera почему-то в драйвере реализовали сканирование только через WIA, для TWAIN надо ставить отдельный косты. враппер, который пробрасывает TWAIN интерфейс в WIA и возвращает обратно результат. Выглядит гуй этого TWAIN драйвера следующим образом:
При этом, по WIA мы можем подключить несколько сканеров Kyocera, в то время как TWAIN интерфейс у нас будет всегда только один. Либо пользуйтесь WIA, либо каждый раз запускайте нашу утилиту и переключайте сканер. Придется смириться, а пока посмотрим как нам обойти запуск этой утилиты на машине пользователя.Утилита хранит настройки в ini-файлах, по одному файлу KM_TWAIN*.ini на каждый сетевой сканер и один результирующий файл с описанием сканеров и файлов их настроек.Скрин обоих файлов, для одного подключенного сканера:
Теперь установка видится следующей: — подключаем сканер через devcon— если утилита TWAIN не установлена, ставим её— добавляем адрес сканера в реестр— проходимся по реестру в поиске подключенных сканеров Kyocera и на основе данных в реестре генерируем ini-файлы
Расширим функцию подключения сканера из предыдущей заметки следующим кодом, который я постарался по-максимуму прокомментировать:
# знакомый нам участок кода с подключением через devcon «M2035dn» { Push-Location 'C:\Drivers\Scanners\ip\2035dnscan\' if ($(Get-Platform) -eq «Windows x64») { .\devconx64.exe /r install $dest\kmwiadrv.inf «KM_WC_ECOSYS_M2035dn_N_WIA» } else { .\devcon.exe /r install $dest\kmwiadrv.inf «KM_WC_ECOSYS_M2035dn_N_WIA» } Pop-Location
# проверяем стоит ли костыль kyocera, если нет ставим в тихом режиме $twain = Get-WMIObject -Class Win32_Product -Filter 'Name = «Kyocera TWAIN Driver»' if (!($twain)) { Push-Location 'C:\Drivers\Scanners\2035dnscan\TWAIN' .\setup.exe /S /v /qn Pop-Location }
# получаем содержимое ветки реестра в которой хранятся настройки сканеров и камер $scanclass = 'HKLM:\SYSTEM\CurrentControlSet\Control\Class\{6BDD1FC6–810F-11D0-BEC7–08002BE2092F}'
# так как мы только что поставили новый сканер, то его номер будет последним среди сканеров $item = (Get-ChildItem $scanclass | Where-Object Name -match »\d{4}$» | Select -Last 1).PSChildName
# добавляем адрес сканера New-ItemProperty »$scanclass\$item\DeviceData» -Name «ScannerAddress» -Value $ipaddress | Out-Null
# тут применил расширенный синтаксис Foreach-Object, состоящий из трех частей # первая и последняя выполняются по одному разу, при запуске цикла и его окончании соответственно; # код в секции process выполняется для каждого элемента цикла Get-ChildItem $scanclass | Foreach-Object -Begin { $count = 0 Add-Type -As System.Web # стандартный пароль, который задает утилита $pass = '43srWkUjR/8=' $scanitem = @{} $filelist = @() } -Process { $path = $_.Name -replace 'HKEY_LOCAL_MACHINE', 'HKLM:' $prop = Get-ItemProperty $path if ($prop.Vendor -eq 'Kyocera') { $count ++ $twfilename = «KM_TWAIN$count`.INI» $devicedata = Get-ItemProperty »$path\DeviceData» $cont = @{'Unit'='0';'ScannerAddress'=$devicedata.ScannerAddress; 'SSL'='0'} $auth = @{'Auth'='0';'UserName'=''; 'Account'='0'; 'ID'='';'Password'=$pass} $twcont = @{'Contents'=$cont; 'Authentication'=$auth} Out-IniFile -inputobject $twcont -FilePath »$env: temp\$twfilename» $filelist += ,»$env: temp\$twfilename»
$devicename = $devicedata.'Model Name' + » #$count» $modelname = $devicedata.'Model Name' $scanreg = @{'Name'=$devicename;'Model'=$modelname;'DefFile'=$twfilename;'LastScan'='';'ScanList'='';'Pos'=($count-1)} $scanitem.Add («Scanner$count», $scanreg) } } -End { $regfilename = 'RegList.ini' $settings = @{'Type'='4'; 'DefaultUse'=$count;'RegNum'=$count;} $reglist = @{'Setting'=$settings} $reglist += $scanitem Out-IniFile -inputobject $reglist -FilePath »$env: temp\$regfilename» $filelist += ,»$env: temp\$regfilename» }
# удаляем предыдущие ini-файлы и подкладываем сгенерированные выше с новым сканером Get-ChildItem $env: systemdrive\users -Directory -Recurse -Include 'appdata' -Force | ForEach-Object { $kyodir = $_.FullName + »\Roaming\Kyocera\KM_TWAIN» If (!(Test-Path $kyodir)) { New-Item -Type Directory -Path $kyodir } else { Remove-Item »$kyodir\*» -Recurse } $filelist | ForEach-Object { Copy-Item $_ $kyodir -Force | Out-Null } } } В скрипте я использовал функцию вывода хэш-таблицы в ini-файл, вот её код: function Out-IniFile ($inputobject, $filepath) { # .Example # $Category1 = @{'Key1'='Value1';'Key2'='Value2'} # $Category2 = @{'Key1'='Value1';'Key2'='Value2'} # $NewINIContent = @{'Category1'=$Category1;'Category2'=$Category2} # Out-IniFile -inputobject $NewINIContent -FilePath 'C:\MyNewFile.INI'
$outfile = New-Item -ItemType File -Path $filepath -Force foreach ($i in $inputobject.keys) { Add-Content -Path $outfile -Value »[$i]» Foreach ($j in ($inputobject[$i].keys | Sort-Object)) { Add-Content -Path $outfile -Value »$j=$($inputobject[$i][$j])» } Add-Content -Path $outfile -Value '' } }
Код этот успешно работал и проблем с ним не возникало, наверное, на протяжении полугода пока ветер опять не подул в другую сторону. В сторону Xerox.В аутлук упало письмо с ip-адресами двух новых мфу, WorkCentre 3615 и WorkCentre 6505DN. Дорога хода мыслей при знакомстве с новым мфу уже проторена, открываем драйвер и видим знакомое:
И настроение мое улучшилось©
Распаковываем драйвер, запускаем консоль, выполняем:.\devcon.exe /r install C:\Drivers\Scanners\xx3615\xrszdim.inf «NON_PNP&WorkCentre3615»
Сканер подключился и на экран выскочил новый, как это принято говорить, воркэраунд, только уже от разработчиков Xerox:
Очередная странная утилита от авторов драйвера для прописывания IP, причем запускается она из драйвера при установке. Значит, для того что бы спрятать ее от пользователя, будем прибивать ее в скрипте, в общем-то не беда.
Сейчас покажу на примере 3615, как расширить функцию подключения сканера. От 6506DN она практически не отличается, разве что другое имя файла драйвера и ID:
»3615» { Push-Location 'C:\Drivers\Scanners\xx3615\' if ($(Get-Platform) -eq «Windows x64») { .\devconx64.exe /r install C:\Drivers\Scanners\xx3615\xrszdim.inf «NON_PNP&WorkCentre3615» } else { .\devcon.exe /r install C:\Drivers\Scanners\xx3615\xrszdim.inf «NON_PNP&WorkCentre3615» } Pop-Location Get-Process «AIOScanSettings» | Stop-Process -Force
# не могу вразумительно ответить почему я тут применил reg add, # спишем на ностальгию по cmd, а замену на New-ItemProperty оставим домашкою читателю & reg.exe add «hklm\SOFTWARE\Xerox\WorkCentre 3615\TwainDriver» /v «EnableEnhancedBW» /t REG_DWORD /d 1 /f & reg.exe add «hklm\SOFTWARE\Xerox\WorkCentre 3615\TwainDriver» /v «ISO_B_Series» /t REG_DWORD /d 1 /f & reg.exe add «hklm\SOFTWARE\Xerox\WorkCentre 3615\TwainDriver» /v «IP Address» /t REG_SZ /d $ipAddress /f } Теперь мы умеем подключать целый зоопарк сетевых мфу и совершенно ничего не боимся, осталось добавить какой-нибудь магии… магии snmp!
SNMP (англ. Simple Network Management Protocol — простой протокол сетевого управления) — стандартный интернет-протокол для управления устройствами в IP-сетях на основе архитектур TCP/UDP.ru.wikipedia.org/wiki/SNMP
Для работы с snmp из powershell я в скрипте использовал открытую библиотеку sharpsnmp, подробнее о ее использовании можно почитать по адресу: vwiki.co.uk/SNMP_and_PowerShellПосле подключения библиотеки получение информации сводится к вызову функции Invoke-SNMPget с указанием Ip и uid, последний из которых легко гуглится.Пример из кода: Invoke-SNMPget $ip .1.3.6.1.2.1.25.3.2.1.3.1
Результат работы поиска выводим на экран, о том как это сделать в одну комманду чуть ниже:
Остается выделить нужный принтер и нажать OK, кстати множественное выделение так же возможно, в этом случае подключатся все выделенные принтеры.Эту удобную гуёвую магию обеспечивает командлет Out-GridView, отображающий любые переданные в него объекты. При вызове с параметром PassThru, после нажатия OK он передаст дальше по конвейеру выбранные объекты, нам остается только по очереди вызвать наши функции установки драйверов с параметрами пришедшими в объекте из конвейера.
$hosts | Out-GridView -Title «Выберите принтеры для установки» -PassThru | Foreach-Object { $printername = $_.Name $printersource = $_.Source
switch -regex ($printername) { «xerox.+3615» { $modelname = «Xerox Phaser 6600DN» $driverpath = 'C:\Drivers\Scanners\xx6505\xrxmozi.inf' } }
Write-Host «Добавляется порт IP принтера $printerName» Add-PrinterPort $printername $printersource
Write-Host «Добавляется драйвер принтера $printername» Add-PrinterDriver $printername $driverpath
Write-Host «Добавляется сканер принтера $printername» Add-Scanner $printersource $printername } В процессе изучения откликов принтеров, столкнулся с тем, что принтеры отдают порой имя отличающееся от имени прописанного в драйвере, для обхода этой особенности добавил в скрипт простой свитч с регулярками, которые никогда не промахиваются и как мы знаем полны по Тьюрингу ;-)
switch -regex ($printername) { «hp.+3050» { $modelName = «HP LaserJet 3050» } «hp.+3052» { $modelName = «HP LaserJet 3052» } «hp.+3055» { $modelName = «HP LaserJet 3055» } «xerox.+3615» { $modelName = «Xerox WorkCentre 3615» } «xerox.+650[0,5]DN» { $modelName = «Xerox Phaser 6600DN» } } Полный код функции поиска и подключения сетевого принтера $ErrorActionPreference = «silentlycontinue»
function Main {
# путь к драйверам $driversdistrib = 'C:\Drivers\'
# загружаем snmp либу $snmplibpath = Join-Path (Get-Location).path »\SharpSnmpLib.dll» if (Test-Path $snmplibpath) { [reflection.assembly]:: LoadFrom ((Resolve-Path $snmplibpath)) } else { Write-Host «Не удалось найти SharpSnmpLib» Exit }
# вычисляем подсеть, без хитрой математики, в лоб и только /24 $network = (Get-IPaddress).ToString () -replace »\.[0–9]{1,3}$»
# в диапазоне закрепленном за принтерами ищем устройства $hosts = 10…40 | ForEach-Object { $ip = »$network.$_» $snmpanswer= $null $snmpanswer = Invoke-SNMPget $ip .1.3.6.1.2.1.25.3.2.1.3.1 if ($snmpanswer) { # формируем объект с двумя свойствами который улетит в переменную $hosts [pscustomobject]@{ Name = $snmpanswer.Data; Source = $ip; } } }
# выводим объекты в гуй с параметром PassThru, который передаст выбранные дальше по конвейеру $hosts | Out-GridView -Title «Выберите принтеры для установки» -PassThru | Foreach-Object { $printername = $_.Name $printersource = $_.Source
switch -regex ($printername) { «hp.+3050» { $printername = «HP LaserJet 3050» $driverpath = Join-Path $driversdistrib 'Printers\3050\hppasc01.inf' } «hp.+3052» { $printername = «HP LaserJet 3052» $driverpath = Join-Path $driversdistrib 'Printers\3050\hppasc01.inf' } «hp.+3055» { $printername = «HP LaserJet 3055» $driverpath = Join-Path $driversdistrib 'Printers\3050\hppasc01.inf' } «hp.+3390» { $printername = «HP LaserJet 3390» $driverpath = Join-Path $driversdistrib 'Printers\3050\hppasc01.inf' } «hp.+153[0,6]» { $printername = «HP LaserJet M1530 MFP» $driverpath = Join-Path $driversdistrib 'Printers\1530\hpc1530c.inf' } «hp.+1522» { $printername = «HP LaserJet M1522 MFP» $driverpath = Join-Path $driversdistrib 'Printers\1522\hppcp608.inf' } «M2035dn» { $printername = «Kyocera ECOSYS M2035dn KX» $driverpath = Join-Path $driversdistrib 'Printers\2035dn\hppasc01.inf' } «xerox.+3615» { $printername = «Xerox WorkCentre 3615» $driverpath = Join-Path $driversdistrib 'Scanners\xx3615\x2GPROX.inf' } «xerox.+650[0,5]DN» { $printername = «Xerox Phaser 6600DN» $driverpath = Join-Path $driversdistrib 'Scanners\xx6505\xrxmozi.inf' } }
Write-Host «Добавляется порт IP принтера $printerName» Add-PrinterPort $printername $printersource
Write-Host «Добавляется драйвер принтера $printername» Add-PrinterDriver $printername $driverpath
Write-Host «Добавляется сканер принтера $printername» Add-Scanner $printersource $printername } } function Add-PrinterPort ($printersource) { &cscript C:\Windows\System32\Printing_Admin_Scripts\ru-RU\prnport.vbs ` -a -r $printersource -h $printersource -o RAW -n 9100 | Out-Null } function Add-PrinterDriver ($printerName, $driverpath) { $folder = Split-Path $driverpath cscript C:\Windows\System32\Printing_Admin_Scripts\ru-RU\prndrvr.vbs ` -a -m $printerName -e Get-Platform -h $folder -i $driverpath } function Add-Scanner ($ipaddress, $printername) { switch -regex ($printername) { »1530» { Push-Location (Join-Path $driversdistrib 'Scanners\1536scan\') if ($(Get-Platform) -eq «Windows x64») { .\hppniscan64.exe -f «hppasc16.inf» -m «vid_03f0&pid_012a&IP_SCAN» -a $ipAddress -n 1 } else { .\hppniscan01.exe -f «hppasc16.inf» -m «vid_03f0&pid_012a&IP_SCAN» -a $ipAddress -n 1 } Pop-Location } »(305\d)|(3390)» { Push-Location (Join-Path $driversdistrib 'Scanners\3055scan\') switch -regex ($printername) { »3050» { .\hppniscan01.exe -f «hppasc01.inf» -m «vid_03f0&pid_3217&IP_SCAN» -a $ipAddress -n 1 } »3052» { .\hppniscan01.exe -f «hppasc01.inf» -m «vid_03f0&pid_3317&IP_SCAN» -a $ipAddress -n 1 } »3055» { .\hppniscan01.exe -f «hppasc01.inf» -m «vid_03f0&pid_3417&IP_SCAN» -a $ipAddress -n 1 } »3390» { .\hppniscan01.exe -f «hppasc01.inf» -m «vid_03f0&pid_3517&IP_SCAN» -a $ipAddress -n 1 } } Pop-Location } »1522» { Push-Location (Join-Path $driversdistrib 'Scanners\1522scan\') if ($(Get-Platform) -eq «Windows x64») { .\hppniscan64.exe -f «hppasc08.inf» -m «vid_03f0&pid_4517&IP_SCAN» -a $ipAddress -n 1 } else { .\hppniscan01.exe -f «hppasc08.inf» -m «vid_03f0&pid_4517&IP_SCAN» -a $ipAddress -n 1 } Pop-Location } «M2035dn» { Push-Location (Join-Path $driversdistrib 'Scanners\2035dnscan\') if ($(Get-Platform) -eq «Windows x64») { .\devconx64.exe /r install $dest\kmwiadrv.inf «KM_WC_ECOSYS_M2035dn_N_WIA» } else { .\devcon.exe /r install $dest\kmwiadrv.inf «KM_WC_ECOSYS_M2035dn_N_WIA» } Pop-Location
$twain = Get-WMIObject -Class Win32_Product -Filter 'Name = «Kyocera TWAIN Driver»' if (!($twain)) { Push-Location (Join-Path $driversdistrib 'Scanners\2035dnscan\TWAIN') .\setup.exe /S /v /qn Pop-Location }
$scanclass = 'HKLM:\SYSTEM\CurrentControlSet\Control\Class\{6BDD1FC6–810F-11D0-BEC7–08002BE2092F}' $item = (Get-ChildItem $scanclass | Where-Object Name -match »\d{4}$» | Select -Last 1).PSChildName New-ItemProperty »$scanclass\$item\DeviceData» -Name «ScannerAddress» -Value $ipAddress | Out-Null Get-ChildItem $scanclass | ForEach-Object -Begin { $count = 0 Add-Type -As System.Web $pass = [System.Web.Security.Membership]:: GeneratePassword (12,2) $scanitem = @{} $filelist = @() } -Process { $path = $_.Name -replace 'HKEY_LOCAL_MACHINE', 'HKLM:' $prop = Get-ItemProperty $path if ($prop.Vendor -eq 'Kyocera') { $count ++ $twfilename = «KM_TWAIN$count`.INI» $devicedata = Get-ItemProperty »$path\DeviceData» $cont = @{'Unit'='0';'ScannerAddress'=$devicedata.ScannerAddress; 'SSL'='0'} $auth = @{'Auth'='0';'UserName'=''; 'Account'='0'; 'ID'='';'Password'=$pass} $twcont = @{'Contents'=$cont; 'Authentication'=$auth} Out-IniFile -inputobject $twcont -FilePath »$env: temp\$twfilename» $filelist += ,»$env: temp\$twfilename»
$devicename = $devicedata.'Model Name' + » #$count» $modelname = $devicedata.'Model Name' $scanreg = @{'Name'=$devicename;'Model'=$modelname;'DefFile'=$twfilename;'LastScan'='';'ScanList'='';'Pos'=($count-1)} $scanitem.Add («Scanner$count», $scanreg) } } -End { $regfilename = 'RegList.ini' $settings = @{'Type'='4'; 'DefaultUse'=$count;'RegNum'=$count;} $reglist = @{'Setting'=$settings} $reglist += $scanitem Out-IniFile -inputobject $reglist -FilePath »$env: temp\$regfilename» $filelist += ,»$env: temp\$regfilename» } Get-ChildItem $env: systemdrive\users -Directory -Recurse -Include 'appdata' -Force | ForEach-Object { $kyodir = $_.FullName + »\Roaming\Kyocera\KM_TWAIN» If (!(Test-Path $kyodir)) { New-Item -Type Directory -Path $kyodir } else { Remove-Item »$kyodir\*» -Recurse } $filelist | ForEach-Object { Copy-Item $_ $kyodir -Force | Out-Null } } } »6505» { Push-Location (Join-Path $driversdistrib 'Scanners\xx6505\') if ($(Get-Platform) -eq «Windows x64») { .\devconx64.exe /r install $dest\xrsmoim.inf «NON_PNP&WorkCentre6505» } else { .\devcon.exe /r install $dest\xrsmoim.inf «NON_PNP&WorkCentre6505» } Pop-Location
Get-Process «AIOScanSettings» | Stop-Process -Force
®.exe add «hklm\SOFTWARE\Xerox\WorkCentre 6505\TwainDriver» /v «EnableEnhancedBW» /t REG_DWORD /d 1 /f ®.exe add «hklm\SOFTWARE\Xerox\WorkCentre 6505\TwainDriver» /v «IP Address» /t REG_SZ /d $ipAddress /f } »3615» { Push-Location (Join-Path $driversdistrib 'Scanners\xx3615\') if ($(Get-Platform) -eq «Windows x64») { .\devconx64.exe /r install $dest\xrszdim.inf «NON_PNP&WorkCentre3615» } else { .\devcon.exe /r install $dest\xrszdim.inf «NON_PNP&WorkCentre3615» } Pop-Location
Get-Process «AIOScanSettings» | Stop-Process -Force
®.exe add «hklm\SOFTWARE\Xerox\WorkCentre 3615\TwainDriver» /v «EnableEnhancedBW» /t REG_DWORD /d 1 /f ®.exe add «hklm\SOFTWARE\Xerox\WorkCentre 3615\TwainDriver» /v «ISO_B_Series» /t REG_DWORD /d 1 /f ®.exe add «hklm\SOFTWARE\Xerox\WorkCentre 3615\TwainDriver» /v «IP Address» /t REG_SZ /d $ipAddress /f } } } function Get-IPaddress { $ipWmiObject = Get-WmiObject Win32_NetworkAdapterConfiguration -filter «IPEnabled = 'True'» $ipWmiObject.IPAddress -match »^192\.([0–9]{1,3}\.){2}[0–9]{1,3}$» } function Get-Platform { if ([System.Environment]:: Is64BitOperatingSystem) { «Windows x64» } else { «Windows NT x86» } } function Out-IniFile ($inputobject, $filepath) { # .Example # $Category1 = @{'Key1'='Value1';'Key2'='Value2'} # $Category2 = @{'Key1'='Value1';'Key2'='Value2'} # $NewINIContent = @{'Category1'=$Category1;'Category2'=$Category2} # Out-IniFile -inputobject $NewINIContent -FilePath 'C:\MyNewFile.INI'
$outfile = New-Item -ItemType File -Path $filepath -Force foreach ($i in $inputobject.keys) { Add-Content -Path $outfile -Value »[$i]» Foreach ($j in ($inputobject[$i].keys | Sort-Object)) { Add-Content -Path $outfile -Value »$j=$($inputobject[$i][$j])» } Add-Content -Path $outfile -Value '' } } function Invoke-SNMPget { param ( [string]$sIP, $sOIDs, [string]$Community = «public», [int]$UDPport = 161, [int]$TimeOut=30 ) $vList = New-Object 'System.Collections.Generic.List[Lextm.SharpSnmpLib.Variable]' foreach ($sOID in $sOIDs) { $oid = New-Object Lextm.SharpSnmpLib.ObjectIdentifier ($sOID) $vList.Add ($oid) } $ip = [System.Net.IPAddress]:: Parse ($sIP) $svr = New-Object System.Net.IpEndPoint ($ip, 161) $ver = [Lextm.SharpSnmpLib.VersionCode]:: V1
try { $msg = [Lextm.SharpSnmpLib.Messaging.Messenger]:: Get ($ver, $svr, $Community, $vList, $TimeOut) } catch { return $null }
$res = @() foreach ($var in $msg) { $line = » | Select OID, Data $line.OID = $var.Id.ToString () $line.Data = $var.Data.ToString () $res += $line } $res }
. Main На этом на сегодня всё, надеюсь мои заметки помогут вам забыть о проблемах с сетевыми принтерами и освободят время для изучения PowerShell.Спасибо за внимание тем, кто дочитал до этого момента ;-)