-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathImport-ABCUpdateLog.ps1
More file actions
236 lines (180 loc) · 9.32 KB
/
Copy pathImport-ABCUpdateLog.ps1
File metadata and controls
236 lines (180 loc) · 9.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
function ConvertFrom-GlobalDateString {
<#
.SYNOPSIS
Converts a text date from certain popular cultures to an object in the local culture.
#>
param([string]$Date, $DateOrTimeOrBoth = 'Both')
$Culture = Get-Culture
$CultureDateTimeFormat = $Culture.DateTimeFormat
$DateFormat = $CultureDateTimeFormat.ShortDatePattern
$TimeFormat = $CultureDateTimeFormat.LongTimePattern
$fmtDateTime = switch ($DateOrTimeOrBoth) {
'Date' {$DateFormat} ;
'Time' {$TimeFormat} ;
'Both' {"$DateFormat $TimeFormat"} ;
Default {'Unhandled parameter value'}
}
[DateTime]::ParseExact(
$Date,
$fmtDateTime,
[System.Globalization.DateTimeFormatInfo]::InvariantInfo,
[System.Globalization.DateTimeStyles]::None
)
}
function Import-AbcUpdateLog {
[CmdletBinding()]
param (
# Path the the log file which contains the output of the ABC-Update.exe command.
[Parameter(Position=0,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[string]
$Path = 'C:\Temp\ABC-Update-Install.txt'
)
begin {
$ptnSeparatorLine = '^_+?\s*$'
$ptnNewName = '\-|\s'
$ptnPatchStatus = '^(.+?)\|'
$ptnPublishedDate = '.+?\|(.+?)\|'
$ptnKbNumber = '.+?(KB\d{6,11})\s\|'
$ptnRevision = '.+?REV\.(\d+?)\s\|'
$ptnPatchTitle = '.+?REV\.\d+?\s\|(.+)'
$ptnFinalMessage = 'Finished\s-\s(.+?)$'
$TextInfo = (Get-Culture).TextInfo
}
process {
if (Test-Path $Path) {
# Different data exists above and below a separator line of underscores (_)
$LogContent = Get-Content $Path
$SeparatorLine = $LogContent | Select-String -Pattern $ptnSeparatorLine
$intSeparatorIndex = ($SeparatorLine.LineNumber) - 1
# first we parse the metadata about the action being performed into a hashtable
$strMetaData = $LogContent[0..$intSeparatorIndex] | Where-Object {$_ -match ':\s'}
$MetaDataHashtable = $strMetaData -replace '\:\s','= ' | ConvertFrom-StringData
# make sure we have content
if (! ($MetaDataHashtable.keys) ) {
echo "No log content: "
$LogContent
exit 0
}
# now we make an object from the hashtable
$MetaDataObject = New-Object PSCustomObject
$MetaDataHashtable.keys | ForEach-Object {
# Convert the keys to Title case, and remove the spaces and hyphens
$newName = $TextInfo.totitlecase($_) -replace $ptnNewName
# use newName for the property name, and add the member with its value to the object
Try {
$MetaDataObject |
Add-Member -MemberType NoteProperty -Name $newName -Value ($MetaDataHashtable.$_) -ea Stop
} Catch {
Write-Warning "Hash key converted to '$newName' and threw an error: $($_.Exception.message)"
}
}
# cast any non-string data types properly
# These are the action list property names
Try {
if ($MetaDataObject.Time) {$MetaDataObject.Time =
ConvertFrom-GlobalDateString ($MetaDataObject.Time) -ea Stop }
} Catch {
$MetaDataObject.Time = (Get-Item $Path -ea 0).LastWriteTime -as [datetime]
}
if ($MetaDataObject.UpdateAPIVersion) {$MetaDataObject.UpdateAPIVersion = [version]$MetaDataObject.UpdateAPIVersion}
if ($MetaDataObject.WindowsUpdateVersion) {$MetaDataObject.WindowsUpdateVersion = [version]$MetaDataObject.WindowsUpdateVersion}
if ($MetaDataObject.ABCUpdateVersion) {$MetaDataObject.ABCUpdateVersion = [version]$MetaDataObject.ABCUpdateVersion}
if ($MetaDataObject.MaxReboots) {$MetaDataObject.MaxReboots = [int16]$MetaDataObject.MaxReboots}
# These are the action install property names
# ????
# I'm not sure if the Install ones are the same or different, all or some
# to determine if any keys are unhandled, we keep this hard-coded list of handled keys
$HandledListKeys = @(
'Time'
'Computer Name'
'Windows version'
'Reboot request'
'ABC-Update Version'
'Windows Update Version'
'Update API Version'
'Action'
'Max reboots'
'Server Type'
'Type'
'Title Include'
'Query'
)
$HandledInstallKeys = @()
$HandledKeys = $HandledListKeys + $HandledInstallKeys
# We compare to find new keys
$UnhandledKeys = Compare-Object ($MetaDataHashtable.Keys) ($HandledKeys) |
Where-Object {$_.SideIndicator -eq '<=='}
if ($UnhandledKeys) {
# We warn the console only that there is an issue
Write-Warning "Unhanlded Key Names exist. They are:"
Write-Host $UnhandledKeys -f Yellow
}
# Now parse the text after the separator in an array of patch info
$postSeparatorContent = $LogContent[($intSeparatorIndex + 1)..($LogContent.count - 1)]
if ($postSeparatorContent -match '\|') {
# If KB info is found after the separator, make a nested object of KB info
$KbInfoObject = New-Object System.Collections.ArrayList
# Parse the KB Info
$postSeparatorContent | Where-Object {$_ -match '\|'} | ForEach-Object {
# Define the values to capture
$PatchStatus = [regex]::Match($_,$ptnPatchStatus).Groups[1].Value.Trim()
# cast any non-string data types properly
[datetime]
$PublishedDate = [regex]::Match($_,$ptnPublishedDate).Groups[1].Value.Trim()
$KB = [regex]::Match($_,$ptnKbNumber).Groups[1].Value.Trim()
[int]
$Revision = [regex]::Match($_,$ptnRevision).Groups[1].Value.Trim()
$PatchTitle = [regex]::Match($_,$ptnPatchTitle).Groups[1].Value.Trim()
# Add an object to the array
[void]($KbInfoObject.Add(
[PSCustomObject]@{
Status = $PatchStatus
PublishedDate = $PublishedDate
KB = $KB
Revision = $Revision
Title = $PatchTitle
}
))
}# $postSeparatorContent | Where-Object {$_ -match '\|'} | ForEach-Object
# Determine if the action performed caused a change in pending reboot status
$boolRebootFlagged =
if ($strFinalMessage -eq 'successful, no reboot required') {
$false
} elseif ($strFinalMessage -eq 'successful, reboot required') {
$true
} elseif ($strFinalMessage -eq 'at least one error occurred, no reboot required') {
$false
} elseif ($strFinalMessage -eq 'at least one error occurred, reboot required') {
$true
} else { # Any other case, no reboots needed
$false
}
# file://tonyp/c$/TP/Utilities/ABC-Update/ABC-Update.pdf pg.18 Return Codes
# Read the final message
$strFinalMessage = [regex]::Match($LogContent,$ptnFinalMessage).Groups[1].Value
# Add to the output object
$MetaDataObject | Add-Member -MemberType NoteProperty -Name 'KbInfo' -Value $KbInfoObject
$MetaDataObject | Add-Member -MemberType NoteProperty -Name 'RebootFlagged' -Value $boolRebootFlagged
$MetaDataObject | Add-Member -MemberType NoteProperty -Name 'FinalMessage' -Value $strFinalMessage
} else {
# If KB info is NOT found after the separator, read the error message and add it to the object
$ErrorMessage = @() ; $val = 1
for ($i = 1; -not [string]::IsNullOrWhiteSpace($val) ; $i++) {
# Every line after the separator that isn't null is the error message
$val = $LogContent[$intSeparatorIndex + $i].Trim()
if (-not [string]::IsNullOrWhiteSpace($val)) {$ErrorMessage += $val}
}
$MetaDataObject | Add-Member -MemberType NoteProperty -Name 'ErrorMessage' -Value ($ErrorMessage -join "`n")
#write-debug 'MetaDataObject' -debug
}# if ($postSeparatorContent -match '\|')
# Add to the output object and output to pipeline
$MetaDataObject | Add-Member -MemberType NoteProperty -Name 'RawContent' -Value ($LogContent -join "`n") -PassThru
} else {
Write-Error "Log file ($($Path)) not found on $($env:COMPUTERNAME)"
}
}
end {
}
}