<# .SYNOPSIS G-Log alert for NinjaOne: exit code 1 when the web server logs of the last minutes hold findings of the chosen severity, with the findings and what to do as the alert text. .DESCRIPTION Runs glog.com on the IIS log folder (or any web server log file or folder) and reads only the lines of the last minutes. Files last written before that are skipped, so a folder with months of logs costs no more than today's file. Schedule it every 15 minutes, or use it where NinjaOne reacts to a script's exit code; the output is the text of the alert: the period, requests, error shares, response time, and each finding with what to do. Nothing is written to disk. .PARAMETER LogPath A log file or folder. Default: the IIS log folder of this machine, all sites. .PARAMETER SinceMinutes How many minutes back to read. Default 15. .PARAMETER FailOn The lowest severity that raises the alert: critical (default), warning or info. .PARAMETER HttpErrPath Also read the HTTP.sys error log in this folder, such as C:\Windows\System32\LogFiles\HTTPERR. That log is read in full, not only the last minutes. .PARAMETER GLog Full path to glog.com. Default: the standard installation folder. .PARAMETER TimeoutSeconds Give up after this long. Default 600. .NOTES G-Log by Garia.Net - https://garia.net/ Run as System, so every log folder can be read. Windows PowerShell 5.1 or later. Exit codes: 0 nothing at or above FailOn, 1 findings (the output says which) or the logs could not be read, 2 glog.com not found or wrong input. #> [CmdletBinding()] param( [string]$LogPath = "$env:SystemDrive\inetpub\logs\LogFiles", [int]$SinceMinutes = 15, [string]$FailOn = 'critical', [string]$HttpErrPath = '', [string]$GLog = '', [int]$TimeoutSeconds = 600 ) $ErrorActionPreference = 'Stop' # NinjaOne script variables arrive as environment variables with the same name. if ($env:logPath) { $LogPath = $env:logPath } if ($env:sinceMinutes) { $SinceMinutes = [int]$env:sinceMinutes } if ($env:failOn) { $FailOn = $env:failOn } if ($env:httpErrPath) { $HttpErrPath = $env:httpErrPath } if ($env:glog) { $GLog = $env:glog } if ($env:timeoutSeconds) { $TimeoutSeconds = [int]$env:timeoutSeconds } function Find-GLog([string]$Given) { if ($Given) { return $Given } $pf = $env:ProgramW6432 if (-not $pf) { $pf = $env:ProgramFiles } Join-Path $pf 'GariaNetTools\G-Log\glog.com' } # Windows command-line quoting: a quote gets a backslash, and backslashes right before a quote # (or before the closing quote) are doubled, or "C:\" would swallow the quote after it. function Format-Argument([string]$Value) { $escaped = [regex]::Replace($Value, '(\\*)"', { param($m) ($m.Groups[1].Value * 2) + '\"' }) '"' + ($escaped -replace '(\\+)$', '$1$1') + '"' } # glog.com with both output streams read as UTF-8, whatever the console code page is. function Invoke-GLog([string]$Exe, [string[]]$Arguments, [int]$Timeout) { $psi = New-Object System.Diagnostics.ProcessStartInfo $psi.FileName = $Exe $psi.Arguments = ($Arguments | ForEach-Object { Format-Argument $_ }) -join ' ' $psi.UseShellExecute = $false $psi.CreateNoWindow = $true $psi.RedirectStandardOutput = $true $psi.RedirectStandardError = $true $psi.StandardOutputEncoding = [System.Text.Encoding]::UTF8 $psi.StandardErrorEncoding = [System.Text.Encoding]::UTF8 $p = [System.Diagnostics.Process]::Start($psi) # Read both at once: a full pipe on one of them would stop the other. $out = $p.StandardOutput.ReadToEndAsync() $err = $p.StandardError.ReadToEndAsync() if (-not $p.WaitForExit($Timeout * 1000)) { try { $p.Kill() } catch { } return [pscustomobject]@{ ExitCode = -1; Output = ''; Error = "no result within $Timeout seconds" } } $p.WaitForExit() [pscustomobject]@{ ExitCode = $p.ExitCode; Output = $out.Result; Error = $err.Result.Trim() } } # ------------------------------------------------------------------------------ input --- if (@('critical', 'warning', 'info') -notcontains $FailOn) { Write-Output "FailOn must be critical, warning or info, not '$FailOn'." exit 2 } if ($SinceMinutes -lt 1) { Write-Output "SinceMinutes must be 1 or more, not $SinceMinutes." exit 2 } $exe = Find-GLog $GLog if (-not (Test-Path -LiteralPath $exe -PathType Leaf)) { Write-Output "glog.com not found at $exe. Install G-Log 0.7.0 or later, or pass -GLog." exit 2 } # ---------------------------------------------------------------------------- analyse --- $arguments = @('--report', $LogPath) if ($HttpErrPath) { $arguments += @('--report', $HttpErrPath) } $arguments += @('--since', "$($SinceMinutes)m", '--format', 'text', '--fail-on', $FailOn, '--out', '-') $run = Invoke-GLog $exe $arguments $TimeoutSeconds switch ($run.ExitCode) { 0 { Write-Output "OK: no findings at or above $FailOn in the last $SinceMinutes minutes on $env:COMPUTERNAME." Write-Output '' Write-Output $run.Output.TrimEnd() exit 0 } 3 { Write-Output "ALERT: findings at or above $FailOn in the last $SinceMinutes minutes on $env:COMPUTERNAME." Write-Output '' Write-Output $run.Output.TrimEnd() exit 1 } 2 { Write-Output "G-Log did not accept the input: $($run.Error)" exit 2 } default { Write-Output "G-Log could not read $LogPath (exit code $($run.ExitCode)): $($run.Error)" exit 1 } }