
Automatic connection of network MFPs with the ability to scan [Part 2]
Kyocera M2035dn, Xerox WorkCentre 3615 and 6505DN
As promised in the first part , for which I successfully received an invite in the sandbox, in this note I will show how to connect Kyocera M2035dn, Xerox WorkCentre 3615 and 6505DN network MFPs, and at the end of the article I will add a small bonus with which any network-related script gets better .

No matter how beautiful homogeneous infrastructures are, even in terms of printers and MFPs, reality often sets its own conditions. While users themselves fully connected and successfully scanned from the once problematic HP MFPs, the Japanese guest came to the company - Kyocera M2035dn.
Kyocera M2035dn
As always, I came not to my cozy admin's den, but immediately to the object and like the HP MFP, I honestly did not even see it in my eyes.
First of all, download the driver and look at the contents ... bah, all people familiar:

There is a note that the network connection is the ID!
Let's try to connect the scanner via devcon, similar to how we connected the MFP from HP in the first part: The
.\devcon.exe /r install C:\Drivers\Scanners\2035dnscan\kmwiadrv.inf "KM_WC_ECOSYS_M2035dn_N_WIA"
scanner is connected, register the scanner address with the ScannerAddress parameter in the registry and start scanning. The scanning application showed a scanned sheet, everything works fine. It would have seemed like a victory, but the launch of the second scanning program we have used has diminished joy - the scanner did not appear in it.

It turns out that the Kyocera developers for some reason implemented a scan only through the WIA in the driver, for TWAIN it is necessary to set separate

At the same time, via WIA we can connect several Kyocera scanners, while there will always be only one TWAIN interface. Either use the WIA, or each time run our utility and switch the scanner. We’ll have to put up with it, but for now let's see how we can get around the launch of this utility on the user's machine.
The utility stores settings in ini-files, one KM_TWAIN * .ini file for each network scanner and one resulting file with a description of the scanners and their settings files.
Screen of both files for one connected scanner:

Now the installation is as follows:
- connect the scanner via devcon
- if TWAIN is not installed, set it
- add the scanner address to the registry
- go through the registry in search of connected Kyocera scanners and generate based on the data in the registry ini-files
We will expand the function of connecting the scanner from the previous note with the following code, which I tried to comment to the maximum:
# знакомый нам участок кода с подключением через 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
}
}
}
In the script, I used the hash table output function in the ini-file, here is its code:
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 and 6505DN
This code worked successfully and there were no problems with it, probably for six months until the wind again blew the other way. Towards Xerox.
A letter with the IP addresses of two new MFPs, WorkCentre 3615 and WorkCentre 6505DN, fell into Outlook. When getting to know the new MFP, the road of thought has already been beaten up, we open the driver and we see the familiar:

And my mood has improved ©
Unpack the driver, launch the console, execute: The
.\devcon.exe /r install C:\Drivers\Scanners\xx3615\xrszdim.inf "NON_PNP&WorkCentre3615"
scanner is connected and a new work-around has popped up on the screen, only from the Xerox developers :

Another strange utility from the authors of the driver for prescribing IP, and it starts from the driver during installation. So, in order to hide it from the user, we will nail it in a script, in general, it does not matter.
Now I will show with an example 3615 how to expand the scanner connection function. It practically does not differ from 6506DN, except for a different driver file name and 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
}
Now we are able to connect a whole zoo of network MFPs and are absolutely not afraid of anything, it remains to add some kind of magic ... magic snmp!
We are looking for multifunction devices on the network by snmp
SNMP (Simple Network Management Protocol) is a standard Internet protocol for managing devices in IP networks based on TCP / UDP architectures.
en.wikipedia.org/wiki/SNMP
To work with snmp from powershell, I used the sharpsnmp open library in the script, for more details on its use, see: vwiki.co.uk/SNMP_and_PowerShell
After connecting the library, obtaining information is reduced to calling the Invoke-SNMPget function with Ip and uid, the last of which googles easily.
Example from the code: We
Invoke-SNMPget $ip .1.3.6.1.2.1.25.3.2.1.3.1
display the result of the search on how to do this in one command a little lower:

It remains to select the desired printer and click OK, by the way, multiple selection is also possible, in this case all selected printers will be connected.
Out-GridView cmdlet, which displays any objects transferred to it, provides this convenient gui magic. When called with the PassThru parameter, after clicking OK, it will pass the selected objects further down the pipeline, we only need to call our driver installation functions with the parameters that came from the pipeline in turn.
In a simplified form, the script will take the form:
$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
}
In the process of studying printer responses, I was faced with the fact that printers sometimes give a name different from the name specified in the driver, to bypass this feature, I added a simple switch to the script with regulars that never miss and, as we know, are Turing-complete ;-)
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"
}
}
Complete code for a ready-made script for searching and connecting a network printer
$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
That's all for today, I hope my notes will help you forget about problems with network printers and free up time for learning PowerShell.
Thank you for your attention to those who have read up to this point ;-)