Centralized collection and processing of Windows print logs

In small offices, network printers and MFPs are not always used, so it’s difficult to get statistics on the use of printing devices. Especially if it is required to be done at minimal cost. Such data can help determine the most actively used devices, assess the load on them and make timely decisions on the purchase of consumables, maintenance, or even replacement with more economical and productive ones. It is possible to solve this problem without additional software using the built-in tools for managing Windows logs and a Powershell script.

Using search engines, you can find ready-made software products for audit of printing in the office, for example:
Such systems are far from always suitable, since they require purchase, free versions have limited functionality, it is necessary to install both central software on the server and agents on client computers, some programs work only with a print server and so on. I propose using the built-in tools of the Windows 7 / 2008R2 operating system and scripts on Powershell to solve the problem.

So, we will take the following information as initial data:
  • The organization has an Active Directory domain
  • The organization uses computers with an operating system of at least Windows 7, and servers with an operating system of at least Windows Server 2008R2
  • There are both network and local printers and MFPs.
  • There is a need to centrally process print logs from printers and have statistics on usage and load on printing devices.

Infrastructure preparation


The first step is to prepare the infrastructure for the centralized collection of event logs from client computers.
The following settings are required for the Windows event subscription to work on the source computer:
  1. The presence of a user in the Event Log Readers group on behalf of which the log will be read
  2. Remote Management Access (Windows Remote Management from the Collector Server
  3. Configured permission to forward events to the log collector server.
  4. Enabled print event log (disabled by default)

We create a new user in the Users and Computers snap-in, specify EventCollectorUser as the full name and login. We assign a complex password and check the boxes “Deny changing the password by the user” and “Password is unlimited”.

Next, create a new group policy on the domain controller and name it, for example, GPO-EventCollector.
In the policy we set the following parameters:
  1. In the " Computer Configuration - Settings - Control Panel Settings - Services " section, create a service entry " Startup: Automatic (delayed start) ", " Service name - Windows Remote Management Service (MS-Management) (WinRM)", " Service action : Startup services »
  2. In the " Computer Configuration - Policies - Administrative Templates - Windows Components - Windows Remote Control - Windows Remote Control Service " section, set the option " Allow listeners to be configured automatically : Enable" and set the IPv4 filter parameter to "*".
  3. In the section “ Computer Configuration - Policies - Windows Configuration - Security Settings - Windows Firewall with Advanced Security - Windows Firewall with Advanced Security - Rules for Incoming Connections ” we create a new rule. We select the item " Predefined rules " and in the list we select " Remote control of Windows (HTTP - incoming traffic) "
  4. In the " Computer Configuration - Policies - Administrative Templates - Windows Components - Event Forwarding " section, set the " Configure destination subscription manager " parameter and in the " SubscriptionManagers " parameter section enter the full FQDN path to the collector server.
  5. In the section " Computer Configuration - Policies - Windows Configuration - Security Settings - Restricted Access Groups " we add a new group " Event Log Readers ". We add the user EventCollectorUser created by us to the group members.


After creating Group Policy, you must restart the target computers or run the gpupdate / force command on them .

You can enable the print log on the target computer through the MMC snap-in "Event Viewer" along the path " Application and Service Logs - Microsoft - Windows - PrintService - Works " (right-click on the log and select "enable"). This option is suitable if there are not many computers.
If you want to enable the log on a large group of PCs, then you can use the following method:
  1. Prepare a text file with a list of computer names. For example, d: \ temp \ computers.txt
  2. Run the following command as a domain administrator user:
    For / F% i in (d: \ temp \ computers.txt) do wevtutil sl Microsoft-Windows-PrintService / Operational / e: true / r:% i


Update: Also, as NeSvist correctly noted , you can set the registry key:
HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ Windows \ CurrentVersion \ WINEVT \ Channels \ Microsoft-Windows-PrintService / Operational - Enabled to 1.
This parameter can be shared in the group policy, which we created above.
For reference: About working with the registry in the GPO

Create an event subscription


After preparing the infrastructure, you can proceed directly to setting up an event subscription on the collector server.
  1. We go to the " Event Viewer - Subscriptions " snap-in .
  2. Select on the right in the menu " Create a subscription ... "
  3. Enter the name of the subscription, for example, “Test Subscription”. Add a clear description.
  4. Next, select the log in which the received events will fall. I recommend leaving everything as default - “Forwarded Events”. Here it is worth noting that I could not find a way to put events in a manually created log. So the default option remains almost the only one.
  5. Next, click on the button " Select computers ... ". In the window that opens, using the Add button, add the necessary source computers. Using the “Check” button, you can check the availability of the target machines using the remote control protocol, the result will be shown in the information window.
  6. We select the events that we will collect from sources. We are interested in the level of events “Details” from the journal “ Microsoft-Windows-PrintService / Works ” with event code 307. We leave all other settings by default.
  7. We click on the “Advanced” button, in the window that opens, select the “specific user” item and indicate the user we created and his password. We leave the event delivery optimization settings in the “Normal” position.
  8. Click “OK” and the subscription is created. When events appear in the source computer log, they will be uploaded to the collector server in the Redirected Events log within 15 minutes.

Script for processing collected events


Collected events can be uploaded in CSV format using the “Event Viewer” snap-in, but the data obtained will not be very informative and will not allow them to obtain interesting statistics that can be shown to management. Therefore, I propose the option of processing received events using Powershell tools for the convenience of their further use.
A search on the Internet has found a print log analysis script for Windows Server 2003. (http://trevorsullivan.net/2009/11/06/windows-2003-print-log-parsing-script-powershell/) As there are certain differences in Powershell cmdlets of new versions, and the method of receiving events differs from the one proposed in the article, I redesigned the script.
I did not change the general structure of the script, it is divided into functions that are easy to modify (in case of localization, for example). The logic of the script is as follows:
  1. Get a list of events from the log.
  2. We parse the Message property of each event and write the fields to the PrintJob object
  3. Save the resulting list of objects in CSV format


We get the list of events from the journal “Redirected Events” using code 307 (in case any more events are sent to the specified log).
Function GetPrintEntriesFromLog()
{
	$PrintEntries = get-winevent -FilterHashTable @{LogName='ForwardedEvents'; ID=307;}
	return $PrintEntries
}

Create a new PrintJob object. Add the required fields to it.
Hidden text
Function CreatePrintJob()
{
	$PrintJob = New-Object PsObject
	Add-Member -Force -InputObject $PrintJob -MemberType NoteProperty -Name PageCount -Value $null
	Add-Member -Force -InputObject $PrintJob -MemberType NoteProperty -Name UserName -Value $null
	Add-Member -Force -InputObject $PrintJob -MemberType NoteProperty -Name DocumentName -Value $null
	Add-Member -Force -InputObject $PrintJob -MemberType NoteProperty -Name Size -Value $null
	Add-Member -Force -InputObject $PrintJob -MemberType NoteProperty -Name Printer -Value $null
	Add-Member -Force -InputObject $PrintJob -MemberType NoteProperty -Name Time -Value $null
	return $PrintJob
}

We get from the Message property of the event the Username, Printer name, Number of printed pages in the document, Document name fields . We pull it all out of the string with regular expressions. In an English-language journal, we change the lines in regular expressions to others, respectively.
Hidden text
#Парсим параметры объекта
Function ParsePrintEntry($PrintEntry)
{
	$NewPrintJob = CreatePrintJob
	$NewPrintJob.PageCount = GetPageCount $PrintEntry.Message
	$NewPrintJob.UserName = GetUserName $PrintEntry.Message
	$NewPrintJob.DocumentName = GetDocumentName $PrintEntry.Message
	$NewPrintJob.Size = GetPrintSize $PrintEntry.Message
	$NewPrintJob.Printer = GetPrinterName $PrintEntry.Message
	$NewPrintJob.Time = $PrintEntry.TimeCreated.ToString()
	return $NewPrintJob
}
#Получаем имя пользователя
Function GetUserName($PrintEntry)
{
	If ($PrintEntry -eq "" -or $PrintEntry -eq $null) { return $null }
	$rxUserName = [regex]"которым владеет ([0-9a-zA-Z.]{1,})"
	$rxMatches = $rxUserName.Match($PrintEntry)
	return $rxMatches.Groups[1].Value
}
#Получаем имя принтера
Function GetPrinterName($PrintEntry)
{
	If ($PrintEntry -eq "" -or $PrintEntry -eq $null) { return $null }
	$rxPrinterName = [regex]"распечатан на (.{1,}) через"
	$rxMatches = $rxPrinterName.Match($PrintEntry)
	return $rxMatches.Groups[1].Value
}
#Получаем размер распечатки в байтах
Function GetPrintSize($PrintEntry)
{
	If ($PrintEntry -eq "" -or $PrintEntry -eq $null) { return $null }
	$rxPrintSize = [regex]"Размер в байтах: ([0-9]+)."
	$rxMatches = $rxPrintSize.Match($PrintEntry)
	return $rxMatches.Groups[1].Value
}
#Получаем количество страниц (самый важный параметр)
Function GetPageCount($PrintEntry)
{
	If ($PrintEntry -eq "" -or $PrintEntry -eq $null) { return $null }
	$rxPageCount = [regex]"Страниц напечатано: ([0-9]+)"
	$rxMatches = $rxPageCount.Match($PrintEntry)
	return $rxMatches.Groups[1].Value
}
#Получаем имя документа
Function GetDocumentName($PrintEntry)
{
	If ($PrintEntry -eq "" -or $PrintEntry -eq $null) { return $null }
	$rxDocumentName = [regex]", (.{1,}) которым владеет"
	$rxMatches = $rxDocumentName.Match($PrintEntry)
	return $rxMatches.Groups[1].Value
}


The main function. We get the list of events and in a cycle we turn it into a PrintJob object. Then we export the resulting list of objects to the specified file. We set the encoding UTF8 for the correct display of the Cyrillic alphabet and the separator ";" for the readable opening of the file in Excel. Every 100 events we write a log, this is convenient for debugging.
Function Main()
{
	$PrintEntries = GetPrintEntriesFromLog
	$Global:ParsedEntries = @{}; $i = 0
	ForEach ($PrintEntry in $PrintEntries)
	{	
        $ParsedEntries.Add($i, $(ParsePrintEntry $PrintEntry))
		$i++
		if ($i % 100 -eq 0)
		{ Write-Host "Processed $i records" }
	}
    $ParsedEntries.Values | Export-Csv "D:\PrintReports\PrintUsageReport.csv" -NoTypeInformation -Encoding UTF8 -Delimiter ';'
}
#Запускаем главную функцию.
Main 

When preparing regular expressions for the script, the site Regex101.com was used . The site is understandable, with brief documentation, highlighting the search result in the source line, decoding the meaning of the regular expression on the fly. Very informative and convenient, recommended for use.
The resulting script can be applied on demand by running PowerShell in the command shell, or it can be assigned as a task in the Task Scheduler.
To do this, you must:
  1. Open Task Scheduler ( Start - Administrative Tools - Task Scheduler )
  2. Select " Create a simple task ... "
  3. Enter task name, description, select execution interval
  4. Select the action " run the program ." As the program, select "C: \ Windows \ System32 \ WindowsPowerShell \ v1.0 \ powershell.exe", and as the argument, the path to the script.
  5. Open the properties of the task and select the "Run regardless of user registration" switch, select the user on behalf of whom the task should be performed and enter the password for it.

Thus, in the directory specified when creating the script, the file from the lists of printed documents will be updated at a specified interval.

Solution of problems


  1. If an error of the form occurs when running the script:
    The file D: \ PrintReports \ PrintInfo.ps1 cannot be loaded because script execution is disabled on this system. For more
    information, see about_Execution_Policies at go.microsoft.com/fwlink/?LinkID=135170 .
    + CategoryInfo: Security Error: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId: UnauthorizedAccess
    this means that the security policy for executable Powershell scripts does not allow the program to run. To run the created script, it is necessary to lower the security level to “ Remote Signed ”, that is, any scripts created locally will be allowed to run, and scripts created on remote systems will be executed only if signed by a trusted publisher. To do this, in the Powershell console running as administrator, you must run the Set-ExecutionPolicy RemoteSigned command.
  2. You can use the eventcreate.exe command-line tool to test forwarding and collecting events. This utility allows you to create events manually in a specific log. For example, you can create an event with ID 100 in the Application log using the following command: eventcreate / t error / id 100 / l application / d “Custom event” . If everything is configured correctly and there is a collection of events from the specified log, the event will be on the collector server within a minute.
    If the event did not reach the collector server, you need to check the following:
    • If the settings were specified by Group Policy, then verify that the policy is applied. If necessary, you can enforce Group Policy with the gpupdate / force command
    • Status of the Windows Remote Management Service (WinRM). The service must be started and configured to start automatically. If necessary, you can configure this service on the client computer with the winrm quickconfig command . This command configures the WinRM service, creates a winrm listener, and creates a firewall exception rule.
      To verify that the collector server can connect to the source computer using WinRM, you can use the following command: winrm id -remote: <Target computer name> -u: <Username> -p: <Password> . The account and password of a user with the ability to connect via winrm are indicated.
    • The EventCollectorUser user is a member of the Event Log Readers group. Only members of this group can read events on a specific computer.


What can be improved


The proposed processing script is not a universal solution and can be improved in various directions, for example,
  • Selection of events by time (per day, week, month) and recording them in the corresponding separate sheets of the spreadsheet or output CSV files. It is possible to provide for setting the date range using the input parameters of the script.
  • Adding a graphical representation of the data using pivot tables for users and printers and graphs. Such a report can immediately be put on the table to the leader.
  • Set up archiving and rotation of the Redirected Events journal, add a function to analyze the corresponding archive files. This will be reasonable when working with a sufficiently large number of computers (more than 50).

So, in this article I tried to show the principle of a centralized collection of events from Windows logs and their further processing using Powershell. This method is suitable for solving problems in small organizations. Of course, specialized software copes with its goals more efficiently, but it is expensive, more cumbersome, its installation brings the IT specialist less new knowledge and skills, understanding the operation of the software he serves.

In preparing this article, the following materials and sources were used:
  1. mcp.su/windows-server-2008/event-collector-in-windows-server-2008
  2. windowsnotes.ru/powershell-2/nastrojka-udalennogo-vzaimodejstviya-v-powershell-chast-1
  3. social.technet.microsoft.com/Forums/en-US/8e7399f6-ffdc-48d6-927b-f0beebd4c7f0/enabling-print-history-through-group-policy?forum=winserverprint
  4. mywinsysadm.wordpress.com/2012/07/16/powershell-audit-printer-event-logs
  5. www.winblog.ru/admin/1147767392-29031101.html
  6. windowsitpro.com/security/q-what-are-some-simple-tips-testing-and-troubleshooting-windows-event-forwarding-and-collec


Update:As noted by NeSvist and selenite , binding to a specific localization and parsing it with regular expressions is not the most effective solution. In this regard, I present an option for processing collected events using their representation in XML. The solution turned out to be more accurate and understandable.

Spoiler heading
# Получаем события из лога      
$Events = Get-Winevent -FilterHashTable @{LogName='ForwardedEvents'; ID=307;}      
# Массив заданий печати
$Jobs = @()
ForEach ($Event in $Events) {            
    # Конвертируем событие XML            
    $eventXML = [xml]$Event.ToXml()
    # Создаем новый объект задания печати и заполняем поля из XML-представления события            
    $PrintJob = New-Object PsObject
    Add-Member -Force -InputObject $PrintJob -MemberType NoteProperty -Name PageCount -Value $eventXML.Event.UserData.DocumentPrinted.Param8
	Add-Member -Force -InputObject $PrintJob -MemberType NoteProperty -Name UserName -Value $eventXML.Event.UserData.DocumentPrinted.Param3
	Add-Member -Force -InputObject $PrintJob -MemberType NoteProperty -Name DocumentName -Value $eventXML.Event.UserData.DocumentPrinted.Param2
	Add-Member -Force -InputObject $PrintJob -MemberType NoteProperty -Name Size -Value $eventXML.Event.UserData.DocumentPrinted.Param7
	Add-Member -Force -InputObject $PrintJob -MemberType NoteProperty -Name Printer -Value $eventXML.Event.UserData.DocumentPrinted.Param5
    # Приводим дату из формата SystemTime к обычному представлению.
    $date = Get-Date $eventXML.Event.System.TimeCreated.SystemTime
    Add-Member -Force -InputObject $PrintJob -MemberType NoteProperty -Name Time -Value $date
    # Добавляем задание печати к массиву
    $Jobs += $PrintJob
}            
# Выводим список полученных заданий печати в CSV          
$Jobs | Export-Csv D:\PrintReports\events.csv -NoTypeInformation -Encoding UTF8 -Delimiter ';'  


Also popular now: