Kyocera M2035dn, Xerox WorkCentre 3615 и 6505DN
Как и обещал в первой части, за которую я успешно получил инвайт в песочнице, в этой заметке я покажу как подключить сетевые МФУ Kyocera M2035dn, Xerox WorkCentre 3615 и 6505DN, а в конце статьи добавлю небольшой бонус с которым любой скрипт связанный с сетью становится лучше.

Как бы небыли прекрасны гомогенные инфраструктуры, пусть даже в части принтеров и мфу, реальность зачастую ставит свои условия. В то время как пользователи сами в полный рост подключали и успешно сканировали с некогда проблемных МФУ HP, в компанию приехал японский гость — Kyocera M2035dn.
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 надо ставить отдельный

При этом, по 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 WorkCentre 3615 и 6505DN
Код этот успешно работал и проблем с ним не возникало, наверное, на протяжении полугода пока ветер опять не подул в другую сторону. В сторону 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 оставим домашкою читателю &rеg.exe add "hklm\SOFTWARE\Xerox\WorkCentre 3615\TwainDriver" /v "EnableEnhancedBW" /t REG_DWORD /d 1 /f &rеg.exe add "hklm\SOFTWARE\Xerox\WorkCentre 3615\TwainDriver" /v "ISO_B_Series" /t REG_DWORD /d 1 /f &rеg.exe add "hklm\SOFTWARE\Xerox\WorkCentre 3615\TwainDriver" /v "IP Address" /t REG_SZ /d $ipAddress /f }
Теперь мы умеем подключать целый зоопарк сетевых мфу и совершенно ничего не боимся, осталось добавить какой-нибудь магии… магии snmp!
Ищем МФУ в сети по 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 WorkCentre 6505DN PCL 6" $driverpath = 'C:\Drivers\Scanners\xx6505\xrxmozi.inf' } } Write-Host "Добавляется порт IP принтера $printerName" Add-PrinterPort $modelname $printersource Write-Host "Добавляется драйвер принтера $printername" Add-PrinterDriver $modelname $driverpath Write-Host "Добавляется сканер принтера $printername" Add-Scanner $printersource $modelname }
В процессе изучения откликов принтеров, столкнулся с тем, что принтеры отдают порой имя отличающееся от имени прописанного в драйвере, для обхода этой особенности добавил в скрипт простой свитч с регулярками, которые никогда не промахиваются и как мы знаем полны по Тьюрингу ;-)
switch -regex ($printername) { "hp.+305\d" { $modelName = "HP LaserJet 3050 Series PCL 6" } "hp.+3390" { $modelName = "HP LaserJet 3390 Series PCL 6" } "xerox.+3615" { $modelName = "Xerox WorkCentre 3615 PCL6" } "xerox.+650[0,5]DN" { $modelName = "Xerox WorkCentre 6505DN PCL 6" } }
Полный код готового скрипта поиска и подключения сетевого принтера
$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.+305\d" { $printername = "HP LaserJet 3050 Series PCL 6" $driverpath = Join-Path $driversdistrib 'Printers\3050\hppcp601.inf' } "hp.+3390" { $printername = "HP LaserJet 3390 Series PCL 6" $driverpath = Join-Path $driversdistrib 'Printers\3050\hppcp601.inf' } "hp.+153[0,6]" { $printername = "HP LaserJet M1530 MFP Series PCL 6" $driverpath = Join-Path $driversdistrib 'Printers\1530\hpc1530c.inf' } "hp.+1522" { $printername = "HP LaserJet M1522 MFP Series PCL 6" $driverpath = Join-Path $driversdistrib 'Printers\1522\hppcp608.inf' } "M2035dn" { $printername = "Kyocera ECOSYS M2035dn KX" $driverpath = Join-Path $driversdistrib 'Printers\2035dn\OEMSETUP.INF' } "xerox.+3615" { $printername = "Xerox WorkCentre 3615 PCL6" $driverpath = Join-Path $driversdistrib 'Scanners\xx3615\x2GPROX.inf' } "xerox.+650[0,5]DN" { $printername = "Xerox WorkCentre 6505DN PCL 6" $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 &rеg.exe add "hklm\SOFTWARE\Xerox\WorkCentre 6505\TwainDriver" /v "EnableEnhancedBW" /t REG_DWORD /d 1 /f &rеg.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 # тут в слове rеg, средняя буква русская, дабы не превращался в ® &rеg.exe add "hklm\SOFTWARE\Xerox\WorkCentre 3615\TwainDriver" /v "EnableEnhancedBW" /t REG_DWORD /d 1 /f &rеg.exe add "hklm\SOFTWARE\Xerox\WorkCentre 3615\TwainDriver" /v "ISO_B_Series" /t REG_DWORD /d 1 /f &rеg.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.
Спасибо за внимание тем, кто дочитал до этого момента ;-)
