Sending Emails in Loop

Hello Everyone,

I have a script that works on some systems but not others, it seems.

It scans a log file and if it finds the key phrase it will email. This job is run by task scheduler on the hour.
Now what’s happening is that it’s sending out this email every hour.

I thought I created a foolproof plan but perhaps not?

Let me know your thoughts.

Dates take this form: [2017-09-28 13:24:59,405]

function Get-CertErr {
    $reviewer = Get-WmiObject win32_groupuser |
        Where-Object { $_.GroupComponent -match 'user' } |
            ForEach-Object {[wmi]$_.PartComponent } |
                Where-Object {$_.Name -notmatch '.*test.*|to_be_default|.*User.*|INTERACTIVE'} |
                    ForEach-Object { $_.Name }

    $logs = Get-ChildItem -Path "C:\pathtolog\important.log*" | Where-Object { $_.LastWriteTime -ge ((Get-Date).AddDays(-40))}
    $var = Get-Content $logs -EA Ignore | Select-String -SimpleMatch "certificate" -Context 0,10 | Sort-Object Date | Select-Object -Last 1 

foreach ($v in $var){
    $date,$message = $v.Line.Split("]",2)
    $date = $date -replace ('\[','')
        $alert1 = "$([datetime]::ParseExact($date,"yyyy-MM-dd HH:mm:ss,fff",$null))"
        $alert2 = "$message"
        $alert3 = "$v"
        }

if ($v){
    if(!(Test-Path "C:\Users\Administrator\Desktop\Monitoring")){
        New-Item -ItemType directory -Path "C:\Users\Administrator\Desktop\Monitoring"
        }
    if(!(Test-Path "C:\Users\Administrator\Desktop\Monitoring\Cert.txt")){
        New-Item -ItemType file -Path "C:\Users\Administrator\Desktop\Monitoring\Cert.txt"
        }

$oc = Get-ChildItem C:\Users\Administrator\Desktop\Monitoring\Cert.txt
$oldcerts = Get-Content $oc | Sort-Object Date | Select-Object -Last 1 
 if ($date -gt $oldcerts){
 Send-Email -To "thatonegroup@Contoso.com" -From "$reviewer@$env:COMPUTERNAME" -Subject "Certificate FAILURE on $reviewer@$env:COMPUTERNAME" -Body $MessageBody -Priority Normal -SMTPServer "relay.contoso.com"
    }
  }
}

 Function Send-Email ($To, $Cc, $Bcc, $From, $Subject, $Body, $Priority, $SMTPServer, $Attachments){
 $HTML = @"




body {background-color: lightblue;}
h1 {background-color: black;color: white;text-align: center;}
h2 {background-color:lightGrey;}
p {font-family: verdana;font-size: 12px;}
p.ridge {border-style: ridge;}



 Certificate Failure 
 Failure Date: $(Get-date $alert1 -Format F)
$($alert3|Out-String)


 Reviewer: $reviewer

 Workstation: $env:COMPUTERNAME


"@

        $EmailParams = @{
             To          = $To
             Cc          = $Cc
             Bcc         = $Bcc
             From        = $From
             Subject     = $Subject
             Body        = $HTML
             BodyAsHtml  = $True
             Priority    = $Priority
             SMTPServer  = $SMTPServer
             Attachments = $Attachments
             ErrorAction = 'Stop'
        }

        $list = New-Object System.Collections.ArrayList

        foreach ($h in $EmailParams.Keys) {
            if ($($EmailParams.Item($h)) -eq $null) {
                $null = $list.Add($h)
            }
        }

        foreach ($h in $list) {
            $EmailParams.Remove($h)
        }

        Try { 
            Send-MailMessage @EmailParams;
            Write-Verbose "Send-Mail: Sending mail to: $To";
            If ($? -eq $true){
            $date | Out-File -FilePath "C:\Users\Administrator\Desktop\Monitoring\Cert.txt" -Append -NoClobber -Force
            }
        }
        Catch { 
            "Failed to send email to $($To) due to: $_"  
        }
        }
Get-CertErr

I’m not quite following either the intent or what’s wrong… can you help me zero in on what you’re asking?

Sorry if I was unclear Don.

The structure of this script is that it pulls content from a log scrape.
If it finds errors it picks the chronologically last occurrence.
Now it will separate the error into a datetime and the message.

Next it compares the date against a file on the admin desktop
If the file contains the date already, the script ends.
If not, the script will email the results of the log scan.
On successful email of the new error the script writes the date to the log.

It’s a simple flag file system I put together because we weren’t getting notices from non-domain systems and this was the best idea I could come up with to ensure the systems would keep notifying us even if email failures occurred.

I thought it was foolproof.

The thing is… this works without issue on our 64 bit systems but from time to time will go haywire on our 32bit systems.
It even works reliably on many of our 32bit systems as well…

When it goes haywire the date of the occurrence may not be the most recent date, the admin desktop file will have multiples of the same date, and they won’t be in order. altho they have a general progression towards chronological order.

Heres’s a bad log:

2017-08-27 11:18:27,601
2017-09-14 17:24:03,965
2017-08-27 11:18:27,601
2017-08-27 11:18:27,601
2017-08-27 11:18:27,601
2017-09-14 17:24:03,965
2017-09-16 14:45:16,018
2017-09-24 12:30:54,654
2017-09-14 17:24:03,965
2017-09-16 14:45:16,018
2017-09-24 12:30:54,654
2017-09-27 03:00:25,300
2017-10-03 00:30:48,881
2017-10-03 01:46:44,194
2017-10-03 03:45:01,107
2017-09-16 14:45:16,018
2017-10-03 09:13:03,693

I’m still working on learning hashtables and key value pairs

My goal for this was to get a list of log files lastwritetime 40 days back.
Sort that list by newest
Find matches / sort matches by newest
get timestamp on match
compare timestamp to flag file who’s purpose is to notify of ‘cert’ problems
email new occurrence events

This is the fix that solved everything:

function Get-CertErr {
    $oc = Get-ChildItem C:\Users\Administrator\Desktop\Monitoring\Cert.txt
    $reviewer = Get-WmiObject win32_groupuser |
        Where-Object { $_.GroupComponent -match 'user' } |
            ForEach-Object {[wmi]$_.PartComponent } |
                Where-Object {$_.Name -notmatch '.*test.*|to_be_default|.*User.*|INTERACTIVE'} |
                    ForEach-Object { $_.Name }

    $logs = Get-ChildItem -Path "C:\pathtolog\important.log*" | Where-Object { $_.LastWriteTime -ge ((Get-Date).AddDays(-40))} | Sort-Object LastWriteTime
    $var = Get-Content $logs -EA Ignore | Select-String -SimpleMatch "certificate" -Context 0,10    
    
    $sorted = @{}
    $datelist = @{}
    $errorlist = @{}
    
    if ($var){
        $sorted = foreach ($v in $var){
            $v -replace('> \[','') | Sort-Object Date
            }
        $datelist = $sorted | foreach {
            $items = $_.split(']',2)
            $items[0]
            }
        $errorlist = $sorted | foreach {
            $items = $_.split(']',2)
            $items[1]
            }

        $alert1 = [DateTime]::ParseExact($datelist[-1],'yyyy-MM-dd HH:mm:ss,fff', $null)
        $alert2 = $errorlist[-1]
        $alert3 = "$alert1 $alert2"

        if(!(Test-Path "C:\Users\Administrator\Desktop\Monitoring")){
            New-Item -ItemType directory -Path "C:\Users\Administrator\Desktop\Monitoring"
            }
        if(!(Test-Path "C:\Users\Administrator\Desktop\Monitoring\Cert.txt")){
            New-Item -ItemType file -Path "C:\Users\Administrator\Desktop\Monitoring\Cert.txt"
            }
    
        $oldcerts = get-content $oc
        $newsort = $oldcerts | Sort-Object { [DateTime]::ParseExact($_, 'yyyy-MM-dd HH:mm:ss,fff', $null) } | Select-Object -Last 1
        $onrecord = [DateTime]::ParseExact($newsort,'yyyy-MM-dd HH:mm:ss,fff', $null)

        if ($alert1 -gt $onrecord){
            Send-Email -To "thatonegroup@Contoso.com" -From "$reviewer@$env:COMPUTERNAME" -Subject "Certificate FAILURE on $reviewer@$env:COMPUTERNAME" -Body $MessageBody -Priority Normal -SMTPServer "relay.contoso.com"
            }
    }
    Filter-Certfile $oldcerts $oc
}

 Function Send-Email ($To, $Cc, $Bcc, $From, $Subject, $Body, $Priority, $SMTPServer, $Attachments){
 $HTML = @"




body {background-color: lightblue;}
h1 {background-color: black;color: white;text-align: center;}
h2 {background-color:lightGrey;}
p {font-family: verdana;font-size: 12px;}
p.ridge {border-style: ridge;}



 Certificate Failure 
 Failure Date: $(Get-date $alert1 -Format F)
$($alert3|Out-String)


 Reviewer: $reviewer

 Workstation: $env:COMPUTERNAME


"@

        $EmailParams = @{
             To          = $To
             Cc          = $Cc
             Bcc         = $Bcc
             From        = $From
             Subject     = $Subject
             Body        = $HTML
             BodyAsHtml  = $True
             Priority    = $Priority
             SMTPServer  = $SMTPServer
             Attachments = $Attachments
             ErrorAction = 'Stop'
        }

        $list = New-Object System.Collections.ArrayList

        foreach ($h in $EmailParams.Keys) {
            if ($($EmailParams.Item($h)) -eq $null) {
                $null = $list.Add($h)
            }
        }

        foreach ($h in $list) {
            $EmailParams.Remove($h)
        }

        Try { 
            Send-MailMessage @EmailParams;
            Write-Verbose "Send-Mail: Sending mail to: $To";
            If ($? -eq $true){
            Get-date $alert1 -Format 'yyyy-MM-dd HH:mm:ss,fff' | Out-File -FilePath "C:\Users\Administrator\Desktop\Monitoring\Cert.txt" -Append -NoClobber -Force
            }
        }
        Catch { 
            "Failed to send email to $($To) due to: $_"  
        }
        }

function Filter-Certfile {
    [cmdletbinding()]
    param(
        [Parameter(ValueFromPipelineByPropertyName=$true)]
        $oldcerts,
        [Parameter(ValueFromPipelineByPropertyName=$true)]
        $oc
        )

    $redsort = $oldcerts | Sort-Object { [DateTime]::ParseExact($_, 'yyyy-MM-dd HH:mm:ss,fff', $null) } | Select-Object -Unique
    $Compare = Compare-Object $oldcerts $redsort

    if ($Compare){
        Remove-Item $oc -Force
	    New-Item -ItemType File -Path "C:\Users\Administrator\Desktop\Monitoring\Cert.txt"
	    $redsort | Out-File -FilePath "C:\Users\Administrator\Desktop\Monitoring\Cert.txt" -Append -NoClobber -Force
	}
}

Get-CertErr