Skip to content

Instantly share code, notes, and snippets.

@fsackur
Last active July 4, 2021 01:37
Show Gist options
  • Save fsackur/1d927ded8d660acc50999b86121aef6c to your computer and use it in GitHub Desktop.
Save fsackur/1d927ded8d660acc50999b86121aef6c to your computer and use it in GitHub Desktop.
Waits for lines in log files matching a pattern.
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