PowerShell, Windows Patches, and the ReportingEvents.log
With respect to Windows Updates (workstation and server), if you search up the C:\Windows\SoftwareDistribution\ReportingEvents.Log you typically won’t find a lot. The general theme is to use the WindowsUpdate.log, since there is a lot more information in that file.
I’ve been using the ReportingEvents.Log for a long time to complement reporting from WSUS and SCCM. I will also admit, if you just view the ReportingEvents.log in a text editor such as Notepad, you can’t make too much sense out of it. The point of this post is to demonstrate how to build a PowerShell Custom object out of the ReportingEvents.Log that will provide useful information. More specifically, this will provide a summary of what exactly is going on for each endpoint.
The log file is tab delimited, and easy to parse. There is one regular expression to extract the date and time from the second field. If you don’t understand the regular expression, I encourage you to figure it out (I’m definitely not a regular expression expert). I recently started dabbling with regular expressions and $matches mainly because I stumbled across a post that used regular expressions heavily. It honestly looked like a foreign language to me. Once I understood $matches, string parsing, and labeling, it was $money!
The key to the summary object is tagging key events. In the code below, I only focused on UpdateOrchestrator. Feel free to add other components such as patching from the SCCM client. Once the summary object is built and you start looking across multiple workstations, patterns start to appear. You can identify machines not syncing with the WSUS server. Working on patching issues directly from the endpoints does complement enterprise patching solutions and tends to raise compliance percentages.
Code:
# Create the Object
$Obj = [PSCustomObject]@{
UpdateOrchestrator_LastSync = $null
UpdateOrchestrator_LastSyncText = $null
UpdateOrchestrator_LastInstall = $null
UpdateOrchestrator_LastInstallText = $null
UpdateOrchestrator_LastFailure = $null
UpdateOrchestrator_LastFailureText = $null
LogEntry = New-Object "System.Collections.Generic.List[psobject]"
ObjTimeStamp = (get-date)
}
## Set the Log File
$LogFile = "C:\Windows\SoftwareDistribution\ReportingEvents.Log"
## Read the log and process it
$LogFileContents = Get-Content $LogFile
# Loop through each line in the file
foreach ($i in $LogFileContents) {
# Process the each log line
if ($i.StartsWith("{")) {
$LogEntryObj = [PSCustomObject]@{
Field1 = $i.Split("`t")[0]
Field2 = $i.Split("`t")[1]
Field3 = $i.Split("`t")[2]
Field4 = $i.Split("`t")[3]
Field5 = $i.Split("`t")[4]
Field6 = $i.Split("`t")[5]
Field7 = $i.Split("`t")[6]
ErrorCode = $i.Split("`t")[7]
Component = $i.Split("`t")[8] # UpdateOrchestrator, Windows Defender Antivirus, etc.
ActionResult = $i.Split("`t")[9] # Success, Failure
Action = $i.Split("`t")[10] # Content Download, Content Install, Software Synchronization, etc.
ActionResultText = $i.Split("`t")[11]
Field13 = $i.Split("`t")[12]
Date = $null
}
# Parse out the date with a regex
if ($LogEntryObj.Field2 -match "(?<DateTime>\d\d\d\d-\d\d-\d\d\s\d\d:\d\d:\d\d:\d\d\d).*") {
$LogEntryObj.Date = [datetime]::ParseExact($Matches.DateTime,"yyyy-MM-dd HH:mm:ss:fff",$null)
}
# Get the last UpdateOrchestrator Sync
if ($LogEntryObj.Component -eq "UpdateOrchestrator" -and $LogEntryObj.Action -eq "Software Synchronization" -and $LogEntryObj.ActionResult -eq "Success") {
$Obj.UpdateOrchestrator_LastSync = $LogEntryObj.Date
$Obj.UpdateOrchestrator_LastSyncText = $LogEntryObj.ActionResultText
}
# Get the last UpdateOrchestrator Last Install
if ($LogEntryObj.Component -eq "UpdateOrchestrator" -and $LogEntryObj.Action -eq "Content Install" -and $LogEntryObj.ActionResult -eq "Success") {
$Obj.UpdateOrchestrator_LastInstall = $LogEntryObj.Date
$Obj.UpdateOrchestrator_LastInstallText = $LogEntryObj.ActionResultText
}
# Get the last UpdateOrchestrator Last Failure
if ($LogEntryObj.Component -eq "UpdateOrchestrator" -and $LogEntryObj.Action -eq "Content Install" -and $LogEntryObj.ActionResult -eq "Failure") {
$Obj.UpdateOrchestrator_LastFailure = $LogEntryObj.Date
$Obj.UpdateOrchestrator_LastFailureText = $LogEntryObj.ActionResultText
}
# Add the Log Entry Object to the list
$Obj.LogEntry.Add($LogEntryObj)
}
}
# Return the Object
$Obj
Here is an example of what the object looks like:
Code:
UpdateOrchestrator_LastSync : 12/2/2019 11:28:05 AM
UpdateOrchestrator_LastSyncText : Windows Update Client successfully detected 1 updates.
UpdateOrchestrator_LastInstall : 12/2/2019 11:28:18 AM
UpdateOrchestrator_LastInstallText : Installation Successful: Windows successfully installed the following update: Security Intelligence Update for Windows Defender Antivirus - KB2267602 (Version 1.305.3192.0)
Feedback is welcomed and follow Dustin Higgins on Twitter
DHB
Edited by user Tuesday, December 03, 2019 3:33:02 AM(UTC)
| Reason: Not specified