PSADHealth.psm1

function New-SlackPost {
    param ($issue)
    
    $payload = @{
        "channel" = "#psmonitor";
        "text" = "$issue";
        "icon_emoji" = ":bomb:";
        "username" = "PSMonitor";
    }

    Write-Verbose "Sending Slack Message"
    
    $slackWebRequest = @{
        Uri = "https://hooks.slack.com/services/$SlackToken"
        Method = "POST"
        Body = (ConvertTo-Json -Compress -InputObject $payload)
    }

    Invoke-WebRequest @slackWebRequest    

}
function Send-AlertCleared {
    Param($InError)
    Write-Verbose "Sending Email"
    Write-Verbose "Output is -- $InError"
    
    #Mail Server Config
    $NBN = (Get-ADDomain).NetBIOSName
    $Domain = (Get-ADDomain).DNSRoot
    $smtpServer = $Configuration.SMTPServer
    $smtp = new-object Net.Mail.SmtpClient($smtpServer)
    $msg = new-object Net.Mail.MailMessage

    #Send to list:
    $emailCount = ($Configuration.Email).Count
    If ($emailCount -gt 0){
        $Emails = $Configuration.Email
        foreach ($target in $Emails){
        Write-Verbose "email will be sent to $target"
        $msg.To.Add("$target")
        }
    }
    Else{
        Write-Verbose "No email addresses defined"
        Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17030 -EntryType Error -message "ALERT - No email addresses defined. Alert email can't be sent!" -category "17030"
    }
    #Message:
    $msg.From = "ADInternalTimeSync-$NBN@$Domain"
    $msg.ReplyTo = "ADInternalTimeSync-$NBN@$Domain"
    $msg.subject = "$NBN AD Internal Time Sync - Alert Cleared!"
    $msg.body = @"
        The previous Internal AD Time Sync alert has now cleared.
 
        Thanks.
"@

    #Send it
    $smtp.Send($msg)
}
function Send-Mail {
    Param($emailOutput)
    Write-Verbose "Sending Email"
    Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17034 -EntryType Information -message "ALERT Email Sent" -category "17034"
    Write-Verbose "Output is -- $emailOutput"
    
    #Mail Server Config
    $NBN = (Get-ADDomain).NetBIOSName
    $Domain = (Get-ADDomain).DNSRoot
    $smtpServer = $Configuration.SMTPServer
    $smtp = new-object Net.Mail.SmtpClient($smtpServer)
    $msg = new-object Net.Mail.MailMessage

    #Send to list:
    $emailCount = ($Configuration.Email).Count

    If ($emailCount -gt 0){
        $Emails = $Configuration.Email
        foreach ($target in $Emails){
        Write-Verbose "email will be sent to $target"
        $msg.To.Add("$target")
        }
    }
    
    Else{
        Write-Verbose "No email addresses defined"
        Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17030 -EntryType Error -message "ALERT - No email addresses defined. Alert email can't be sent!" -category "17030"
    }
    
    #Message:
    $msg.From = "ADInternalTimeSync-$NBN@$Domain"
    $msg.ReplyTo = "ADInternalTimeSync-$NBN@$Domain"
    $msg.subject = "$NBN AD Internal Time Sync Alert!"
    $msg.body = @"
        Time of Event: $((get-date))`r`n $emailOutput
        See the following support article $SupportArticle
"@


    #Send it
    $smtp.Send($msg)
}

function Copy-Files ($scriptToDeploy)
{
    $targets = Get-Content "C:\Scripts\RemotePSMonitorServers.txt"
    
    foreach ($Server in $Targets)
    {
        Write-output "Copying to $Server..."
        Copy-Item  $scriptToDeploy -Destination "\\$Server\Scripts\"
    }
    
}


<# Comment out to test module loading.
$scriptToDeploy = "C:\Scripts\Test-ADReplication.ps1"
Copy-Files $scriptToDeploy
 
$scriptToDeploy = "C:\Scripts\ADConfig.json"
Copy-Files $scriptToDeploy
 
$scriptToDeploy = "C:\Scripts\Test-ADLastBackupDate.ps1"
Copy-Files $scriptToDeploy
 
$scriptToDeploy = "C:\Scripts\Test-ADObjectReplication.ps1"
Copy-Files $scriptToDeploy
 
$scriptToDeploy = "C:\Scripts\Test-ADTimeSync.ps1"
Copy-Files $scriptToDeploy
 
$scriptToDeploy = "C:\Scripts\Test-ADTimeSyncToExternalNTP.ps1"
Copy-Files $scriptToDeploy
 
$scriptToDeploy = "C:\Scripts\Test-SYSVOL-Replication.ps1"
Copy-Files $scriptToDeploy
 
#>

function Get-ADConfig {
    <#
        .SYNOPSIS
        Converts json config data into usable powershell object
 
        .PARAMETER Configuration
 
        Location of the json file which hold module configuration data
        .EXAMPLE
 
        Get-ADConfig "C:\configs\ADConfig.json"
 
 
    #>

    [cmdletBinding()]
    [Alias('Get-ADHealthConfig')]
    Param(
        [Parameter(Position=0)]
        [ValidateScript({ Test-Path $_})]
        [String]
        $ConfigurationFile = "$PSScriptRoot\Config\ADConfig.json"
    )

    begin {}

    process {

        $Global:Configuration = Get-Content $ConfigurationFile | ConvertFrom-JSON

        $Configuration
    }

    end {}

}
function Get-ADLastBackupDate {
    [CmdletBinding()]
    Param()
    <#
    .SYNOPSIS
    Check AD Last Backup Date
     
    .DESCRIPTION
    This script Checks AD for the last backup date
 
    .EXAMPLE
    Run as a scheduled task. Use Event Log consolidation tools to pull and alert on issues found.
 
    .EXAMPLE
    Run in verbose mode if you want on-screen feedback for testing
    
    .NOTES
    Authors: Mike Kanakos, Greg Onstot
    Version: 0.6.3
    Version Date: 04/19/2019
     
    Event Source 'PSMonitor' will be created
 
    EventID Definition:
    17050 - Failure
    17051 - Beginning of test
    17052 - Successful Test Result
    17053 - End of test
    17054 - Alert Email Sent
    #>


    Begin {
        Import-Module activedirectory

        $null = Get-ADConfig

        $SupportArticle = $Configuration.SupportArticle

        if (![System.Diagnostics.EventLog]::SourceExists("PSMonitor")) {
            write-verbose "Adding Event Source."
            New-EventLog -LogName Application -Source "PSMonitor"
        }#end if

        Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17051 -EntryType Information -message "START of AD Backup Check ." -category "17051"
        
        $Domain = (Get-ADDomain).DNSRoot
        $Regex = '\d\d\d\d-\d\d-\d\d'
        $CurrentDate = Get-Date
        $MaxDaysSinceBackup = $Configuration.MaxDaysSinceBackup
        
    }#End Begin

    Process {
        #get the date of last backup from repadmin command using regex
        $LastBackup = (repadmin /showbackup $Domain | Select-String $Regex |ForEach-Object { $_.Matches } | ForEach-Object { $_.Value } )[0]
        #Compare the last backup date to today's date
        $Result = (New-TimeSpan -Start $LastBackup -End $CurrentDate).Days
        
        Write-Verbose "Last Active Directory backup occurred on $LastBackup! $Result days is less than the alert criteria of $MaxDaysSinceBackup day."
                        
        #Test if result is greater than max allowed days without backup
        If ($Result -gt $MaxDaysSinceBackup) {
            
            Write-Verbose "Last Active Directory backup occurred on $LastBackup! $Result days is higher than the alert criteria of $MaxDaysSinceBackup day."
            
            $emailOutput = "Last Active Directory backup occurred on $LastBackup! $Result days is higher than the alert criteria of $MaxDaysSinceBackup day."
            
            Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17050 -EntryType Warning -message "ALERT - AD Backup is not current. $emailOutput" -category "17050"
            
            $global:CurrentFailure = $true

            $mailParams = @{
                To = $Configuration.MailTo
                From = $Configuration.MailFrom
                SmtpServer = $Configuration.SmtpServer
                Subject = "AD Backup Check Alert! Backup is $Result days old"
                Body = $emailOutput
                BodyAsHtml = $true
          }

          Send-MailMessage @mailParams
          #Write-Verbose "Sending Slack Alert"
          #New-SlackPost "Alert - AD Last Backup is $Result days old"
        }else {
            Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17052 -EntryType Information -message "SUCCESS - Last Active Directory backup occurred on $LastBackup! $Result days is less than the alert criteria of $MaxDaysSinceBackup day." -category "17052"
        }#end else
        
    
    }#End Process
    
    End {
        
        Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17053 -EntryType Information -message "END of AD Backup Check ." -category "17053"
        
        If (!$CurrentFailure){
            Write-Verbose "No Issues found in this run"
            $InError = Get-EventLog application -After (Get-Date).AddHours(-24) | where {($_.InstanceID -Match "17050")} 
            
            If ($InError) {
                Write-Verbose "Previous Errors Seen"
                #Previous run had an alert
                #No errors foun during this test so send email that the previous error(s) have cleared
                $alertclearedParams = @{
                    To = $Configuration.MailTo
                    From = $Configuration.MailFrom
                    SmtpServer = $Configuration.SmtpServer
                    Subject = "AD Internal Time Sync - Alert Cleared!"
                    Body = "The previous Internal AD Time Sync alert has now cleared."
                    BodyAsHtml = $true
              }
    
              Send-MailMessage @alertclearedParams
              #Write-Verbose "Sending Slack Message - AD Backup Alert Cleared"
              #New-SlackPost "The previous alert, for AD Last Backup has cleared."
                #Write-Output $InError
            }#End if
        
        }#End if

    }#End End

}#End Function
Function Get-DCDiskspace {
      [cmdletBinding()]
      Param(
            [Parameter(Mandatory,Position=0)]
            [String]
            $DriveLetter
      )
      
      begin {
            Import-Module ActiveDirectory
            #Creates a global $configuration variable
            $null = Get-ADConfig
      }

      process {
            $DClist = (get-adgroupmember "Domain Controllers").name
            $FreeDiskThreshold = $Configuration.FreeDiskThreshold

            ForEach ($server in $DClist){

                  $disk = Get-WmiObject Win32_LogicalDisk -Filter "DriveType=3" -ComputerName $server | Where-Object { $_.DeviceId -eq $DriveLetter}
                  $Size = (($disk | Measure-Object -Property Size -Sum).sum/1gb)
                  $FreeSpace = (($disk | Measure-Object -Property FreeSpace -Sum).sum/1gb)
                  $freepercent = [math]::round(($FreeSpace / $size) * 100,0)
                  $Diskinfo = [PSCustomObject]@{
                        Drive = $disk.Name
                        "Total Disk Size (GB)" = [math]::round($size,2)
                        "Free Disk Size (GB)" = [math]::round($FreeSpace,2)
                        "Percent Free (%)" = $freepercent
                  } #End $DiskInfo Calculations
            
            If ($Diskinfo.'Percent Free (%)' -lt $FreeDiskThreshold){
                  $Subject = "Low Disk Space: Server $Server"
                  $EmailBody = @"
             
             
            Server named <font color="Red"><b> $Server </b></font> is running low on disk space on drive C:!
            <br/>
            $($Diskinfo | ConvertTo-Html -Fragment)
            <br/>
            Time of Event: <font color="Red"><b>"""$((get-date))"""</b></font><br/>
            <br/>
            THIS EMAIL WAS AUTO-GENERATED. PLEASE DO NOT REPLY TO THIS EMAIL.
"@


                  $mailParams = @{
                        To = $Configuration.MailTo
                        From = $Configuration.MailFrom
                        SmtpServer = $Configuration.SmtpServer
                        Subject = $Subject
                        Body = $EmailBody
                        BodyAsHtml = $true
                  }
                  Send-MailMessage @mailParams
            
            } #End If


            } # End ForEach

      }

      end {}

}

function Set-PSADHealthConfig
{
    <#
        .SYNOPSIS
        Sets the configuration data for this module
 
        .PARAMETER PSADHealthConfigPath
 
        The filesystem location to store configuration file data.
 
        .PARAMETER SMTPServer
 
        The smtp server this module will use for reports.
 
        .EXAMPLE
        Set-PSADHealthConfig -SMTPServer email.company.com
 
        .EXAMPLE
        Set-PSADHealthConfig -MailFrom admonitor@foobar.come -MailTo directoryadmins@foobar.com
 
        .EXAMPLE
        Set-PSADHealthConfig -MaxDaysSinceBackup 12
 
 
    #>


    [cmdletBinding()]
    Param(

        [Parameter(Position=0)]
        $PSADHealthConfigPath = "$(Split-Path $PSScriptRoot)\Config\ADConfig.json",

        [Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)]
        [string]
        $SMTPServer = "mail.server.fqdn",

        [Parameter()]
        [String]
        $MailFrom,

        [Parameter()]
        [String[]]
        $MailTo,

        [Parameter()]
        [String]
        $MaxDaysSinceBackup,

        [Parameter()]
        [Int]
        $MaxIntTimeDrift,

        [Parameter()]
        [Int]
        $MaxExtTimeDrift,

        [Parameter()]
        [string]
        $ExternalTimeServer,

        [Parameter()]
        [Int]
        $MaxObjectReplCycles,

        [Parameter()]
        [Int]
        $MaxSysvolReplCycles,

        [Parameter()]
        [String]
        $SupportArticleUrl,

        [Parameter()]
        [String]
        $SlackToken
    )

    
    $config = Get-ADConfig -ConfigurationFile $PSADHealthConfigPath
    
    Switch($PSBoundParameters.Keys){
        'SMTPServer' {
            $config.smtpserver = $SMTPServer
         }
        'MailFrom' {
            $config.MailFrom = $MailFrom
        }
        'MailTo' {
            $config.MailTo = $MailTo
        }
        'MaxDaysSinceBackup' {
            $config.MaxDaysSinceBackup = $MaxDaysSinceBackup
        }
        'MaxIntTimeDrift' {
            $config.MaxIntTimeDrift = $MaxIntTimeDrift
        }
        'MaxExtTimeDrift' {
            $config.MaxExtTimeDrift = $MaxExtTimeDrift
        }
        'ExternalTimeServer' {
            $config.ExternalTimeSvr = $ExternalTimeServer
        }
        'MaxObjectReplCycles' {
            $config.MaxObjectReplCycles = $MaxObjectReplCycles
        }
        'MaxSysvolReplCycles' {
            $config.MaxSysvolReplCycles = $MaxSysvolReplCycles
        }
        'SupportArticleUrl' {
            $config.SupportArticle = $SupportArticleUrl
        }
        'SlackToken' {
            $config.SlackToken = $SlackToken
        }

    }
    
    $config | ConvertTo-Json | Set-Content $PSADHealthConfigPath
    
}
function Test-ADConfigMailer {


    begin { $null = Get-ADConfig }


    process {

        $mailParams = @{
            To = $Configuration.MailTo
            From = $Configuration.MailFrom
            SmtpServer = $Configuration.SmtpServer
            Subject = "Testing PSADHealth Mail Capability"
            Body = "If you can read this, your scripts can alert via email!"
            BodyAsHtml = $true
        }

        Send-MailMessage @mailParams
    }
    
}
function Test-ADObjectReplication {
    [CmdletBinding()]
    Param()
    <#
    .SYNOPSIS
    Monitor AD Object Replication
     
    .DESCRIPTION
    Each run of the script creates a unique test object in the domain, and tracks it's replication to all other DCs in the domain.
    By default it will query the DCs for about 60 minutes. If after 60 loops the object hasn't repliated the test will terminate and create an alert.
 
    .EXAMPLE
    Run as a scheduled task. Use Event Log consolidation tools to pull and alert on issues found.
 
    .EXAMPLE
    Run in verbose mode if you want on-screen feedback for testing
    
    .NOTES
    Author Greg Onstot
    Version: 0.6.3
    Version Date: 04/18/2019
 
    This script must be run from a Win10, or Server 2016 system. It can target older OS Versions.
 
    Event Source 'PSMonitor' will be created
 
    EventID Definition:
    17010 - Failure
    17011 - Cycle Count
    17012 - Test Object not yet on DC
    17013 - Test Object on DC
    17014 - Tests didn't complete in alloted time span
    17015 - Job output
    17016 - Test Object Created
    17017 - Test Object Deleted
    17018 - 1 minute Sleep
    17019 - Posible old object detected
    #>


    Begin {
        Import-Module activedirectory
        $NBN = (Get-ADDomain).NetBIOSName
        $Domain = (Get-ADDomain).DNSRoot
        $domainname = (Get-ADDomain).dnsroot
        $null = Get-ADConfig
        $SupportArticle = $.SupportArticle
        if (![System.Diagnostics.EventLog]::SourceExists("PSMonitor")) {
            write-verbose "Adding Event Source."
            New-EventLog -LogName Application -Source "PSMonitor"
        }
        $continue = $true
        $CurrentFailure = $false
        $existingObj = $null
        $DCs = (Get-ADDomainController -Filter *).Name 
        $SourceSystem = (Get-ADDomain).pdcemulator
        [int]$MaxCycles = $Configuration.MaxObjectReplCycles
    }

    Process {
        if (Test-NetConnection $SourceSystem -Port 445 -InformationLevel Quiet) {
            Write-Verbose 'PDCE is online'
            $tempObjectPath = (Get-ADDomain).computersContainer
            $existingObj = Get-ADComputer -filter 'name -like "ADRT-*"' -prop * -SearchBase "$tempObjectPath" |Select-Object -ExpandProperty Name
            If ($existingObj){
                Write-Verbose "Warning - Cleanup of a old object(s) may not have occured. Object(s) starting with 'ADRT-' exists in $tempObjectPath : $existingObj - Please review, and cleanup if required."
                Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17019 -EntryType Warning -message "WARNING - AD Object Replication Cleanup of old object(s) may not have occured. Object(s) starting with 'ADRT-' exists in $tempObjectPath : $existingObj. Please review, and cleanup if required." -category "17019"
                #Write-Verbose "Sending Slack Alert"
                #New-SlackPost "Alert - Cleanup of a old object(s) may not have occured. Object(s) starting with 'ADRT-' exists in $tempObjectPath : $existingObj - Please review, and cleanup if required."
            }

            $site = (Get-ADDomainController $SourceSystem).site
            $startDateTime = Get-Date
            [string]$tempObjectName = "ADRT-" + (Get-Date -f yyyyMMddHHmmss)
            
            New-ADComputer -Name "$tempObjectName" -samAccountName "$tempObjectName" -Path "$tempObjectPath" -Server $SourceSystem -Enabled $False
            
            Write-Verbose "Object created for tracking - $tempObjectName in $site"
            Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17016 -EntryType Information -message "CREATED AD Object Replication Test object - $tempObjectName - has been created on $SourceSystem in site - $site" -category "17016"
            $i = 0
        }
        else {
            Write-Verbose 'PDCE is offline. You should really resolve that before continuing.'
            Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17010 -EntryType Error -message "FAILURE AD Object Replication - Failed to connect to PDCE - $SourceSystem in site - $site" -category "17010"
            $Alert = "In $domainname Failed to connect to PDCE - $dc in site - $site. Test stopping! See the following support article $SupportArticle"
            $CurrentFailure = $true
            Send-Mail $Alert
            #Write-Verbose "Sending Slack Alert"
            #New-SlackPost "Alert - PDCE is Offline in $domainname, AD Object Replication test has exited."
            Exit
        }

        While ($continue) {
            $i++
            Write-Verbose 'Sleeping for 1 minute.'
            Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17018 -EntryType Information -message "SLEEPING AD Object Replication for 1 minute" -category "17018"
            Start-Sleep 60
            $replicated = $true
            Write-Verbose "Cycle - $i"
            Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17011 -EntryType Information -message "CHECKING AD Object Replication ADRepl Cycle $i" -category "17011"
        
            Foreach ($dc in $DCs) {
                $site = (Get-ADDomainController $dc).site
                if (Test-NetConnection $dc -Port 445 -InformationLevel Quiet) {
                    Write-Verbose "Online - $dc"
                    $connectionResult = "SUCCESS"
                }
                else {
                    Write-Verbose "!!!!!OFFLINE - $dc !!!!!"
                    $connectionResult = "FAILURE"
                    Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17010 -EntryType Error -message "FAILURE AD Object Replication failed to connect to DC - $dc in site - $site" -category "17010"
                    
                    $CurrentFailure = $true
                    if ($i -eq 10){
                        $Alert = "In $domainname Failed to connect to DC - $dc in site - $site. See the following support article $SupportArticle"
                        #If we get a failure on the 10th run, send an email for additional visibility, but not spam on every pass if a server or site is offline.
                        Send-Mail $Alert
                        #Write-Verbose "Sending Slack Alert"
                        #New-SlackPost "Alert - In $domainname Failed to connect to DC - $dc in site - $site."
                    }
                    
                }

                # If The Connection To The DC Is Successful
                If ($connectionResult -eq "SUCCESS") {
                    Try {    
                        $Milliseconds = (Measure-Command {$Query = Get-ADComputer $tempObjectName -Server $dc | select Name}).TotalMilliseconds
                        Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17013 -EntryType information -message "SUCCESS AD Object Replication Test object replicated to - $dc in site - $site - in $Milliseconds ms. " -category "17013"
                        write-Verbose "SUCCESS! - Replicated - $($query.Name) - $($dc) - $site - $Milliseconds"
                    }
                    Catch {
                        write-Verbose "PENDING! - Test object $tempObjectName does not exist on $dc in $site."
                        Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17012 -EntryType information -message "PENDING AD Object Replication Test object pending replication to - $dc in site - $site. " -category "17012"
                        $replicated = $false
                    }    
                }
                
                # If The Connection To The DC Is Unsuccessful
                If ($connectionResult -eq "FAILURE") {
                    Write-Verbose " - Unable To Connect To DC/GC And Check For The Temp Object..."
                    Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17010 -EntryType Error -message "FAILURE AD Object Replication failed to connect to DC - $dc in site - $site" -category "17010"
                    $Alert = "In $domainname Failed to connect to DC - $dc in site - $site. See the following support article $SupportArticle"
                    $CurrentFailure = $true
                    Send-Mail $Alert
                }
            }

            If ($replicated) {
                $continue = $false
            } 

            If ($i -gt $MaxCycles) {
                $continue = $false
                #gather event history to see which DC did, and which did not, get the replication
                $list = Get-EventLog application -After (Get-Date).AddHours(-2) | where {($_.InstanceID -Match "17012") -OR ($_.InstanceID -Match "17013") -OR ($_.InstanceID -Match "17016")} 
                $RelevantEvents = $list |Select InstanceID,Message |Out-String
                Write-Verbose "Cycle has run $i times, and replication hasn't finished. Need to generate an alert."
                Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17014 -EntryType Warning -message "INCOMPLETE AD Object Replication Test cycle has run $i times without the object succesfully replicating to all DCs" -category "17014"
                
                $Alert = "In $domainname - the AD Object Replication Test cycle has run $i times without the object succesfully replicating to all DCs.
                Please see the following support article $SupportArticle to help investigate
                 
                Recent history:
                $RelevantEvents
                "

                $CurrentFailure = $true
                Send-Mail $Alert
                #Write-Verbose "Sending Slack Alert"
                #$New-SlackPost "Alert - In $domainname - the AD Object Replication Test cycle has run $i times without the object succesfully replicating to all DCs."
            } 
        }
    }

    End {
        # Show The Start Time, The End Time And The Duration Of The Replication
        $endDateTime = Get-Date
        $duration = "{0:n2}" -f ($endDateTime.Subtract($startDateTime).TotalSeconds)
        $output = "`n Start Time......: $(Get-Date $startDateTime -format "yyyy-MM-dd HH:mm:ss")"
        $output = $output + "`n End Time........: $(Get-Date $endDateTime -format "yyyy-MM-dd HH:mm:ss")"
        $output = $output + "`n Duration........: $duration Seconds"
        Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17015 -EntryType Information -message "Test cycle has Ended - $output" -category "17015"
        
        Write-Verbose "`n Start Time......: $(Get-Date $startDateTime -format "yyyy-MM-dd HH:mm:ss")"
        Write-Verbose " End Time........: $(Get-Date $endDateTime -format "yyyy-MM-dd HH:mm:ss")"
        Write-Verbose " Duration........: $duration Seconds"
        
        # Delete The Temp Object On The RWDC
        Write-Verbose " Deleting AD Object File..."
        Remove-ADComputer $tempObjectName -Confirm:$False
        Write-Verbose " AD Object [$tempObjectName] Has Been Deleted."
        Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17017 -EntryType Information -message "DELETED AD Object Replication test object - $tempObjectName - has been deleted." -category "17017"

        If (!$CurrentFailure){
            Write-Verbose "No Issues found in this run"
            $InError = Get-EventLog application -After (Get-Date).AddHours(-2) | where {($_.InstanceID -Match "17010") -or ($_.InstanceID -Match "17014")} 
            If ($InError) {
                Write-Verbose "Previous Errors Seen"
                #Previous run had an alert
                #No errors foun during this test so send email that the previous error(s) have cleared
                Send-AlertCleared
                #Write-Verbose "Sending Slack Message - Alert Cleared"
                #New-SlackPost "The previous alert, for AD Object Replication, has cleared."
                #Write-Output $InError
            }#End if
        }#End if

    }
}
function Test-ADReplication {
    [CmdletBinding()]
    Param()
    <#
    .SYNOPSIS
    Monitor AD Object Replication
     
    .DESCRIPTION
    This script monitors DCs for Replication Failures
 
    .EXAMPLE
    Run as a scheduled task. Use Event Log consolidation tools to pull and alert on issues found.
 
    .EXAMPLE
    Run in verbose mode if you want on-screen feedback for testing
    
    .NOTES
    Authors: Mike Kanakos, Greg Onstot
    Version: 0.6.2
    Version Date: 04/18/2019
 
    Event Source 'PSMonitor' will be created
 
    EventID Definition:
    17020 - Failure
    17021 - Beginning of test
    17022 - Testing individual systems
    17023 - End of test
    17024 - Alert Email Sent
    #>


    Begin {
        Import-Module activedirectory
        $null = Get-ADConfig
        $SupportArticle = $Configuration.SupportArticle
        if (![System.Diagnostics.EventLog]::SourceExists("PSMonitor")) {
            write-verbose "Adding Event Source."
            New-EventLog -LogName Application -Source "PSMonitor"
        }
        #$DClist = (Get-ADGroupMember -Identity 'Domain Controllers').name #For RWDCs only, RODCs are not in this group.
        $DClist = (Get-ADDomainController -Filter *).name  # For ALL DCs
        Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17021 -EntryType Information -message "START AD Replication Test Cycle ." -category "17021"
    }#End Begin

    Process {
        Foreach ($server in $DClist) {
            Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17022 -EntryType Information -message "CHECKING AD Replication - Server - $server" -category "17022"
            Write-Verbose "TESTING - $server"
            $OutputDetails = $null
            $Result = (Get-ADReplicationFailure -Target $server).failurecount
            Write-Verbose "$server - $Result"
            $Details = Get-ADReplicationFailure -Target $server
            $errcount = $Details.FailureCount
            $name = $Details.server
            $Fail = $Details.FirstFailureTime
            $Partner = $Details.Partner
        
            If ($result -ne $null -and $Result -gt 1) {
                $OutputDetails = "ServerName: `r`n $name `r`n FailureCount: $errcount `r`n `r`n FirstFailureTime: `r`n $Fail `r`n `r`n Error with Partner: `r`n $Partner `r`n `r`n - See the following support article $SupportArticle"
                Write-Verbose "Failure - $OutputDetails"
                Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17020 -EntryType Warning -message "FAILURE AD Replicaion on $server - $OutputDetails ." -category "17020"
                $global:CurrentFailure = $true
                Send-Mail $OutputDetails
                #Write-Verbose "Sending Slack Alert"
                #New-SlackPost "Alert - FAILURE AD Replicaion on $server - $OutputDetails ."
            } #End if
        }#End Foreach
    }#End Process

    
    End {
        Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17023 -EntryType Information -message "END of AD Replication Test Cycle ." -category "17023"
        If (!$CurrentFailure){
            Write-Verbose "No Issues found in this run"
            $InError = Get-EventLog application -After (Get-Date).AddHours(-1) | where {($_.InstanceID -Match "17020")} 
            If ($InError.Count -gt 1) {
                Write-Verbose "Previous Errors Seen"
                #Previous run had an alert
                #No errors foun during this test so send email that the previous error(s) have cleared
                Send-AlertCleared
                #Write-Verbose "Sending Slack Message - Alert Cleared"
                #New-SlackPost "The previous alert, for AD Replication, has cleared."
                #Write-Output $InError
            }#End if
        }#End if
    }#End End
}#End Function
# Test-ADServices.ps1
function Test-ADServices {
    [cmdletBinding()]
    Param()

    begin {
        Import-Module ActiveDirectory
        #Creates a global $configuration variable
        $null = Get-ADConfig
    }

    process {
        $DClist = (get-adgroupmember "Domain Controllers").name
        $collection = @('ADWS',
                        'DHCPServer',
                        'DNS',
                        'DFS',
                        'DFSR',
                        'Eventlog',
                        'EventSystem',
                        'KDC',
                        'LanManWorkstation',
                        'LanManWorkstation',
                        'NetLogon',
                        'NTDS',
                        'RPCSS',
                        'SAMSS',
                        'W32Time')

        

        forEach ($server in $DClist){
            
            forEach ($service in $collection){
                try {
                   $s = Get-Service -Name $Service -Computername $server -ErrorAction Stop
                   $s
                }
                
                catch {
                    Out-Null
                }


                if($s.status -eq "Stopped"){


                    $Subject = "Windows Service: $($s.Displayname), is stopped"
                    
                    $EmailBody = @"
                                Service named <font color=Red><b>$s</b></font> is stopped!
                                Time of Event: <font color=Red><b>"""$((get-date))"""</b></font><br/>
                                <br/>
                                THIS EMAIL WAS AUTO-GENERATED. PLEASE DO NOT REPLY TO THIS EMAIL.
"@

                
                    $mailParams = @{
                        To = $Configuration.MailTo
                        From = $Configuration.MailFrom
                        SmtpServer = $Configuration.SmtpServer
                        Subject = $Subject
                        Body = $EmailBody
                        BodyAsHtml = $true
                    }

                    Send-MailMessage @mailParams
                
                } #End If

            } #Service Foreach
        
        } #DCList Foreach
    
    } #Process

} #function
Function Test-DCsOnline {
    [cmdletBinding()]
    Param()

    Begin {
        Import-Module ActiveDirectory
        #Creates a global $configuration variable
        $null = Get-ADConfig
    }
    
    Process {
        $DClist = (get-adgroupmember "Domain Controllers").name

        ForEach ($server in $DClist){

            if  ((!(Test-Connection -ComputerName $Server -quiet -count 4)))
            {
                $Subject = "Server $Server is offline"
                $EmailBody = @"
         
         
        Server named <font color="Red"><b> $Server </b></font> is offline!
        Time of Event: <font color="Red"><b> $((get-date))</b></font><br/>
        <br/>
        THIS EMAIL WAS AUTO-GENERATED. PLEASE DO NOT REPLY TO THIS EMAIL.
"@


                $mailParams = @{
                    To = $Configuration.MailTo
                    From = $Configuration.MailFrom
                    SmtpServer = $Configuration.SmtpServer
                    Subject = $Subject
                    Body = $EmailBody
                    BodyAsHtml = $true
                }
                Send-MailMessage @mailParams

            } #End if
        }#End Foreach
}
    End {}
}
# Test-ExternalDNSServers.ps1
Function Test-ExternalDNSServers {
    [cmdletBinding()]
    Param()

    begin {
        Import-Module ActiveDirectory
        #Creates a global $configuration variable
        $null = Get-ADConfig
    }

    process {
        $DClist = (get-adgroupmember "Domain Controllers").name
        $ExternalDNSServers = $Configuration.ExternalDNSServers 

        ForEach ($server in $DClist){

            ForEach ($DNSServer in $ExternalDNSServers) {
                
            if  ((!(Invoke-Command -ComputerName $server -ScriptBlock { Test-Connection $args[0] -Quiet -Count 1} -ArgumentList $DNSServer)))
            {
                    
                    $Subject = "External DNS $DNSServer is unreachable"
                    $EmailBody = @"
         
         
                    A Test connection from <font color="Red"><b> $Server </b></font> to $DNSServer was unsuccessful!
                    Time of Event: <font color="Red"><b> """$((get-date))"""</b></font><br/>
                    <br/>
                    THIS EMAIL WAS AUTO-GENERATED. PLEASE DO NOT REPLY TO THIS EMAIL.
"@

         
                    $mailParams = @{
                        To = $Configuration.MailTo
                        From = $Configuration.MailFrom
                        SmtpServer = $Configuration.SmtpServer
                        Subject = $Subject
                        Body = $EmailBody
                        BodyAsHtml = $true
                    }

                    Send-MailMessage @mailParams

                } #End if
            
            }# End Foreach (DCLIst)
        
        } # End ForEach (ExternalDNSServers)

    }

    end {}
}
function Test-ADExternalTimeSync {
    [CmdletBinding()]
    Param()
    <#
    .SYNOPSIS
    Monitor AD External Time Sync
     
    .DESCRIPTION
    This script monitors External NTP to the PDCE for Time Sync Issues
 
    .EXAMPLE
    Run as a scheduled task. Use Event Log consolidation tools to pull and alert on issues found.
 
    .EXAMPLE
    Run in verbose mode if you want on-screen feedback for testing
    
    .NOTES
    Authors: Mike Kanakos, Greg Onstot
    Version: 0.7.2
    Version Date: 4/18/2019
         
    Event Source 'PSMonitor' will be created
 
    EventID Definition:
    17040 - Failure
    17041 - Beginning of test
    17042 - Testing individual systems
    17043 - End of test
    17044 - Alert Email Sent
    17045 - Automated Repair Attempted
    #>


    Begin {
        Import-Module activedirectory
        $CurrentFailure = $null
        $null = Get-ADConfig
        if (![System.Diagnostics.EventLog]::SourceExists("PSMonitor")) {
            write-verbose "Adding Event Source."
            New-EventLog -LogName Application -Source "PSMonitor"
        }#end if

        #$DClist = (Get-ADGroupMember -Identity 'Domain Controllers').name #For RWDCs only, RODCs are not in this group.
        $PDCEmulator = (Get-ADDomainController -Discover -Service PrimaryDC).name
        $ExternalTimeSvr = $Configuration.ExternalTimeSvr
        $MaxTimeDrift = $Configuration.MaxExtTimeDrift
        Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17041 -EntryType Information -message "START of External Time Sync Test Cycle ." -category "17041"
    }#End Begin

    Process {
        
        $PDCeTime = ([WMI]'').ConvertToDateTime((Get-WmiObject -Class win32_operatingsystem -ComputerName $PDCEmulator).LocalDateTime)
        $ExternalTime = (w32tm /stripchart /dataonly /computer:$ExternalTimeSvr /samples:1)[-1].split("[")[0]
        $ExternalTimeOutput = [Regex]::Match($ExternalTime, "\d+\:\d+\:\d+").value
        $result = (New-TimeSpan -Start $ExternalTimeOutput -End $PDCeTime).Seconds
        
        $emailOutput = "$PDCEmulator - Offset: $result - Time:$PDCeTime - ReferenceTime: $ExternalTimeOutput `r`n "
        
        Write-Verbose "ServerName $PDCEmulator - Offset: $result - ExternalTime: $ExternalTimeOutput - PDCE Time: $PDCeTime"
        Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17042 -EntryType Information -message "CHECKING External Time Sync on Server - $PDCEmulator - $emailOutput" -category "17042"

        #If result is a negative number (ie -6 seconds) convert to positive number
        # for easy comparison
        If ($result -lt 0) { $result = $result * (-1)}
        #test if result is greater than max time drift
        If ($result -gt $MaxTimeDrift) {
            
            Write-Verbose "ALERT - Time drift above maximum allowed threshold on - $server - $emailOutput"
            Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17040 -EntryType Warning -message "FAILURE External time drift above maximum allowed on $emailOutput `r`n " -category "17040"
            
            #attempt to automatically fix the issue
            Invoke-Command -ComputerName $server -ScriptBlock { 'w32tm /resync' }
            Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17045 -EntryType Information -message "REPAIR External Time Sync Remediation was attempted `r`n " -category "17045"
            $CurrentFailure = $true
            
            
            $mailParams = @{
                To = $Configuration.MailTo
                From = $Configuration.MailFrom
                SmtpServer = $Configuration.SmtpServer
                Subject = $"AD External Time Sync Alert!"
                Body = $emailOutput
                BodyAsHtml = $true
            }

            Send-MailMessage @mailParams
            #Write-Verbose "Sending Slack Alert"
            #New-SlackPost "Alert - External Time drift above max threashold - $emailOutput"

        }#end if
        If (!$CurrentFailure) {
            Write-Verbose "No Issues found in this run"
            $InError = Get-EventLog application -After (Get-Date).AddHours(-24) | where {($_.InstanceID -Match "17040")} 
            $errtext = $InError |out-string
            If ($errtext -like "*$server*") {
                Write-Verbose "Previous Errors Seen"
                #Previous run had an alert
                #No errors foun during this test so send email that the previous error(s) have cleared
                
                
                
                $alertParams = @{

                    To = $Configuration.MailTo
                    From = $Configuration.MailFrom
                    SmtpServer = $Configuration.SmtpServer
                    Subject = "AD External Time Sync - Alert Cleared!"
                    Body = "The previous alert for AD External Time Sync has now cleared."
                    BodyAsHtml = $true

                }
                
                Send-MailMessage @alertParams
                #Write-Verbose "Sending Slack Message - Alert Cleared"
                #New-SlackPost "The previous alert, for AD External Time Sync, has cleared."
            
            }#End if
       
        }#End if
    }#End Process
    
    End {
        Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17043 -EntryType Information -message "END of External Time Sync Test Cycle ." -category "17043"
        
    }#End End
    
}#End Function
function Test-ADInternalTimeSync {
    [CmdletBinding()]
    Param()
    <#
    .SYNOPSIS
    Monitor AD Internal Time Sync
     
    .DESCRIPTION
    This script monitors DCs for Time Sync Issues
 
    .EXAMPLE
    Run as a scheduled task. Use Event Log consolidation tools to pull and alert on issues found.
 
    .EXAMPLE
    Run in verbose mode if you want on-screen feedback for testing
    
    .NOTES
    Authors: Mike Kanakos, Greg Onstot
    Version: 0.8.2
    Version Date: 4/18/2019
     
    Event Source 'PSMonitor' will be created
 
    EventID Definition:
    17030 - Failure
    17031 - Beginning of test
    17032 - Testing individual systems
    17033 - End of test
    17034 - Alert Email Sent
    17035 - Automated Repair Attempted
    #>


    Begin {
        Import-Module activedirectory
        $CurrentFailure = $null
        $null = Get-ADConfig
        $SupportArticle = $Configuration.SupportArticle
        $SlackToken = $Configuration.SlackToken
        if (!([System.Diagnostics.EventLog]::SourceExists("PSMonitor"))) {
            write-verbose "Adding Event Source."
            New-EventLog -LogName Application -Source "PSMonitor"
        }#end if
        $DClist = (Get-ADDomainController -Filter *).name  # For ALL DCs
        $PDCEmulator = (Get-ADDomainController -Discover -Service PrimaryDC).name
        $MaxTimeDrift = $Configuration.MaxIntTimeDrift

        $beginEventLog = @{
            LogName   = "Application"
            Source    = "PSMonitor"
            EventID   = 17031
            EntryType = "Information"
            Message   = "START of Internal Time Sync Test Cycle."
            Category  = "17031"
        }

        Write-eventlog  @beginEventLog

    }#End Begin

    Process {

        Foreach ($server in $DClist) {
            
            Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17032 -EntryType Information -message "CHECKING Internal Time Sync on Server - $server" -category "17032"
            Write-Verbose "CHECKING - $server"
            
            $OutputDetails = $null
            $Remotetime = ([WMI]'').ConvertToDateTime((Get-WmiObject -Class win32_operatingsystem -ComputerName $server).LocalDateTime)
            $Referencetime = ([WMI]'').ConvertToDateTime((Get-WmiObject -Class win32_operatingsystem -ComputerName $PDCEmulator).LocalDateTime)
            $result = (New-TimeSpan -Start $Referencetime -End $Remotetime).Seconds
            Write-Verbose "$server - Offset: $result - Time:$Remotetime - ReferenceTime: $Referencetime"
            
            #If result is a negative number (ie -6 seconds) convert to positive number
            # for easy comparison
            If ($result -lt 0) {
                 
                $result = $result * (-1)
            
            }
                
            #test if result is greater than max time drift
            If ($result -gt $MaxTimeDrift) {
                $emailOutput = "$server - Offset: $result - Time:$Remotetime - ReferenceTime: $Referencetime `r`n "
                Write-Verbose "ALERT - Time drift above maximum allowed threshold on - $server - $emailOutput"
                Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17030 -EntryType Warning -message "FAILURE Internal time drift above maximum allowed on $emailOutput `r`n " -category "17030"
                    
                #attempt to automatically fix the issue
                Invoke-Command -ComputerName $server -ScriptBlock { 'w32tm /resync' }
                Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17035 -EntryType Information -message "REPAIR Internal Time Sync remediation was attempted `r`n " -category "17035"
                CurrentFailure = $true
                Send-Mail $emailOutput
                Write-Verbose "Sending Slack Alert"
                New-SlackPost "Alert - Internal Time drift above max threashold - $emailOutput"
            }#end if
            If (!$CurrentFailure) {
                Write-Verbose "No Issues found in this run"
                $InError = Get-EventLog application -After (Get-Date).AddHours(-24) | where {($_.InstanceID -Match "17030")} 
                $errtext = $InError |out-string
                If ($errtext -like "*$server*") {
                    Write-Verbose "Previous Errors Seen"
                    #Previous run had an alert
                    #No errors foun during this test so send email that the previous error(s) have cleared
                    Send-AlertCleared
                    Write-Verbose "Sending Slack Message - Alert Cleared"
                    New-SlackPost "The previous alert, for AD Internal Time Sync, has cleared."
                    #Write-Output $InError
                }#End if

            }#End if

        }#End Foreach

    }#End Process
    
    End {
        Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17033 -EntryType Information -message "END of Internal Time Sync Test Cycle ." -category "17033"
    }#End End

}#End Function
Function Test-SRVRecords {

    [cmdletBinding()]
    Param()

    begin {
        Import-Module ActiveDirectory
        #Creates a global $configuration variable
        $null = Get-ADConfig
    }

    process {
        $DomainFQDN = (get-addomain).dnsroot
        $DCList = (get-adgroupmember "Domain Controllers").name
        $DCCount = (get-adgroupmember "Domain Controllers").count
        $PDCEmulator = (get-addomaincontroller -Discover -Service PrimaryDC).name
        $MSDCSZoneName = "_msdcs." + $DomainFQDN
        
        # $MSDCSZoneName = '_msdcs.bigfirm.biz'
        
        $DC_SRV_Record = '_ldap._tcp.dc'
        $GC_SRV_Record = '_ldap._tcp.gc'
        $KDC_SRV_Record = '_kerberos._tcp.dc'
        $PDC_SRV_Record = '_ldap._tcp.pdc'
        
        $DC_SRV_RecordCount = (@(Get-DnsServerResourceRecord -ZoneName $MSDCSZoneName -Name $DC_SRV_Record -RRType srv -ComputerName $PDCEmulator).count)
        $GC_SRV_RecordCount = (@(Get-DnsServerResourceRecord -ZoneName $MSDCSZoneName -Name $GC_SRV_Record -RRType srv -ComputerName $PDCEmulator).count)
        $KDC_SRV_RecordCount = (@(Get-DnsServerResourceRecord -ZoneName $MSDCSZoneName -Name $KDC_SRV_Record -RRType srv -ComputerName $PDCEmulator).count)
        
        $PDC_SRV_RecordCount = (@(Get-DnsServerResourceRecord -ZoneName $MSDCSZoneName -Name $PDC_SRV_Record -RRType srv -ComputerName $PDCEmulator).Count)

        $DCHash = @{}
        $DCHash.add($dc_SRV_Record,$dc_SRV_RecordCount)
        
        $GCHash = @{}
        $GCHash.add($gc_SRV_Record,$gc_SRV_RecordCount)
        
        $KDCHash = @{}
        $KDCHash.add($kdc_SRV_Record,$kdc_SRV_RecordCount)



        $Records = @($DCHash, $GCHash, $KDCHash)
        ForEach ($Record in $Records){
            # If ($Record -ne $DCCount){
            If ($record.values -ne $DCCount){
                $Subject = "There is an SRV record missing from DNS"
                $EmailBody = @"
         
         
        The number of records in the <font color="Red"><b> $($Record.keys) </b></font> zone in DNS does not match the number of Domain Controllers in Active Directory. Please check DNS for missing SRV records.
         
        Time of Event: <font color="Red"><b> $((get-date))</b></font><br/>
        <br/>
        THIS EMAIL WAS AUTO-GENERATED. PLEASE DO NOT REPLY TO THIS EMAIL.
"@


            $mailParams = @{
                To = $Configuration.MailTo
                From = $Configuration.MailFrom
                SmtpServer = $Configuration.SmtpServer
                Subject = $Subject
                Body = $EmailBody
                BodyAsHtml = $true
            }

            Send-MailMessage @mailParams

            } #End if
        }#End Foreach


        If ($PDC_SRV_RecordCount -ne 1) { 
                
                $Subject = "The PDC SRV record is missing from DNS"
                $EmailBody = @"
         
         
        The <font color="Red"><b> PDC SRV record</b></font> is missing from the $MSDCSZoneName in DNS.
        Time of Event: <font color="Red"><b> $((get-date))</b></font><br/>
        <br/>
        THIS EMAIL WAS AUTO-GENERATED. PLEASE DO NOT REPLY TO THIS EMAIL.
"@


            $mailParams = @{
                To = $Configuration.MailTo
                From = $Configuration.MailFrom
                SmtpServer = $Configuration.SmtpServer
                Subject = $Subject
                Body = $EmailBody
                BodyAsHtml = $true
            }
            
            Send-MailMessage @mailParams

            } #END PDC If

    }

    end {}
}
<#A simplified re-write of a script published by Jorge de Almeida Pinto, to be used primarily for non-interactive monitoring/alerting.
 
The original can be found here:
https://jorgequestforknowledge.wordpress.com/2014/02/17/testing-sysvol-replication-latencyconvergence-through-powershell-update-3/
 
#>


function Test-SysvolReplication {
    [CmdletBinding()]
    Param()
    <#
    .SYNOPSIS
    Monitor AD SYSVOL Replication
     
    .DESCRIPTION
    Each run of the script creates a unique test object in SYSVOL on the PDCE, and tracks it's replication to all other DCs in the domain.
    By default it will query the DCs for about 60 minutes. If after 60 loops the file hasn't repliated the test will terminate and create an alert.
 
    .EXAMPLE
    Run as a scheduled task. Use Event Log consolidation tools to pull and alert on issues found.
 
    .EXAMPLE
    Run in verbose mode if you want on-screen feedback for testing
    
    .NOTES
    Author Greg Onstot
    This script must be run from a Win10, or Server 2016 system. It can target older OS Versions.
    Version: 0.6.5
    Version Date: 4/18/2019
     
    Event Source 'PSMonitor' will be created
 
    EventID Definition:
    17000 - Failure
    17001 - Cycle Count
    17002 - Test Object not yet on DC
    17003 - Test Object on DC
    17004 - Tests didn't complete in alloted time span
    17005 - Job output
    17006 - Test Object Created
    17007 - Test Object Deleted
    17008 - 1 minute Sleep
    17009 - Alert Email Sent
    #>


    Begin {
        Import-Module activedirectory
        $null = Get-ADConfig
        $SupportArticle = $Configuration.SupportArticle
        if (![System.Diagnostics.EventLog]::SourceExists("PSMonitor")) {
            write-verbose "Adding Event Source."
            New-EventLog -LogName Application -Source "PSMonitor"
        }
        $continue = $true
        $CurrentFailure = $false
        $domainname = (Get-ADDomain).dnsroot
        $DCList = (Get-ADDomainController -Filter *).name
        $SourceSystem = (Get-ADDomain).pdcemulator
        [int]$MaxCycles = $Configuration.MaxSysvolReplCycles
    }
    
    Process {
        if (Test-NetConnection $SourceSystem -Port 445) {
            Write-Verbose 'PDCE is online'
            $TempObjectLocation = "\\$SourceSystem\SYSVOL\$domainname\Scripts"
            $tempObjectName = "sysvolReplTempObject" + (Get-Date -f yyyyMMddHHmmss) + ".txt"
            $objectPath = "\\$SourceSystem\SYSVOL\$domainname\Scripts\$tempObjectName"
            "...!!!...TEMP OBJECT TO TEST AD REPLICATION LATENCY/CONVERGENCE...!!!..." | Out-File -FilePath $($TempObjectLocation + "\" + $tempObjectName)
            $site = (Get-ADDomainController $SourceSystem).site

            Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17006 -EntryType Information -message "CREATE SYSVOL Test object - $tempObjectName - has been created on $SourceSystem in site - $site" -category "17006"
            Start-Sleep 30
            If (!(Test-Path -Path $objectPath)){
                Write-Verbose "Object wasn't created properly, trying a second time"
                $tempObjectName = "sysvolReplTempObject" + (Get-Date -f yyyyMMddHHmmss) + ".txt"
                $objectPath = "\\$SourceSystem\SYSVOL\$domainname\Scripts\$tempObjectName"
                "...!!!...TEMP OBJECT TO TEST AD REPLICATION LATENCY/CONVERGENCE...!!!..." | Out-File -FilePath $($TempObjectLocation + "\" + $tempObjectName)
                Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17006 -EntryType Information -message "CREATE SYSVOL Test object attempt Number 2 - $tempObjectName - has been created on $SourceSystem in site - $site" -category "17006"
                Start-Sleep 30
            }

            If (!(Test-Path -Path $objectPath)){
                Write-Verbose "Object wasn't created properly after 2 tries, exiting..."
                Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17000 -EntryType Error -message "FAILURE to write SYSVOL test object to PDCE - $SourceSystem in site - $site" -category "17000"
                #Write-Verbose "Sending Slack Alert"
                #New-SlackPost "Alert - FAILURE to write SYSVOL test object to PDCE - $SourceSystem in site - $site"
                Exit
            }
            
            $startDateTime = Get-Date
            $i = 0
        }
        else {
            Write-Verbose 'PDCE is offline. You should really resolve that before continuing.'
            Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17000 -EntryType Error -message "FAILURE to connect to PDCE - $SourceSystem in site - $site" -category "17000"
            #Write-Verbose "Sending Slack Alert"
            #New-SlackPost "Alert - FAILURE to connect to PDCE - $SourceSystem in site - $site"
            Exit
        }
        
        While ($continue) {
            $i++
            Write-Verbose 'Sleeping for 1 minute.'
            Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17008 -EntryType Information -message "SLEEPING SYSVOL test for 1 minute" -category "17008"
            Start-Sleep 60
            $replicated = $true
            Write-Verbose "Cycle - $i"
            Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17001 -EntryType Information -message "CHECKING SYSVOL ADRepl Cycle $i" -category "17001"
        
            Foreach ($dc in $DCList) {
                $site = (Get-ADDomainController $dc).site
                if (Test-NetConnection $dc -Port 445) {
                    Write-Verbose "Online - $dc"
                    $objectPath = "\\$dc\SYSVOL\$domainname\Scripts\$tempObjectName"
                    $connectionResult = "SUCCESS"
                }
                else {
                    Write-Verbose "!!!!!OFFLINE - $dc !!!!!"
                    $connectionResult = "FAILURE"
                    Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17000 -EntryType Error -message "FAILURE to connect to DC - $dc in site - $site" -category "17000"
                }
                # If The Connection To The DC Is Successful
                If ($connectionResult -eq "SUCCESS") {
                    If (Test-Path -Path $objectPath) {
                        # If The Temp Object Already Exists
                        Write-Verbose " - Object [$tempObjectName] Now Does Exist In The NetLogon Share"
                        Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17003 -EntryType Information -message "SUCCESS SYSVOL Object Successfully replicated to - $dc in site - $site" -category "17003"
                    }
                    Else {
                        # If The Temp Object Does Not Yet Exist
                        Write-Verbose " - Object [$tempObjectName] Does NOT Exist Yet In The NetLogon Share"
                        Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17002 -EntryType Information -message "PENDING SYSVOL Object replication pending for - $dc in site - $site" -category "17002"
                        $replicated = $false
                    }
                }
                
                # If The Connection To The DC Is Unsuccessful
                If ($connectionResult -eq "FAILURE") {
                    Write-Verbose " - Unable To Connect To DC/GC And Check For The Temp Object..."
                    Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17000 -EntryType Error -message "FAILURE to connect to DC - $dc in site - $site" -category "17000"
                }
            }
            If ($replicated) {
                $continue = $false
            } 
        
            If ($i -gt $MaxCycles) {
                $continue = $false
                #gather event history to see which DC did, and which did not, get the replication
                $list = Get-EventLog application -After (Get-Date).AddHours(-2) | where {($_.InstanceID -Match "17002") -OR ($_.InstanceID -Match "17003") -OR ($_.InstanceID -Match "17006")} 
                $RelevantEvents = $list |Select InstanceID,Message |Out-String
                
                Write-Verbose "Cycle has run $i times, and replication hasn't finished. Need to generate an alert."
                Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17004 -EntryType Warning -message "INCOMPLETE SYSVOL Test cycle has run $i times without the object succesfully replicating to all DCs" -category "17004"
                $Alert = "In $domainname - the SYSVOL test cycle has run $i times without the object succesfully replicating to all DCs.
                Please see the following support article $SupportArticle to help investigate
                 
                Recent history:
                $RelevantEvents
                "

                $CurrentFailure = $true
                Send-Mail $Alert
                #Write-Verbose "Sending Slack Alert"
                #New-SlackPost "Alert - Incomplete SYSVOL Replication Cycle in the domain: $domainname"
            } 
        }    
    }
    
    End {
        # Show The Start Time, The End Time And The Duration Of The Replication
        $endDateTime = Get-Date
        $duration = "{0:n2}" -f ($endDateTime.Subtract($startDateTime).TotalSeconds)
        $output = "`n Start Time......: $(Get-Date $startDateTime -format "yyyy-MM-dd HH:mm:ss")"
        $output = $output + "`n End Time........: $(Get-Date $endDateTime -format "yyyy-MM-dd HH:mm:ss")"
        $output = $output + "`n Duration........: $duration Seconds"
        Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17005 -EntryType Information -message "END of SYSVOL Test cycle - $output" -category "17005"
        
        Write-Verbose "`n Start Time......: $(Get-Date $startDateTime -format "yyyy-MM-dd HH:mm:ss")"
        Write-Verbose " End Time........: $(Get-Date $endDateTime -format "yyyy-MM-dd HH:mm:ss")"
        Write-Verbose " Duration........: $duration Seconds"
        
        # Delete The Temp Object On The RWDC
        Write-Verbose " Deleting Temp Text File..."
        Remove-Item "$TempObjectLocation\$tempObjectName" -Force
        Write-Verbose " Temp Text File [$tempObjectName] Has Been Deleted On The Source System"
        Write-eventlog -logname "Application" -Source "PSMonitor" -EventID 17007 -EntryType Information -message "DELETED SYSVOL Test object - $tempObjectName - has been deleted." -category "17007"

        If (!$CurrentFailure){
            Write-Verbose "No Issues found in this run"
            $InError = Get-EventLog application -After (Get-Date).AddHours(-2) | where {($_.InstanceID -Match "17000") -or ($_.InstanceID -Match "17004")} 
            If ($InError) {
                Write-Verbose "Previous Errors Seen"
                #Previous run had an alert
                #No errors foun during this test so send email that the previous error(s) have cleared
                Send-AlertCleared
                #Write-Verbose "Sending Slack Message - Alert Cleared"
                #New-SlackPost "The previous alert, for AD SYSVOL Replication, has cleared."
                #Write-Output $InError
            }#End if
        }#End if
    }#End End
}
$PublicFunctions = 'Copy-Scripts', 'Get-ADConfig', 'Get-ADLastBackupDate', 'Get-DCDiskSpace', 'Restore-PSADHealthConfig', 'Set-PSADHealthConfig', 'Test-ADConfigMailer', 'Test-ADObjectReplication', 'Test-ADReplication', 'Test-ADServices', 'Test-DCsOnline', 'Test-ExternalDNSServers', 'Test-ExternalTimeSync', 'Test-InternalTimeSync', 'Test-SRVRecords', 'Test-SYSVOL-Replication'