Friday, December 1, 2017

Track time sync across all domain controllers

I hate always harping on Windows time but since it sucks I can't help myself.

I wrote this script to watch the time on all our domain controllers, compare it, and alert me if it's off.

Be aware that this version doesn't have a window of larger than 1 minute.  I plan to fix that later.

Same as always, create the xml config file in the same folder.  See the end of the script for parameters for the file.  Use at your own risk.  Feel free to do what you want with the script (It would be great if you leave my name associated with this and any other scripts I write, but thats up to you.)

NOTE: Please use this as reference ONLY for now.  Until I can add a trigger window all this will do is spam you all week!

Param(
      [bool]$Console = $false
      )
<#==============================================================================
          File Name : DCTimeTest.ps1
    Original Author : Kenneth C. Mazie
                    :
        Description : Identifies all domain controllers and polls them for current time, then compares.
                    :
              Notes : Normal operation is with no command line options. 
                    :
   Optional argument: -Console $true (displays runtime info on console)
                    :
           Warnings : None
                    :  
              Legal : Public Domain. Modify and redistribute freely. No rights reserved.
                    : SCRIPT PROVIDED "AS IS" WITHOUT WARRANTIES OR GUARANTEES OF
                    : ANY KIND. USE AT YOUR OWN RISK. NO TECHNICAL SUPPORT PROVIDED.
                    :
            Credits : Code snippets and/or ideas came from many sources including but
                    :   not limited to the following:
                    :
     Last Update by : Kenneth C. Mazie
    Version History : v1.0 - 11-30-17 - Original
     Change History : v1.1 - 00-00-00 - Coming soon. Expected to allow finer comparison of minute differences.
                    :
#===============================================================================#>
#requires -version 5.0

Clear-host
$ErrorActionPreference = "silentlycontinue"
$Computer = $Env:ComputerName
$ScriptName = ($MyInvocation.MyCommand.Name).split(".")[0]
$Script:ConfigFile = "$PSScriptRoot\$ScriptName.xml"

Import-Module ActiveDirectory
 
$Script:Message = @()
$Script:MError = 0
$Script:HError = 0
$Script:LoopCount = 1

#--[ Add header to email report ]--
$Script:ReportBody = @()
$Script:ReportBody += '
<style type="text/css">
      table.myTable { border:5px solid black;border-collapse:collapse; }
    table.myTable td { border:2px solid black;padding:5px}
      table.myTable th { border:2px solid black;padding:5px;background: #949494 }
      table.bottomBorder { border-collapse:collapse; }
      table.bottomBorder td, table.bottomBorder th { border-bottom:1px dotted black;padding:5px; }
      tr.noBorder td {border: 0; }
</style>'

$Script:ReportBody +=
'<table class="myTable">
<tr class="noBorder"><td colspan=2><center><h1>- Domain Controller Time Sync Error -</h1></td></tr>
<tr class="noBorder"><td colspan=2><center>A time synchronization error has been detected on a domain controller.</td></tr>
<tr><th>Domain Controller</th><th>Detected Time</th></tr>
'

#--[ Read and load configuration file ]-------------------------------------
If (!(Test-Path $Script:ConfigFile)){       #--[ Error out if configuration file doesn't exist ]--
    Write-Host "---------------------------------------------" -ForegroundColor Red
    Write-Host "--[ MISSING CONFIG FILE.  Script aborted. ]--" -ForegroundColor Red
    Write-Host "---------------------------------------------" -ForegroundColor Red
    break
}Else{
      [xml]$Script:Configuration = Get-Content "$PSScriptRoot\$ScriptName.xml"      #--[ Load configuration ]--
     
    $Script:DebugEmail = $Script:Configuration.Settings.Email.Debug
    $Script:EmailTo = $Script:Configuration.Settings.Email.To
    $Script:EmailFrom = $Script:Configuration.Settings.Email.From
    $Script:Subject = $Script:Configuration.Settings.Email.Subject
    $Script:EmailHTML = $Script:Configuration.Settings.Email.HTML
    $Script:UserName = $Script:Configuration.Settings.Credentials.Username
    $Script:EncryptedPW = $Script:Configuration.Settings.Credentials.Password
    $Script:Base64String = $Script:Configuration.Settings.Credentials.Key
    $Script:SmtpServer = $Script:Configuration.Settings.Email.SmtpServer
    $Script:UserName = $Script:Configuration.Settings.Credentials.Username
    $Script:EncryptedPW = $Script:Configuration.Settings.Credentials.Password
    $Script:Base64String = $Script:Configuration.Settings.Credentials.Key  
    $ByteArray = [System.Convert]::FromBase64String($Script:Base64String);
    $Script:Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Script:UserName, ($Script:EncryptedPW | ConvertTo-SecureString -Key $ByteArray)
}    
#-------------------------------------------------------------------------------

Function eMail {
    If ($Console){Write-Host "-- Sending Email" -ForegroundColor Yellow}
    $SMTP = new-object System.Net.Mail.SmtpClient($Script:SmtpServer)
    $Email = New-Object System.Net.Mail.MailMessage
    $Email.Body = $Script:ReportBody
    $Email.IsBodyHtml = $Script:EmailHTML
    $Email.To.Add($Script:EmailTo)
    $Email.From = $Script:EmailFrom
    $Email.Subject = $Script:Subject
    $SMTP.Send($Email)
    $Email.Dispose()
    $SMTP.Dispose()     
}

Function GetTimes {
    If ($Console){Write-Host "`nGetting Data..." -ForegroundColor Yellow}
    $Script:HourResults = @()
    $Script:MinuteResults = @()
    $FGColor = "#000000"
    $BGColor = "#bbbbbb"
    $BGColorRed = "#bbbbbb"
    $BGColorOra = "#bbbbbb"
    $BGColorYel = "#bbbbbb"
    $Script:RunSummary = @()
   
    foreach($DC in $Script:DcList){
        if ($DC.Name -ne $Env:computername){
            try{
                If ($Console){Write-Host "-- Polling:"$DC.Name.PadRight(13," ") -ForegroundColor cyan -NoNewline }
                $Data = @(Get-WmiObject -Class win32_localtime -ComputerName $DC.name -Credential $Script:Credential -ErrorAction "stop"#--[ Array with all returned time data for selected DC ]--
                $HMS = (($Data.Hour).ToString().PadLeft(2,"0"))+':'+(($Data.Minute).ToString().PadLeft(2,"0"))+':'+(($Data.Second).ToString().PadLeft(2,"0"))
                If ($Console){Write-Host $HMS}
            }catch{
                $_.Exception.Message
            }
        }Else{
            #--[ local time if run from a DC ]--         
        }       
        $Script:HourResults += ($Data.Hour).ToString()
        $Script:MinuteResults += ($Data.Minute).ToString()
        $RowData += '<tr>'
        $RowData += '<td bgcolor='+$BGColor+'><font color='+$FGColor+'>'+$Data.PSComputerName+'</td>'
        $RowData += '<td bgcolor='+$BGColor+'><font color='+$FGColor+'>'+$HMS+'</td>'
      $RowData += '</tr>'
    }
    $Script:RunSummary += $RowData
}


Function CheckHour {

        If (($Script:HourResults | Sort-Object | Get-Unique).count -gt 1){
        #If (($Script:Results[0]).Hour -eq ($Script:Results[1]).Hour -eq ($Script:Results[2]).Hour -eq ($Script:Results[3]).Hour){
            If ($Console){Write-Host "-- Hour mis-match" -ForegroundColor red}
            #If ($Console){Write-Host  ($Script:Results[0])[1]"  "($Script:Results[1])[1]"   "($Script:Results[1])[1]"   "($Script:Results[1])[1]}
            $Script:HError       
    }Else{       
        If ($Console){Write-Host "-- Hour match" -ForegroundColor green}
        $Script:HError = $False
    }
}

Function CheckMinute {
    If (($Script:MinuteResults | Sort-Object | Get-Unique).count -gt 1){
        If ($Console){Write-Host "-- Minute mis-match" -ForegroundColor red}
        $Script:MError = $True
    }Else{
        If ($Console){Write-Host "-- Minute match" -ForegroundColor green}
        $Script:MError = $False
    }  
}

#--[ Main Loop ]-----------------------------------------------------

#$Script:DcList = Get-ADGroupMember 'Domain Controllers' -Credential $Script:Credential
$Script:DcList = Get-ADDomainController -Credential $Script:Credential -Filter *

While(($Script:HError) -or ($Script:MError) -or ($Script:LoopCount -eq 1)){
    If ($Console -and ($Script:LoopCount -gt 1)){Write-Host "`nLooping..." -ForegroundColor yellow}
    Write-Host "`n--[ Loop Iteration: $Script:LoopCount ]--------------------------------"
    GetTimes 
    If ($Console){Write-Host "`nComparing..." -ForegroundColor Yellow}
    CheckHour
    CheckMinute

    If(($Script:HError) -or ($Script:MError)){
        $SleepSec = 6
        If ($Console){Write-Host "`nLooping..." -ForegroundColor yellow}
        If ($Console){Write-Host "-- Sleeping $SleepSec sec..." -ForegroundColor cyan
        Sleep -Seconds $SleepSec
    }   
    $Script:LoopCount++
    If (($Script:LoopCount % 11) -eq 0){    #--[ Send email every 10 loops (counter starts at 1) ]--
        $Script:ReportBody +=$Script:RunSummary
        $Script:ReportBody += '<tr class="noBorder"><td colspan=2><font size=2 color=#909090>Script "'+$MyInvocation.MyCommand.Name+'" executed from server "'+$env:computername+'".</td></tr>'
        $Script:ReportBody += '</table><br><br>'
        eMail
    }
}

If ((!$Script:HError) -and (!$Script:MError)){
    If ($Console){Write-Host "`n-- Good Result. No further action required." -ForegroundColor green
    If ($Console){Write-Host "`n--- COMPLETED ---`n" -ForegroundColor red}
}


<#--[ Sample Config file ]--------------------------------------------------------------------
<!-- Settings & Configuration File -->
<Settings>
      <General>
            <DebugTarget>testpc</DebugTarget>
      </General>
      <Email>
            <From>PowerShell@domain.com</From>
            <To>you@domain.com</To>
            <Debug>you@domain.com</Debug>
            <Subject>Domain Controller Time Sync Error</Subject>
            <HTML>$true</HTML>
            <SmtpServer>10.10.10.50</SmtpServer>
      </Email>
      <Credentials>
            <UserName>domain\serviceaccount</UserName>
            <Password>764A0AGEAMAAwf0423413b16050a5345MgBA0AGEAMAAw0a5AWQB6AHoFEAPQA9AHA0AGEAMAAwAZgA3ADIAYQAwADYAZAADQAZgBiAGMAYQBA0AGEAMAAwQAzAAeQA0AEcAaAB1AGYANAAyADUAYQA2AGQAZAA2ADQAYwBkAA0ADEANgBiADAANwBkADEANAA4AGQA3AGUAZgBkAGYAZAA=</Password>
            <Key>kdh/AWnHbuC+Eyj2CvLO6/AWnHbuTh7HCvLOIObie8mE=</Key>
      </Credentials>
</Settings>


#>