Как бы небыли прекрасны гомогенные инфраструктуры, пусть даже в части принтеров и мфу, реальность зачастую ставит свои условия. В то время как пользователи сами в полный рост подключали и успешно сканировали с некогда проблемных МФУ 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.
http://ift.tt/1EjvFhb
Для работы с snmp из powershell я в скрипте использовал открытую библиотеку sharpsnmp, подробнее о ее использовании можно почитать по адресу: http://ift.tt/1435H6P
После подключения библиотеки получение информации сводится к вызову функции 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.
Спасибо за внимание тем, кто дочитал до этого момента ;-)
This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.
Комментариев нет:
Отправить комментарий