Короткий но полезный скрипт для админов. Имеется принтсервер с 34 принтерами, и у него происходят застревания в очереди печати, обычно это происходит когда принтер отдела отправлен в ремонт, а другой отдел хочет им отправить документ на не существующий принтер. Загрузка процессора поднимается и может достигнуть 100%.

Получив несколько раз такую ситуацию, и обнаружив что принтсервер может простоять с загрузкой процессора 30-50% неделю(!) до тех пока это не увидит админ. Чтобы решить эту проблему и разгрузить персонал был набросан небольшой скрипт, он крайне прост и у него много аналогов, выложить сюда я его решил поскольку он снова и снова, доказывает свою полезность.



У нас сломалось три принтера подряд, мы сняли их на ремонт, а пользователи других отделов продолжали слать задания в очередь, в результате автоочистка и перегрузка службы печати избавила нас от работы в тот момент когда мы были заняты ремонтом физической техники.

для того чтобы снять нагрузку с процессора нужно очистить задания печати в каталоге 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)


  1. MagicEx
    20.02.2018 02:11

    Что-то у вас нездоровое со спулером, ну или дрова кривые на принтера. Зависшие в очереди задания это конечно проблема, но решается она гораздо проще:
    Get-Printer | get-printjob | where{$_.SubmittedTime -lt ((Get-Date).adddays(-1))} | Remove-PrintJob

    Запуск каждый час — вычищать все, что поставлено в очередь больше суток назад. Врядли кому-то нужны уже эти документы.
    Ну и даже при 700+ суммарно в очередях — проблем со 100% потреблением ЦПУ не наблюдали.


    1. pak-nikolai Автор
      20.02.2018 06:11

      у всех по разному, здесь вот например глючит, нагрузка на процессор растет почти сразуже после после попадания в очередь на принтер которого нет.
      такое наблюдается на kyocera точно
      чистить приходится сразу
      дрова официальные

      и кстати весь этот огород написан чтобы получать статистику от какой именно очереди происходит нагрузка, и в случае проблемы найти глючный драйвер


      1. LoadRunner
        20.02.2018 10:22

        Можно же настроить так, чтобы в журналы Windows падала вся статистика по печати, да и в принципе их надо в первую очередь шерстить на предмет возможных проблем.


    1. pak-nikolai Автор
      20.02.2018 06:50

      кстати вот типовая нагрузка при застревании на принтер Kyocera 3060dn система win 2012r2 установлена только роль принт сервера, драйвера официальные, изоляция драйверов включена
      image
      скачки на графике, это отправка ночной сменой задания на сломанный и снятый на ремонт принтер, второй скачек это они забылись и отправили снова, как можно видеть с 12 ночи к 12ти утра мы уже имеем 50% загрузку

      поэтому этот скрипт и писался, за счет его отчета на почту, можно сразу разобраться от какого принтера произошел сбой и уже работать от него. Это вполне конкретная вещь, она позволяет видеть что именно вызвало такой жвах, и перегружать самостоятельное если загрузка достигла 100%. При нагрузке под 100 тормозить начнут уже все вообще принтера, и звонить начнут все отделы


  1. 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-alert


    1. pak-nikolai Автор
      20.02.2018 08:59

      спасибо за инфо
      может ктонибудь напишет статью по perfomance counter, чтото типа get-counter и так далее.
      Как вы используете счетчики? Я их применял всего пару раз т.к. обычно хватает мониторинга, а при работе скрипта хватает WMI.

      в данном случае проблема в библиотеках драйвера, если разберусь в чем дело обновлю статью


      1. Louie
        20.02.2018 11:50

        Да никак использую. Просто подумал, что слишком уж неэлегантно скриптом крошить спулер по расписанию (извините) — вот и глянул в энторнетах чего там в новых виндовс напихано. У меня ток 2003 в KVM осталась. И то исключительно для всяких ваших одинэсов. А там хоть и есть каунтеры, но нет алертов.
        Выяснилось, что помимо счетчиков производительности и журнала (который, при желании можно парсить скриптом) — есть т.н. performance counter alerts, которые по пороговым срабатываниям счетчиков могут гибко вам решить проблему. И самое первое действие я бы поставил — оповестить пользователей, чтоб не печатали больше на принтер N и как-то отключить возможность постановки в очередь заданий на сбойный принтер.


        1. pak-nikolai Автор
          20.02.2018 13:04

          а он и не крошит по расписанию, он проверяет по расписанию и если очередь забилась выписывает что в ней застряло, затем останавливает спулер и выбрасывает задания, както так работает

          а наступление момента Х определяется по загрузке процессора, он не дает чтобы загрузка была 100% больше 5ти секунд


  1. 14th
    20.02.2018 10:40

    У kyocera очень кривые драйвера. Скриптом это не победить. У меня много орг-ций под наблюдением и только у одной такие принтера. Пока её пользователи не появляются на общих ресурсах — там всё работает хорошо. Частично вопрос снимает EasyPrint.


    1. pak-nikolai Автор
      20.02.2018 11:02

      я тоже так понял, когда докопал до самого драйвера. снижает нагрузку также отключение gui kyocera — окошечко такое вылазиет сбоку на принт сервере. нагрузка начинает расти когда драйвер пытается отправить на принтер которого физически нет, с двух заданий в очереди всегда есть глюк. помогает если почистить очеред и убить процесс в памяти вручную, тоже работать будет.

      как я понял нужно на альтернативные дровишки какието переходить или протокол использовать какойто другой (родной?)

      в принципе я решил это таким образом, этакий workaround, теоритически надо загрузку процессоров смотреть чтобы правильно убивать, но в принципе за пол года такая схема работает нормально.


      1. MagicEx
        20.02.2018 11:39

        Пробовали встроенные в винду v4 драйвера? У нас была проблема с HP UPD всех версий.
        Когда число принтеров достигло 200 — с ними работать стало невозможно, свойства принтера открывались минут по 5.
        Переход на Class Driver проблему решил полностью.
        На Kyocera вижу тоже есть встроенные драйвера, в том числе и универсальные Monochrome\Color.


        1. pak-nikolai Автор
          20.02.2018 11:45

          спасибо, попробую отпишусь


          1. 4mz
            20.02.2018 21:00

            Также испытываем много проблем с Kyocera, очередь часто застревает, а еще периодически возникают проблемы с полями непонятно от чего, в предварительном просмотре при печати все отлично, а в напечатанном документе поля шире\уже\хренпоймикак (((

            Напишите об опыте замены KX драйвера на что-то иное, если будет возможность!


            1. pak-nikolai Автор
              20.02.2018 23:19

              по полям не понятно, из какой программы печатаете?
              какая модель принтера?


  1. mayorovp
    20.02.2018 13:27

    А приостановка печати в очередях к отключенным принтерам не поможет? Или пользователи обратно включают?


    1. pak-nikolai Автор
      20.02.2018 14:31

      если принтер отключают на ночь или по запарке не поможет, а если действие точно спрогнозировано поможет