Получив несколько раз такую ситуацию, и обнаружив что принтсервер может простоять с загрузкой процессора 30-50% неделю(!) до тех пока это не увидит админ. Чтобы решить эту проблему и разгрузить персонал был набросан небольшой скрипт, он крайне прост и у него много аналогов, выложить сюда я его решил поскольку он снова и снова, доказывает свою полезность.
![](https://habrastorage.org/webt/gt/6e/lv/gt6elvfjrcvfdb2d8xeny1lciqq.jpeg)
У нас сломалось три принтера подряд, мы сняли их на ремонт, а пользователи других отделов продолжали слать задания в очередь, в результате автоочистка и перегрузка службы печати избавила нас от работы в тот момент когда мы были заняты ремонтом физической техники.
для того чтобы снять нагрузку с процессора нужно очистить задания печати в каталоге C:\Windows\System32\spool\PRINTERS\ просто удаляются все файлы в каталоге при остановленной службе:
Get-Service *spool* | Stop-Service -Force -Verbose
Start-Sleep -Seconds 5
$path = "C:\Windows\System32\spool\PRINTERS\"
Get-ChildItem $path -File | Remove-Item -Force -Verbose
Get-Service Spooler | Start-Service -Verbose
Для административного оповещения предварительно сохраняем — кто, какой документ, какого размера и куда печатал. Заодно пример как создавать объекты.
# получаем список заданий со всех принтеров в системе на этот момент
$temp = Get-Printer | Get-PrintJob
# создаем пустой список объектов
$Jobs = @()
# перебирем все объекты заданий печати чтобы сформировать полноценный объект
foreach ( $p in $temp ) {
# ниже применяется свойство [ordered] позволяющее зафиксировать порядок в свойствах
# объекта обычно пошик создает свойства в произвольном порядке
# этого указателя нет в старом пошике, поэтому если получаете ошибку просто
# удалите ее она нужна больше для людей - когда ведешь вывод в консоль
# в данном случае она нужна чтобы зафиксировать порядок колонок в html письма
$props = [ordered]@{ ID = $p.Id
PrinterName = $p.PrinterName
UserName = $p.UserName
DocumentName = $p.DocumentName
DataType = $p.Datatype
SubmittedTime = $p.SubmittedTime
Size = $p.Size
JobTime = $p.JobTime
PagesPrinted = $p.PagesPrinted
TotalPages = $p.TotalPages
Status = $p.Status
}
# создаем еденичный объект с значениями из переменной $props
$obj = New-Object -TypeName PSObject -Property $props
# добавляем в список объекта
$Jobs += $obj
}
# на выходе имеем полноценные объекты
$Jobs
При работе желательно создавать объекты.
- Во-первых повторно использовать лучше;
- Проще дописывать новые свойства — просто вставляешь строчку в $props и получаешь свойство у всех объектов;
- Лучше сортировать\записывать\выводить\объединять\фильтровать;
- Лично мне проще объединять свойства нескольких объектов в один и выводить дальше как один, работать получается проще.
ПримерFunction Get-NBTName { # получаем консольный вывод команды NBTSTAT, сразу же выкидываем ненужное $data=nbtstat /n | Select-String "<" | where {$_ -notmatch "__MSBROWSE__"} # обрезаем каждую строку от мусора $lines=$data | foreach { $_.Line.Trim() } # расщепляем кажду строку на массив элементов по пробелу # то что получилось помещаем в хэш таблицу формируя объект $lines | foreach { $temp=$_ -split "\s+" [PSCustomObject]@{ Name=$temp[0] NbtCode=$temp[1] Type=$temp[2] Status=$temp[3] } } }
А теперь вызываем Get-NBTName | sort type | Format-Table –Autosize
На выходе будет набор объектов что-то типа:
Name NbtCode Type Status ---- ------- ---- ------ MYCOMPANY <1E> GROUP Registered MYCOMPANY <00> GROUP Registered MYCOMPANY <1D> UNIQUE Registered CLIENT2 <00> UNIQUE Registered CLIENT2 <20> UNIQUE Registered
Мы хотим иметь диагностическое инфо на момент перезагрузки спулера для этих целей добавим список процессов с загрузкой по процессору и памяти:
# дергаем запущенные процессы
$temp = Get-Process | sort -Property cpu -Descending
# объявляем пустой список объектов
$proc = @()
foreach ( $p in $temp ) {
# заполняем свойства будущего объекта
$props = [ordered]@{ Name=$p.ProcessName
CPU_total_in_seconds=$p.CPU
PhysicallMemory_in_Mb=$p.WS/1mb
ProcessID=$p.Id
}
# создаем объект из свойств
$obj = New-Object -TypeName PSObject -Property $props
# добавляем его в список объектов
$proc += $obj
}
$proc
Для принятия решения что пора перегружать спулер, берутся отсчеты от процессора и если они превысили порог в течение 5 секунд то считается что пора перегружаться. Данная схема не оптимальна, например при установке обновлений может держаться такая загрузка, но для оперативной работы, и ежедневной деятельности показала себя достаточной, поэтому и оставляю ее как есть:
# счетчик загрузки, если загрузка больше чем 95 то инкремент
$cfi = 0
for( $i=1; $i -le 5; $i++ ) {
Start-Sleep -Seconds 1
# подгружаем загрузку процессора
$load = Get-WmiObject win32_Processor | select -Property LoadPercentage
Write-Host "CPU load $load" -ForegroundColor Green
if ($($load.LoadPercentage) -gt 95) {
$cfi = $cfi + 1
Write-Host "indicator is $cfi" -ForegroundColor Green
}
}
Скрипт нужно добавить в шедулер на запуск каждые 5-10 минут.
Для отправки письма мы используем с небольшими изменениями метод описанный здесь.
Отправка почты вынесена в отдельную функцию, т.к. она используется в других местах, вам нужно поменять адрес почтового сервера 'SMTPServer'='Exchange.domain.ru' на ваш сервер вот в этом месте:
$params = @{'To'=$MailAddress
'From'='bot@domain.local'
'Subject'="$Subject $Date"
'Body'=$MailBody
'BodyAsHTML'=$True
'SMTPServer'='Exchange.domain.ru'
}
Send-MailMessage @params -Encoding $encoding
И заменить адрес admin@domain.ru на адрес на который должно уходить административное оповещение вот здесь:
Send-ToAdmin -MailAddress 'admin@domain.ru' -Style $Style -Subject 'Сервер печати - процессор загружен на 100%' -Body $Body -Header1 $header
В первоначальной редакции скрипт умел извлекать DLL загруженные в память для каждого процесса для того чтобы выловить «глючный» драйвер. в данной редакции убран т.к. генерирует слишком большое количество инфо, был реализован через ListDll, но генерировал очень большой список.
Полный код скрипта:
<#
смотрит за спулером, чистит очередь и перегружает службу
дополнение отправка уведомления на почту
#>
$StyleYellowSimple = @'
<style>
body { background-color:#ffffff;
font-family:Tahoma;
font-size:12pt; }
td, th { border:1px solid black;
border-collapse:collapse; }
th { color:white;
background-color:black; }
table, tr, td, th { padding: 2px; margin: 0px }
table { font-family: "Lucida Sans Unicode", "Lucida Grande", Sans-Serif;
font-size: 14px;
border-radius: 10px;
border-spacing: 0;
text-align: center; }
th { background: #BCEBDD;
color: white;
text-shadow: 0 1px 1px #2D2020;
padding: 10px 20px; }
th, td { border-style: solid;
border-width: 0 1px 1px 0;
border-color: white; }
th:first-child, td:first-child { text-align: left; }
th:first-child { border-top-left-radius: 10px; }
th:last-child { border-top-right-radius: 10px;
border-right: none; }
td { padding: 10px 20px;
background: #F8E391; }
tr:last-child td:first-child { border-radius: 0 0 0 10px; }
tr:last-child td:last-child { border-radius: 0 0 10px 0; }
tr td:last-child { border-right: none; }
</style>
'@
<#
отправляет письмо на указанный адрес с оповещением.
для использования в других частях скриптов
#>
function Send-ToAdmin
{
Param ( [string]$MailAddress = 'admin@domain.ru',
[string]$Subject = 'Test message',
[string]$Header1,
[string]$Body,
[string]$Style
)
BEGIN {}
PROCESS {
Write-Verbose 'definiting CSS'
<# Switch ($Style)
{
'YellowSimple' { $head = $StyleYellowSimple; break }
'BlueSimple' { $head = $StyleBlueSimple; break }
'DataTable' {$head = $StyleResposTable; break }
default { $head = $StyleYellowSimple; break }
}#>
$head = $StyleYellowSimple
$encoding = [System.Text.Encoding]::UTF8
$Date = Get-Date
$MailBody = ConvertTo-HTML -head $head -PostContent $Body -PreContent "<h1>$Subject. Date:$Date</h1><br><h3>$Header1</h3>" | Out-String
Write-Verbose "Sending e-mail. Address: $MailAddress"
$params = @{'To'=$MailAddress
'From'='bot@domain.local'
'Subject'="$Subject $Date"
'Body'=$MailBody
'BodyAsHTML'=$True
'SMTPServer'='Exchange.domain.ru'
}
Send-MailMessage @params -Encoding $encoding
}
END{}
}
$cfi = 0
for( $i=1; $i -le 5; $i++ ) {
Start-Sleep -Seconds 1
$load = Get-WmiObject win32_Processor | select -Property LoadPercentage
Write-Host "CPU load $load" -ForegroundColor Green
if ($($load.LoadPercentage) -gt 95) {
$cfi = $cfi + 1
Write-Host "indicator is $cfi" -ForegroundColor Green
}
}
if ($cfi -gt 2) {
# дергаем процессы для инфо
$temp = Get-Process | sort -Property cpu -Descending
$proc = @()
foreach ( $p in $temp ) {
$props = [ordered]@{ Name=$p.ProcessName
CPU_total_in_seconds=$p.CPU
PhysicallMemory_in_Mb=$p.WS/1mb
ProcessID=$p.Id
}
$obj = New-Object -TypeName PSObject -Property $props
$proc += $obj
}
$temp = Get-Printer | Get-PrintJob
$Jobs = @()
foreach ( $p in $temp ) {
$props = [ordered]@{ ID = $p.Id
PrinterName=$p.PrinterName
UserName=$p.UserName
DocumentName=$p.DocumentName
DataType=$p.Datatype
SubmittedTime=$p.SubmittedTime
Size=$p.Size
JobTime=$p.JobTime
PagesPrinted=$p.PagesPrinted
TotalPages=$p.TotalPages
Status=$p.Status
}
$obj = New-Object -TypeName PSObject -Property $props
$Jobs += $obj
}
# перегружаем спулер тут
Write-Host "Перегружаем спулер" -ForegroundColor Green
Get-Service *spool* | Stop-Service -Force -Verbose
Start-Sleep -Seconds 5
$path = "C:\Windows\System32\spool\PRINTERS\"
Get-ChildItem $path -File | Remove-Item -Force -Verbose
Get-Service Spooler | Start-Service -Verbose
$frag1 = $proc | ConvertTo-Html -As table -Fragment -PreContent '<h2>Процессы в памяти перед перезагрузкой спулера</h2>' | Out-String
$frag2 = $Jobs | ConvertTo-Html -As table -Fragment -PreContent '<h2>Задания печати из всех очередей всех принтеров</h2><br>если есть список то скорее всего задание зависло (принтер отключен)' | Out-String
$Body = '<br><br>служба сервера была перезагружена т.к. процессор был слишком сильно нагружен<br><br>'
$Body = $Body + $frag2 + '<br><br>'
$Body = $Body + $frag1 + '<br><br>---------------------------------------------------------------------------<br>дополнительная отладочная информация<br><H2>список загруженных библиотек в памяти на момент зависания по каждому процессу</H2>'
$Date = Get-Date
$header = "$Date сервер печати"
$Style = 'YellowSimple'
Send-ToAdmin -MailAddress 'admin@domain.ru' -Style $Style -Subject 'Сервер печати - процессор загружен на 100%' -Body $Body -Header1 $header
}
Скрипт нужно добавить в шедулер на запуск каждые 5-10 минут.
> Ссылка на альтернативный скрипт
Комментарии (16)
Louie
20.02.2018 08:30Если есть проблема с производительностью — можно штатными средствами создать счетчики с пороговым значением и алертами.
docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/cc749249(v=ws.11)
www.microsoftpressstore.com/articles/article.aspx?p=2217266&seqNum=2
support.microsoft.com/en-us/help/2424491/how-to-create-performance-counter-alert-and-send-email-when-the-alertpak-nikolai Автор
20.02.2018 08:59спасибо за инфо
может ктонибудь напишет статью по perfomance counter, чтото типа get-counter и так далее.
Как вы используете счетчики? Я их применял всего пару раз т.к. обычно хватает мониторинга, а при работе скрипта хватает WMI.
в данном случае проблема в библиотеках драйвера, если разберусь в чем дело обновлю статьюLouie
20.02.2018 11:50Да никак использую. Просто подумал, что слишком уж неэлегантно скриптом крошить спулер по расписанию (извините) — вот и глянул в энторнетах чего там в новых виндовс напихано. У меня ток 2003 в KVM осталась. И то исключительно для всяких ваших одинэсов. А там хоть и есть каунтеры, но нет алертов.
Выяснилось, что помимо счетчиков производительности и журнала (который, при желании можно парсить скриптом) — есть т.н. performance counter alerts, которые по пороговым срабатываниям счетчиков могут гибко вам решить проблему. И самое первое действие я бы поставил — оповестить пользователей, чтоб не печатали больше на принтер N и как-то отключить возможность постановки в очередь заданий на сбойный принтер.pak-nikolai Автор
20.02.2018 13:04а он и не крошит по расписанию, он проверяет по расписанию и если очередь забилась выписывает что в ней застряло, затем останавливает спулер и выбрасывает задания, както так работает
а наступление момента Х определяется по загрузке процессора, он не дает чтобы загрузка была 100% больше 5ти секунд
14th
20.02.2018 10:40У kyocera очень кривые драйвера. Скриптом это не победить. У меня много орг-ций под наблюдением и только у одной такие принтера. Пока её пользователи не появляются на общих ресурсах — там всё работает хорошо. Частично вопрос снимает EasyPrint.
pak-nikolai Автор
20.02.2018 11:02я тоже так понял, когда докопал до самого драйвера. снижает нагрузку также отключение gui kyocera — окошечко такое вылазиет сбоку на принт сервере. нагрузка начинает расти когда драйвер пытается отправить на принтер которого физически нет, с двух заданий в очереди всегда есть глюк. помогает если почистить очеред и убить процесс в памяти вручную, тоже работать будет.
как я понял нужно на альтернативные дровишки какието переходить или протокол использовать какойто другой (родной?)
в принципе я решил это таким образом, этакий workaround, теоритически надо загрузку процессоров смотреть чтобы правильно убивать, но в принципе за пол года такая схема работает нормально.MagicEx
20.02.2018 11:39Пробовали встроенные в винду v4 драйвера? У нас была проблема с HP UPD всех версий.
Когда число принтеров достигло 200 — с ними работать стало невозможно, свойства принтера открывались минут по 5.
Переход на Class Driver проблему решил полностью.
На Kyocera вижу тоже есть встроенные драйвера, в том числе и универсальные Monochrome\Color.pak-nikolai Автор
20.02.2018 11:45спасибо, попробую отпишусь
4mz
20.02.2018 21:00Также испытываем много проблем с Kyocera, очередь часто застревает, а еще периодически возникают проблемы с полями непонятно от чего, в предварительном просмотре при печати все отлично, а в напечатанном документе поля шире\уже\хренпоймикак (((
Напишите об опыте замены KX драйвера на что-то иное, если будет возможность!pak-nikolai Автор
20.02.2018 23:19по полям не понятно, из какой программы печатаете?
какая модель принтера?
mayorovp
20.02.2018 13:27А приостановка печати в очередях к отключенным принтерам не поможет? Или пользователи обратно включают?
pak-nikolai Автор
20.02.2018 14:31если принтер отключают на ночь или по запарке не поможет, а если действие точно спрогнозировано поможет
MagicEx
Что-то у вас нездоровое со спулером, ну или дрова кривые на принтера. Зависшие в очереди задания это конечно проблема, но решается она гораздо проще:
Get-Printer | get-printjob | where{$_.SubmittedTime -lt ((Get-Date).adddays(-1))} | Remove-PrintJob
Запуск каждый час — вычищать все, что поставлено в очередь больше суток назад. Врядли кому-то нужны уже эти документы.
Ну и даже при 700+ суммарно в очередях — проблем со 100% потреблением ЦПУ не наблюдали.
pak-nikolai Автор
у всех по разному, здесь вот например глючит, нагрузка на процессор растет почти сразуже после после попадания в очередь на принтер которого нет.
такое наблюдается на kyocera точно
чистить приходится сразу
дрова официальные
и кстати весь этот огород написан чтобы получать статистику от какой именно очереди происходит нагрузка, и в случае проблемы найти глючный драйвер
LoadRunner
Можно же настроить так, чтобы в журналы Windows падала вся статистика по печати, да и в принципе их надо в первую очередь шерстить на предмет возможных проблем.
pak-nikolai Автор
кстати вот типовая нагрузка при застревании на принтер Kyocera 3060dn система win 2012r2 установлена только роль принт сервера, драйвера официальные, изоляция драйверов включена
![image](https://habrastorage.org/webt/mv/-b/wb/mv-bwbebiyp7jgxvlzmscdgcovu.jpeg)
скачки на графике, это отправка ночной сменой задания на сломанный и снятый на ремонт принтер, второй скачек это они забылись и отправили снова, как можно видеть с 12 ночи к 12ти утра мы уже имеем 50% загрузку
поэтому этот скрипт и писался, за счет его отчета на почту, можно сразу разобраться от какого принтера произошел сбой и уже работать от него. Это вполне конкретная вещь, она позволяет видеть что именно вызвало такой жвах, и перегружать самостоятельное если загрузка достигла 100%. При нагрузке под 100 тормозить начнут уже все вообще принтера, и звонить начнут все отделы