Last active
July 4, 2021 01:37
-
-
Save fsackur/1d927ded8d660acc50999b86121aef6c to your computer and use it in GitHub Desktop.
Waits for lines in log files matching a pattern.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function Wait-LogLine | |
{ | |
<# | |
.SYNOPSIS | |
Waits for lines in log files matching a pattern. | |
.DESCRIPTION | |
This command blocks, reading a file until a line appears that matches a pattern. If the | |
line matches the pattern, the entire line is written to the output stream. | |
When all the provided patterns have been matched, the command exits. | |
This command discards lines where the timestamp is before a start time. If the timestamp is | |
after an end time, this command exits with a timeout error. The end time is calculated from | |
the start time and the timeout value. | |
.PARAMETER Path | |
Provide the path to a log file. | |
.PARAMETER Pattern | |
Provide a regex pattern for the line you want from the log. The first matching line will be | |
returned. | |
You can provide multiple patterns. The command will continue reading the log until all | |
patterns are matched. | |
Note that one line could potentially match multiple patterns. | |
.PARAMETER TimestampParser | |
Provide a scriptblock that parses a line and outputs a DateTime object or a string that can | |
be parsed by Get-Date. The scriptblock should receive the line of text as `$_`. | |
The default is: `{$_ -replace '\s*\|.*'}` | |
This works when the timestamp is the first column and the separator is `|`. | |
If the scriptblock fails to parse a timestamp, the log line is discarded. | |
.PARAMETER StartTime | |
Provide a start time. Lines with timestamps before this time will be discarded. | |
.PARAMETER Timeout | |
Enter a timeout value in seconds. Default is 300 (5 minutes). | |
.OUTPUTS | |
[string[]] | |
.EXAMPLE | |
$VminstLog = 'C:\Users\Rack\AppData\Local\Temp\2\vminst.log' | |
Wait-LogLine $VminstLog -Pattern 'exit code' | |
2021-07-04T10:18:28.242+11:00| BootStrapper-build-15389592| I0: Setup exit code is: 0 | |
Reads 'vminst.log' until a line is found matching 'exit code'. | |
.EXAMPLE | |
$VminstLog = 'C:\Users\Rack\AppData\Local\Temp\2\vminst.log' | |
& "C:\rs-pkgs\Software\VMware-tools-11.0.5-x86_64\setup64.exe" /x | |
Wait-LogLine $VminstLog -Pattern 'exit code', 'extracting setup files to' -StartTime '2021-07-04T10:18:28.074+11:00' | |
2021-07-04T10:18:28.106+11:00| BootStrapper-build-15389592| I0: Extracting setup files to "C:\Users\Rack\AppData\Local\Temp\2\{25932044-BBC8-444F-ACF4-7E508054FA12}~setup\" | |
2021-07-04T10:18:28.242+11:00| BootStrapper-build-15389592| I0: Setup exit code is: 0 | |
Reads 'vminst.log' until lines are found matching 'exit code' and 'extracting setup files | |
to'. Lines with a timestamp before the start time are discarded. | |
#> | |
[CmdletBinding(DefaultParameterSetName = 'TimestampCheck')] | |
param | |
( | |
[Parameter(Mandatory, Position = 0)] | |
[string]$Path, | |
[Parameter(Mandatory, Position = 1)] | |
[string[]]$Pattern, | |
[Parameter(ParameterSetName = 'NoTimestampCheck')] | |
[switch]$NoTimestampCheck, | |
[Parameter(ParameterSetName = 'TimestampCheck')] | |
[scriptblock]$TimestampParser = {$_ -replace '\s*\|.*'}, | |
[Parameter(ParameterSetName = 'TimestampCheck')] | |
[datetime]$StartTime = [datetime]::Now, | |
[Parameter(ParameterSetName = 'TimestampCheck')] | |
[int]$Timeout = 300 | |
) | |
$EndTime = $StartTime.AddSeconds($Timeout) | |
$Stream = $Reader = $null | |
try | |
{ | |
while ($true) | |
{ | |
if (-not $Reader) | |
{ | |
if (-not (Test-Path $Path)) | |
{ | |
Write-Verbose " ...waiting for '$Path' to be created..." | |
Start-Sleep 1 | |
continue | |
} | |
$Stream = [IO.FileStream]::new( | |
$Path, | |
[IO.FileMode]::Open, | |
[IO.FileAccess]::Read, | |
[IO.FileShare]::ReadWrite # This lets the writer keep writing while we read | |
) | |
$Reader = [IO.StreamReader]::new($Stream) | |
Write-Verbose " ...opened '$Path' for reading." | |
} | |
$Line = $Reader.ReadLine() | |
if ($null -eq $Line) | |
{ | |
Write-Debug " ...reached end of '$Path', waiting for writer..." | |
Start-Sleep 1 | |
continue | |
} | |
Write-Debug $Line | |
if ($NoTimestampCheck) | |
{ | |
# Lets Timeout work with NoTimestampCheck | |
$Timestamp = [datetime]::Now | |
} | |
else | |
{ | |
# https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_script_blocks#using-delay-bind-script-blocks-with-parameters | |
# Works in PSv2 | |
$Timestamp = $Line | Get-Date -Date $TimestampParser -ErrorAction SilentlyContinue | |
} | |
if ((-not $NoTimestampCheck) -and ($Timestamp -lt $StartTime)) | |
{ | |
continue | |
} | |
elseif ($Timestamp -gt $EndTime) | |
{ | |
Write-Error "Timed out after $Timeout seconds waiting for '$Path'." | |
return | |
} | |
[string[]]$MatchedPatterns = $Pattern | Where-Object {$Line -match $_} | |
if ($MatchedPatterns) | |
{ | |
Write-Output $Line | |
$Pattern = $Pattern | Where-Object {$MatchedPatterns -notcontains $_} | |
if (-not $Pattern) | |
{ | |
return | |
} | |
} | |
} | |
} | |
finally | |
{ | |
if ($Reader) | |
{ | |
$Reader.Dispose() | |
$Stream.Dispose() | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment