📜 ⬆️ ⬇️

Centralized collection and processing of Windows print logs

In small offices, network printers and multifunction printers are not always used; therefore, it is rather difficult to obtain statistics on the use of printing devices. Especially, if it is required to produce with minimal cost. Such data can help identify 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. This task can be solved without additional software using the built-in management tools for Windows logs and Powershell script.

Using search engines you can find ready-made software products for auditing printing in the office, for example:
Such systems are far from always suitable, as they require a 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 suggest using the built-in tools of the Windows 7 / 2008R2 operating system and Powershell scripts to solve the problem.

So, let's take the following information as the source data:

Infrastructure preparation


The first step is to prepare the infrastructure for centrally collecting event logs from client computers.
For the Windows event subscription to work on the source computer, the following settings are required:
  1. User presence in the Event Log Readers group, on whose behalf the log will be read
  2. Remote control access (Windows Remote Management from the collector server
  3. Configured permission to forward events to the log collector server.
  4. Print event log enabled (disabled by default)

Create a new user in the Users and Computers snap-in, and specify EventCollectorUser as the full name and login. We assign a complex password and put the checkboxes “Forbid changing the password by the user” and “Password validity is unlimited”.

Next, create a new group policy on the domain controller and call it, for example, GPO-EventCollector.
In the policy we set the following parameters:
  1. In the section " Computer Configuration - Settings - Control Panel Settings - Services " create a service entry " Startup: Automatic (Delayed Start) ", " Service Name - Windows Remote Management Service (MS-Management) (WinRM)", " Service Action : Start services "
  2. In the section " Computer Configuration - Policies - Administrative Templates - Windows Components - Windows Remote Management - Windows Remote Management Service " set the " Allow automatic configuration of listeners : Enable" option and set the "IPv4 Filter" section to the value "*".
  3. In the section “ Computer Configuration - Policies - Windows Configuration - Security Settings - Windows Firewall with Advanced Security - Windows Firewall with Advanced Security - Inbound Rules ” create a new rule. Select the item “ Predefined rules ” and in the list select “ Windows Remote Control (HTTP - incoming traffic) ”
  4. In the “ Computer Configuration - Policies - Administrative Templates - Windows Components - Event Forwarding ” section, set the “ Configure Subscription Manager ” option and in the “ SubscriptionManagers ” section enter the full FQDN path to the server – collector.
  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 EventCollectorUser user created by us to the group members.

')
After creating the group policy, you must restart the target computers or execute the gpupdate / force command on them.

You can enable the print log on the target computer using the Event Viewer MMC snap-in, under the path “ Applications and Services Logs — Microsoft — Windows — PrintService — Works ” (right-click on the log and select “enable”). This option is suitable if there are not so many computers.
In case you need to enable the log on a large group of PCs, 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 user with domain administrator rights:
    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 install the registry key:
HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ Windows \ CurrentVersion \ WINEVT \ Channels \ Microsoft-Windows-PrintService / Operational - Enabled equal to 1.
This parameter can be distributed in the group policy that we created above.
For reference: About working with the registry in GPO

Creating an event subscription


After preparing the infrastructure, you can proceed directly to setting up a subscription to events on the collector server.
  1. Go to the " Event Viewer - Subscriptions " snap-in.
  2. Choose on the right in the menu " Create a subscription ... "
  3. Enter the subscription name, for example, “Test Subscription”. Add a clear description.
  4. Next, select the log, which will receive received events. I recommend leaving everything by default - “Forwarded Events”. It is worth noting that I did not manage to find a way to place events in a manually created log. So the default option is almost the only one.
  5. Next, click on the button " Select computers ... ". In the window that opens, click the "Add" button to 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 events which we will collect from sources. We are interested in the level of events “Information” from the “ Microsoft-Windows-PrintService / Works ” log with event code 307. All other settings are left as default.
  7. We click on the "Advanced" button, in the window that opens, select the "specific user" item and specify the user we created and his password. The settings for optimizing the delivery of events are left in the “Normal” position.
  8. Click "OK" and the subscription is created. When events appear in the log of source computers, they will be uploaded to the collector server in the “Forwarded Events” log for 15 minutes.

Script for processing collected events


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


We receive a list of events from the “Forwarded Events” log by code 307 (in case any other 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 } 

From the Message property, we get the event field User name, Printer name, Number of printed pages in the document, Document name. We take it all out of the line with regular expressions. In the case of an English magazine, we change the lines in the 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 a list of events and turn it into a PrintJob object in a loop. Then we export the list of objects to the specified file. We put the UTF8 encoding for correct display of the Cyrillic alphabet and the separator ";" for readable opening of the file in Excel. Every 100 events we write a log, it is convenient when 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 a regular expression on the fly. Very informative and convenient, it is recommended to use.
The resulting script can be used on demand by launching the PowerShell in the command shell, or it can be assigned as a task in the Task Scheduler.
For this you need:
  1. Open Task Scheduler ( Start - Administration - Task Scheduler )
  2. Select " Create a simple task ... "
  3. Enter task name, description, select execution interval
  4. Select the action " run the program ." As a program, select “C: \ Windows \ System32 \ WindowsPowerShell \ v1.0 \ powershell.exe”, and the path to the script as an argument.
  5. Open the task properties and set the “Execute regardless of user registration” switch, select the user on whose behalf the task should be executed and enter the password for it.

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

Problem solving


  1. If an error of the following type occurs when starting the script:
    The file D: \ PrintReports \ PrintInfo.ps1 could not be loaded because script execution is disabled on this system. For more
    For 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 starting the program. To run the created script, you need to lower the security level to “ Remote Signed ”, that is, you will be allowed to run any scripts created locally, and scripts created on remote systems are executed only if signed by a trusted publisher. To do this, in the Powershell console running as an administrator, you must run the Set-ExecutionPolicy RemoteSigned command.
  2. You can use the eventcreate.exe command line tool to test the forwarding and collection of 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 server collector within a minute.
    If the event does not hit the collector server, you need to check the following:
    • If the parameters were set by group policy, then check that the policy is applied. If necessary, you can enforce group policy with the gpupdate / force command
    • The status of the Windows Remote Management (WinRM) service (Windows Remote Management Service). The service must be started and configured to start automatically. If necessary, this service can be configured on the client computer using 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> . Specify the account and password of the user who has the ability to connect via winrm.
    • 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,

So, in this article I tried to show the principle of centralized collection of events from Windows logs and their further processing with 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 an IT specialist less new knowledge and skills, and an understanding of the work of the software they serve.

The following materials and sources were used in the preparation of this article:
  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 give the option of processing the collected events using their representation in XML. The solution was more accurate and understandable.

Spoiler header
 #     $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 ';' 

Source: https://habr.com/ru/post/257297/


All Articles